sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzFix vim count tokens as words And add meta command labels With Claude
src/text/vim_buffer.cpp | 88 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/src/text/vim_buffer.cpp b/src/text/vim_buffer.cpp index ec4ad20..6a387aa 100644 --- a/src/text/vim_buffer.cpp +++ b/src/text/vim_buffer.cpp @@ -52,8 +52,17 @@ void VimBuffer::key_escape() if (_mode == Mode::Command) { cancel_Command(); return; } + // <Esc> cancels any half-typed normal-mode command — a pending count ("12") + // and/or operator ("d", "c", "y", "g", ">", "<") — so it disappears from the + // showcmd, matching vim. The cursor only nudges left when actually leaving + // insert mode, not when cancelling a count in normal mode. + const bool was_insert = (_mode == Mode::Insert); + _mode = Mode::Normal; - if (_col > 0) --_col; + _count = 0; + _pending.clear(); + + if (was_insert && _col > 0) --_col; clamp_Col(); touch(); } @@ -388,8 +397,46 @@ VimBuffer::is_Dirty() const { return _dirty; } +// ── word motion treats each TOKEN as a vim word ────────────────────────────── +// The classes mirror the game tokenizer (handler.cpp relayout/chunker): a word +// run [A-Za-z0-9_], an operator run from the op set, single-char punctuation, +// or a whitespace run. So dw/cw/yw/w/e/b stop on token boundaries exactly where +// the rendered tokens split — e.g. dw on "fuu(buu)" with the cursor on 'f' +// deletes just "fuu". +namespace { + +enum CharClass : uint8_t { CC_SPACE, CC_WORD, CC_OP, CC_PUNCT }; + +inline CharClass +char_Class(char ch) +{ + if (ch == ' ' || ch == '\t') return CC_SPACE; + if ((ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= '9') || ch == '_') return CC_WORD; + for (const char* o = "+-*/%=<>!&|^~.:?"; *o; ++o) // op set + if (ch == *o) return CC_OP; + return CC_PUNCT; +} + +// Exclusive end of the token that starts at p: word/op/space form runs, a punct +// char is a token of its own (matching how the renderer splits tokens). +inline int +token_End(const std::string& s, int p) +{ + const int n = static_cast<int>(s.size()); + if (p >= n) return p; + const CharClass c = char_Class(s[static_cast<size_t>(p)]); + if (c == CC_PUNCT) return p + 1; + while (p < n && char_Class(s[static_cast<size_t>(p)]) == c) ++p; + return p; +} + +} // namespace + + int -VimBuffer::word_EndCol(int times) const +VimBuffer::word_EndCol(int times) const // cw: exclusive end of token(s) { const std::string& line = _lines[static_cast<size_t>(_row)]; @@ -397,14 +444,14 @@ VimBuffer::word_EndCol(int times) const int c = _col; for (int k = 0; k < times; ++k) { - while (c < n && line[static_cast<size_t>(c)] == ' ') ++c; // skip spaces - while (c < n && line[static_cast<size_t>(c)] != ' ') ++c; // to end of word + while (c < n && char_Class(line[static_cast<size_t>(c)]) == CC_SPACE) ++c; + if (c < n) c = token_End(line, c); } return c; } int -VimBuffer::next_WordCol(int times) const +VimBuffer::next_WordCol(int times) const // w: start of the next token { const std::string& line = _lines[static_cast<size_t>(_row)]; @@ -412,8 +459,10 @@ VimBuffer::next_WordCol(int times) const int c = _col; for (int k = 0; k < times; ++k) { - while (c < n && line[static_cast<size_t>(c)] != ' ') ++c; // skip current word - while (c < n && line[static_cast<size_t>(c)] == ' ') ++c; // skip spaces to next word + if (c >= n) break; + if (char_Class(line[static_cast<size_t>(c)]) != CC_SPACE) + c = token_End(line, c); // past this token + while (c < n && char_Class(line[static_cast<size_t>(c)]) == CC_SPACE) ++c; } return c; } @@ -423,7 +472,7 @@ VimBuffer::next_WordCol(int times) const void VimBuffer::yank_Lines(int n) { - const int end = (_row + n < static_cast<int>(_lines.size())) + const int end = (_row + n < static_cast<int>(_lines.size())) ? _row + n : static_cast<int>(_lines.size()); @@ -585,7 +634,7 @@ VimBuffer::indent_Lines(int n, int dir) } -// ── e: last char of the n-th word forward (always advances at least one) ───── +// ── e: last char of the n-th token forward (always advances at least one) ──── int VimBuffer::end_WordCol(int times) const { @@ -594,24 +643,31 @@ VimBuffer::end_WordCol(int times) const if (n == 0) return 0; int c = _col; for (int k = 0; k < times; ++k) { - if (c < n) ++c; // step off current char - while (c < n && line[static_cast<size_t>(c)] == ' ') ++c; // skip spaces - while (c + 1 < n && line[static_cast<size_t>(c + 1)] != ' ') ++c; // to last char of word + ++c; // step off current char + while (c < n && char_Class(line[static_cast<size_t>(c)]) == CC_SPACE) ++c; + if (c < n) { + const int e = token_End(line, c); // exclusive end + c = (e > c) ? e - 1 : c; // → last char of token + } } return (c < n) ? c : n - 1; } -// ── b: start of the n-th word backward (always retreats at least one) ──────── +// ── b: start of the n-th token backward (always retreats at least one) ─────── int VimBuffer::prev_WordCol(int times) const { const std::string& line = _lines[static_cast<size_t>(_row)]; int c = _col; for (int k = 0; k < times; ++k) { - if (c > 0) --c; // step off current char - while (c > 0 && line[static_cast<size_t>(c)] == ' ') --c; // skip spaces backward - while (c > 0 && line[static_cast<size_t>(c - 1)] != ' ') --c; // to first char of word + --c; // step off current char + while (c >= 0 && char_Class(line[static_cast<size_t>(c)]) == CC_SPACE) --c; + if (c < 0) break; + const CharClass cl = char_Class(line[static_cast<size_t>(c)]); + if (cl != CC_PUNCT) // word/op run → its start + while (c - 1 >= 0 && char_Class(line[static_cast<size_t>(c - 1)]) == cl) --c; + // CC_PUNCT is a single-char token: c already sits at its start } return (c > 0) ? c : 0; }