Command.cc: share the word scans, search reset and completion prefix

Word motion (M-b/M-f) and word deletion (M-Backspace/M-d) each carried a
copy of the same backward and forward scans; word_back and word_forward
now serve both. Deleting N words backwards built the kill text by
prepending each piece (quadratic in N); the pieces are joined once. The
five-call search reset appeared nine times (end_search), and the
completion longest-common-prefix loop twice (keep_common_prefix).

A new test pins word motion and deletion with counts across line ends
and punctuation; it gives the same results on the code before this
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 12:19:59 -07:00
committed by Kyle Isom
parent 95bcf8ef09
commit 7039c14507
2 changed files with 183 additions and 196 deletions
+52
View File
@@ -131,3 +131,55 @@ TEST(CommandSemantics_Syntax_OnOff_SetsUserOverride)
ASSERT_EQ(b.SyntaxEnabled(), true);
ASSERT_EQ(b.SyntaxUserOverride(), true);
}
// Word motion and word deletion with counts, across line ends and
// punctuation; deleting backwards N words kills them in text order.
TEST(CommandSemantics_WordMotionAndDelete_WithCounts)
{
const std::string text = "alpha beta, gamma\n\n delta.epsilon zeta\nend";
struct Pos {
std::size_t x, y;
};
auto run_motion = [&](CommandId id, Pos start, int count) {
TestHarness h;
h.Buf().insert_text(0, 0, text);
h.Buf().SetCursor(start.x, start.y);
(void) h.Exec(id, "", count);
return Pos{h.Buf().Curx(), h.Buf().Cury()};
};
Pos p = run_motion(CommandId::WordNext, {0, 0}, 1);
ASSERT_EQ(p.x, (std::size_t) 6);
p = run_motion(CommandId::WordNext, {0, 0}, 3);
ASSERT_EQ(p.y, (std::size_t) 2);
ASSERT_EQ(p.x, (std::size_t) 2);
p = run_motion(CommandId::WordNext, {0, 0}, 5);
ASSERT_EQ(p.y, (std::size_t) 2);
ASSERT_EQ(p.x, (std::size_t) 16);
p = run_motion(CommandId::WordPrev, {3, 3}, 1);
ASSERT_EQ(p.y, (std::size_t) 3);
ASSERT_EQ(p.x, (std::size_t) 0);
p = run_motion(CommandId::WordPrev, {3, 3}, 3);
ASSERT_EQ(p.y, (std::size_t) 2);
ASSERT_EQ(p.x, (std::size_t) 8);
p = run_motion(CommandId::WordPrev, {3, 3}, 50);
ASSERT_EQ(p.y, (std::size_t) 0);
ASSERT_EQ(p.x, (std::size_t) 0);
{
TestHarness h;
h.Buf().insert_text(0, 0, text);
h.Buf().SetCursor(3, 3);
ASSERT_TRUE(h.Exec(CommandId::DeleteWordPrev, "", 3));
ASSERT_EQ(h.Text(), std::string("alpha beta, gamma\n\n delta."));
ASSERT_EQ(h.EditorRef().KillRingHead(), std::string("epsilon zeta\nend"));
}
{
TestHarness h;
h.Buf().insert_text(0, 0, text);
h.Buf().SetCursor(6, 0);
ASSERT_TRUE(h.Exec(CommandId::DeleteWordNext, "", 2));
ASSERT_EQ(h.Text(), std::string("alpha delta.epsilon zeta\nend"));
ASSERT_EQ(h.EditorRef().KillRingHead(), std::string("beta, gamma\n\n "));
}
}