Skip to content

Commit 76626ce

Browse files
kareltucekclaude
andcommitted
Fix key drop after code-expansion in tapKeySeq (consumeWhite context pop)
consumeWhite()'s whitespace-skipping loop evaluated `*ctx->at <= 32` before calling isEnd(). isEnd() may pop an exhausted parser context (such as the sub-context pushed by tapKeySeq's expandCodes for hexCodeOf/decCodeOf/ altCodeOf/uCodeOf), which moves ctx->at to the parent's next token. Because the dereference happened first and the loop body then ran ctx->at++, the first character of that parent token was stepped over and dropped. Concretely, `tapKeySeq x y hexCodeOf(!) x y` typed `x y 2 1 y` — the `x` following the expansion was lost. Reordering the condition so isEnd() (and its context pop) runs before the dereference keeps ctx->at on the restored token. Verified on uhk-80-right: the TapKeySeq regression test now passes and the full on-device test suite is green (52/52). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 27613d1 commit 76626ce

2 files changed

Lines changed: 10 additions & 13 deletions

File tree

right/src/str_utils.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,15 @@ bool IsEnd(parser_context_t* ctx) {
136136
static void consumeWhite(parser_context_t* ctx)
137137
{
138138
while (!isEnd(ctx)) {
139-
while (*ctx->at <= 32 && !isEnd(ctx)) {
139+
// isEnd() may pop an exhausted parser context (e.g. an expandCodes/
140+
// template expansion), moving ctx->at to the parent's next token. It
141+
// must be checked *before* dereferencing ctx->at, otherwise a ctx->at++
142+
// here would step over that token's first character and drop it.
143+
while (!isEnd(ctx) && *ctx->at <= 32) {
140144
ctx->at++;
141145
}
142146
if (ctx->at[0] == '/' && ctx->at[1] == '/' && consumeCommentsAsWhite) {
143-
while (*ctx->at != '\n' && !isEnd(ctx)) {
147+
while (!isEnd(ctx) && *ctx->at != '\n') {
144148
ctx->at++;
145149
}
146150
}
@@ -322,7 +326,7 @@ void ConsumeAnyIdentifier(parser_context_t* ctx)
322326

323327
void ConsumeUntilDot(parser_context_t* ctx)
324328
{
325-
while(*ctx->at > 32 && *ctx->at != '.' && !isEnd(ctx)) {
329+
while(!isEnd(ctx) && *ctx->at > 32 && *ctx->at != '.') {
326330
ctx->at++;
327331
}
328332
if (*ctx->at != '.') {

right/src/test_suite/tests/test_tapkeyseq.c

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
#include "tests.h"
22

3-
// Regression test for a tapKeySeq bug: when a code-expansion command such as
4-
// hexCodeOf(...) sits in the middle of the sequence, the key that immediately
5-
// follows the expansion used to be dropped.
3+
// Regression test for a template expansion bug.
64
//
7-
// `tapKeySeq x y hexCodeOf(!) x y` must type
8-
// x y 2 1 x y
9-
// ('!' == U+0021, so hexCodeOf(!) expands to the key sequence `2 1`). The bug
10-
// dropped the final key of the whole sequence whenever a code-expansion command
11-
// preceded it, so the tail came out as `... x` instead of `... x y`. Distinct
12-
// keys (x/y) around the expansion make a dropped key surface as a wrong-char
13-
// mismatch rather than only a timeout.
5+
// `tapKeySeq x y hexCodeOf(!) x y` would produce "xy21y" due to wrong ConsumeWhite/isEnd expansion.
6+
147
static const test_action_t test_tapkeyseq_hexcode[] = {
158
TEST_SET_MACRO("u",
169
"tapKeySeq x y hexCodeOf(!) x y\n"

0 commit comments

Comments
 (0)