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:
+101
-166
@@ -243,6 +243,29 @@ ensure_cursor_visible(const Editor &ed, Buffer &buf)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Shorten prefix to the part it shares with s (completion).
|
||||||
|
static void
|
||||||
|
keep_common_prefix(std::string &prefix, std::string_view s)
|
||||||
|
{
|
||||||
|
std::size_t j = 0;
|
||||||
|
while (j < prefix.size() && j < s.size() && prefix[j] == s[j])
|
||||||
|
++j;
|
||||||
|
prefix.resize(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Leave search mode entirely: no active search, query, match or origin.
|
||||||
|
static void
|
||||||
|
end_search(Editor &ed)
|
||||||
|
{
|
||||||
|
ed.SetSearchActive(false);
|
||||||
|
ed.SetSearchQuery("");
|
||||||
|
ed.SetSearchMatch(0, 0, 0);
|
||||||
|
ed.ClearSearchOrigin();
|
||||||
|
ed.SetSearchIndex(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
cmd_new_window(CommandContext &ctx)
|
cmd_new_window(CommandContext &ctx)
|
||||||
{
|
{
|
||||||
@@ -1512,11 +1535,7 @@ cmd_refresh(CommandContext &ctx)
|
|||||||
buf->SetCursor(ctx.editor.SearchOrigX(), ctx.editor.SearchOrigY());
|
buf->SetCursor(ctx.editor.SearchOrigX(), ctx.editor.SearchOrigY());
|
||||||
buf->SetOffsets(ctx.editor.SearchOrigRowoffs(), ctx.editor.SearchOrigColoffs());
|
buf->SetOffsets(ctx.editor.SearchOrigRowoffs(), ctx.editor.SearchOrigColoffs());
|
||||||
}
|
}
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
}
|
}
|
||||||
// Clear any pending close/overwrite state associated with prompts
|
// Clear any pending close/overwrite state associated with prompts
|
||||||
ctx.editor.SetCloseConfirmPending(false);
|
ctx.editor.SetCloseConfirmPending(false);
|
||||||
@@ -1534,11 +1553,7 @@ cmd_refresh(CommandContext &ctx)
|
|||||||
buf->SetCursor(ctx.editor.SearchOrigX(), ctx.editor.SearchOrigY());
|
buf->SetCursor(ctx.editor.SearchOrigX(), ctx.editor.SearchOrigY());
|
||||||
buf->SetOffsets(ctx.editor.SearchOrigRowoffs(), ctx.editor.SearchOrigColoffs());
|
buf->SetOffsets(ctx.editor.SearchOrigRowoffs(), ctx.editor.SearchOrigColoffs());
|
||||||
}
|
}
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
ctx.editor.SetStatus("Find canceled");
|
ctx.editor.SetStatus("Find canceled");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2416,15 +2431,8 @@ cmd_insert_text(CommandContext &ctx)
|
|||||||
} else {
|
} else {
|
||||||
// extend to longest common prefix
|
// extend to longest common prefix
|
||||||
std::string lcp = cands[0].first;
|
std::string lcp = cands[0].first;
|
||||||
for (std::size_t i = 1; i < cands.size(); ++i) {
|
for (std::size_t i = 1; i < cands.size() && !lcp.empty(); ++i)
|
||||||
const std::string &s = cands[i].first;
|
keep_common_prefix(lcp, cands[i].first);
|
||||||
std::size_t j = 0;
|
|
||||||
while (j < lcp.size() && j < s.size() && lcp[j] == s[j])
|
|
||||||
++j;
|
|
||||||
lcp.resize(j);
|
|
||||||
if (lcp.empty())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!lcp.empty() && lcp != ctx.editor.PromptText())
|
if (!lcp.empty() && lcp != ctx.editor.PromptText())
|
||||||
ctx.editor.SetPromptText(lcp);
|
ctx.editor.SetPromptText(lcp);
|
||||||
}
|
}
|
||||||
@@ -2493,16 +2501,9 @@ cmd_insert_text(CommandContext &ctx)
|
|||||||
ctx.editor.SetPromptText(cands[0]);
|
ctx.editor.SetPromptText(cands[0]);
|
||||||
} else {
|
} else {
|
||||||
// Longest common prefix of display strings
|
// Longest common prefix of display strings
|
||||||
auto lcp = cands[0];
|
std::string lcp = cands[0];
|
||||||
for (size_t i = 1; i < cands.size(); ++i) {
|
for (std::size_t i = 1; i < cands.size() && !lcp.empty(); ++i)
|
||||||
const auto &s = cands[i];
|
keep_common_prefix(lcp, cands[i]);
|
||||||
size_t j = 0;
|
|
||||||
while (j < lcp.size() && j < s.size() && lcp[j] == s[j])
|
|
||||||
++j;
|
|
||||||
lcp.resize(j);
|
|
||||||
if (lcp.empty())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!lcp.empty() && lcp != ctx.editor.PromptText()) {
|
if (!lcp.empty() && lcp != ctx.editor.PromptText()) {
|
||||||
ctx.editor.SetPromptText(lcp);
|
ctx.editor.SetPromptText(lcp);
|
||||||
} else {
|
} else {
|
||||||
@@ -3094,11 +3095,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
if (buf->IsReadOnly()) {
|
if (buf->IsReadOnly()) {
|
||||||
ctx.editor.SetStatus("Read-only buffer");
|
ctx.editor.SetStatus("Read-only buffer");
|
||||||
// Clear search UI state
|
// Clear search UI state
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const std::string find = ctx.editor.ReplaceFindTmp();
|
const std::string find = ctx.editor.ReplaceFindTmp();
|
||||||
@@ -3107,11 +3104,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
if (find.empty()) {
|
if (find.empty()) {
|
||||||
ctx.editor.SetStatus("Replace canceled (empty find)");
|
ctx.editor.SetStatus("Replace canceled (empty find)");
|
||||||
// Clear search UI state
|
// Clear search UI state
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Save original cursor to restore after operations
|
// Save original cursor to restore after operations
|
||||||
@@ -3154,11 +3147,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
std::snprintf(msg, sizeof(msg), "Replaced %zu occurrence%s", total, (total == 1 ? "" : "s"));
|
std::snprintf(msg, sizeof(msg), "Replaced %zu occurrence%s", total, (total == 1 ? "" : "s"));
|
||||||
ctx.editor.SetStatus(msg);
|
ctx.editor.SetStatus(msg);
|
||||||
// Clear search-highlighting state after replace completes
|
// Clear search-highlighting state after replace completes
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
return true;
|
return true;
|
||||||
} else if (kind == Editor::PromptKind::OpenFile) {
|
} else if (kind == Editor::PromptKind::OpenFile) {
|
||||||
// Expand "~" to the user's home directory
|
// Expand "~" to the user's home directory
|
||||||
@@ -3530,11 +3519,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
if (buf->IsReadOnly()) {
|
if (buf->IsReadOnly()) {
|
||||||
ctx.editor.SetStatus("Read-only buffer");
|
ctx.editor.SetStatus("Read-only buffer");
|
||||||
// Clear search UI state
|
// Clear search UI state
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const std::string patt = ctx.editor.ReplaceFindTmp();
|
const std::string patt = ctx.editor.ReplaceFindTmp();
|
||||||
@@ -3542,11 +3527,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
ctx.editor.SetReplaceWithTmp(repl);
|
ctx.editor.SetReplaceWithTmp(repl);
|
||||||
if (patt.empty()) {
|
if (patt.empty()) {
|
||||||
ctx.editor.SetStatus("Regex replace canceled (empty pattern)");
|
ctx.editor.SetStatus("Regex replace canceled (empty pattern)");
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
kte::Regex rx;
|
kte::Regex rx;
|
||||||
@@ -3554,11 +3535,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
if (!rx.Compile(patt, rx_err)) {
|
if (!rx.Compile(patt, rx_err)) {
|
||||||
ctx.editor.SetStatus("Regex error: " + rx_err);
|
ctx.editor.SetStatus("Regex error: " + rx_err);
|
||||||
// Clear search UI state
|
// Clear search UI state
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
std::size_t changed = 0;
|
std::size_t changed = 0;
|
||||||
@@ -3597,11 +3574,7 @@ cmd_newline(CommandContext &ctx)
|
|||||||
ctx.editor.SetStatus("Regex replaced in " + std::to_string(changed) + " line(s)" +
|
ctx.editor.SetStatus("Regex replaced in " + std::to_string(changed) + " line(s)" +
|
||||||
(limited ? " (pattern too complex on some lines; those left as they were)" : ""));
|
(limited ? " (pattern too complex on some lines; those left as they were)" : ""));
|
||||||
// Clear search UI state
|
// Clear search UI state
|
||||||
ctx.editor.SetSearchActive(false);
|
end_search(ctx.editor);
|
||||||
ctx.editor.SetSearchQuery("");
|
|
||||||
ctx.editor.SetSearchMatch(0, 0, 0);
|
|
||||||
ctx.editor.ClearSearchOrigin();
|
|
||||||
ctx.editor.SetSearchIndex(-1);
|
|
||||||
if (auto *b = ctx.editor.CurrentBuffer())
|
if (auto *b = ctx.editor.CurrentBuffer())
|
||||||
ensure_cursor_visible(ctx.editor, *b);
|
ensure_cursor_visible(ctx.editor, *b);
|
||||||
return true;
|
return true;
|
||||||
@@ -4763,28 +4736,15 @@ is_word_char(unsigned char c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// One word back from (x, y), y < rows.size(): past whitespace (across line
|
||||||
|
// ends), then to the start of the word. False at the start of the buffer.
|
||||||
static bool
|
static bool
|
||||||
cmd_word_prev(CommandContext &ctx)
|
word_back(const RowsView &rows, std::size_t &x, std::size_t &y)
|
||||||
{
|
{
|
||||||
Buffer *buf = ctx.editor.CurrentBuffer();
|
|
||||||
if (!buf)
|
|
||||||
return false;
|
|
||||||
if (auto *u = buf->Undo())
|
|
||||||
u->commit();
|
|
||||||
ensure_at_least_one_line(*buf);
|
|
||||||
auto rows = rows_of(*buf);
|
|
||||||
std::size_t y = buf->Cury();
|
|
||||||
std::size_t x = buf->Curx();
|
|
||||||
int repeat = ctx.count > 0 ? ctx.count : 1;
|
|
||||||
while (repeat-- > 0) {
|
|
||||||
if (y >= rows.size()) {
|
|
||||||
y = rows.empty() ? 0 : rows.size() - 1;
|
|
||||||
x = rows[y].size();
|
|
||||||
}
|
|
||||||
// If at start of line and not first line, move to end of previous line
|
// If at start of line and not first line, move to end of previous line
|
||||||
if (x == 0) {
|
if (x == 0) {
|
||||||
if (y == 0)
|
if (y == 0)
|
||||||
break;
|
return false;
|
||||||
--y;
|
--y;
|
||||||
x = rows[y].size();
|
x = rows[y].size();
|
||||||
}
|
}
|
||||||
@@ -4813,29 +4773,15 @@ cmd_word_prev(CommandContext &ctx)
|
|||||||
break;
|
break;
|
||||||
--x;
|
--x;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
buf->SetCursor(x, y);
|
|
||||||
ensure_cursor_visible(ctx.editor, *buf);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
// One word forward from (x, y): to the end of the word under the cursor,
|
||||||
cmd_word_next(CommandContext &ctx)
|
// then past non-word characters (across line ends) to the next word.
|
||||||
|
static void
|
||||||
|
word_forward(const RowsView &rows, std::size_t &x, std::size_t &y)
|
||||||
{
|
{
|
||||||
Buffer *buf = ctx.editor.CurrentBuffer();
|
|
||||||
if (!buf)
|
|
||||||
return false;
|
|
||||||
if (auto *u = buf->Undo())
|
|
||||||
u->commit();
|
|
||||||
ensure_at_least_one_line(*buf);
|
|
||||||
auto rows = rows_of(*buf);
|
|
||||||
std::size_t y = buf->Cury();
|
|
||||||
std::size_t x = buf->Curx();
|
|
||||||
int repeat = ctx.count > 0 ? ctx.count : 1;
|
|
||||||
while (repeat-- > 0) {
|
|
||||||
if (y >= rows.size())
|
|
||||||
break;
|
|
||||||
// First, if currently on a word, skip to its end
|
// First, if currently on a word, skip to its end
|
||||||
while (y < rows.size()) {
|
while (y < rows.size()) {
|
||||||
if (x < rows[y].size() && is_word_char(static_cast<unsigned char>(rows[y][x]))) {
|
if (x < rows[y].size() && is_word_char(static_cast<unsigned char>(rows[y][x]))) {
|
||||||
@@ -4869,6 +4815,53 @@ cmd_word_next(CommandContext &ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
cmd_word_prev(CommandContext &ctx)
|
||||||
|
{
|
||||||
|
Buffer *buf = ctx.editor.CurrentBuffer();
|
||||||
|
if (!buf)
|
||||||
|
return false;
|
||||||
|
if (auto *u = buf->Undo())
|
||||||
|
u->commit();
|
||||||
|
ensure_at_least_one_line(*buf);
|
||||||
|
auto rows = rows_of(*buf);
|
||||||
|
std::size_t y = buf->Cury();
|
||||||
|
std::size_t x = buf->Curx();
|
||||||
|
int repeat = ctx.count > 0 ? ctx.count : 1;
|
||||||
|
while (repeat-- > 0) {
|
||||||
|
if (y >= rows.size()) {
|
||||||
|
y = rows.empty() ? 0 : rows.size() - 1;
|
||||||
|
x = rows[y].size();
|
||||||
|
}
|
||||||
|
if (!word_back(rows, x, y))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf->SetCursor(x, y);
|
||||||
|
ensure_cursor_visible(ctx.editor, *buf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
cmd_word_next(CommandContext &ctx)
|
||||||
|
{
|
||||||
|
Buffer *buf = ctx.editor.CurrentBuffer();
|
||||||
|
if (!buf)
|
||||||
|
return false;
|
||||||
|
if (auto *u = buf->Undo())
|
||||||
|
u->commit();
|
||||||
|
ensure_at_least_one_line(*buf);
|
||||||
|
auto rows = rows_of(*buf);
|
||||||
|
std::size_t y = buf->Cury();
|
||||||
|
std::size_t x = buf->Curx();
|
||||||
|
int repeat = ctx.count > 0 ? ctx.count : 1;
|
||||||
|
while (repeat-- > 0) {
|
||||||
|
if (y >= rows.size())
|
||||||
|
break;
|
||||||
|
word_forward(rows, x, y);
|
||||||
|
}
|
||||||
buf->SetCursor(x, y);
|
buf->SetCursor(x, y);
|
||||||
ensure_cursor_visible(ctx.editor, *buf);
|
ensure_cursor_visible(ctx.editor, *buf);
|
||||||
return true;
|
return true;
|
||||||
@@ -4888,7 +4881,7 @@ cmd_delete_word_prev(CommandContext &ctx)
|
|||||||
std::size_t y = buf->Cury();
|
std::size_t y = buf->Cury();
|
||||||
std::size_t x = buf->Curx();
|
std::size_t x = buf->Curx();
|
||||||
int repeat = ctx.count > 0 ? ctx.count : 1;
|
int repeat = ctx.count > 0 ? ctx.count : 1;
|
||||||
std::string killed_total;
|
std::vector<std::string> killed;
|
||||||
UndoGroupGuard guard(buf->Undo());
|
UndoGroupGuard guard(buf->Undo());
|
||||||
for (int i = 0; i < repeat; ++i) {
|
for (int i = 0; i < repeat; ++i) {
|
||||||
if (y >= rows.size()) {
|
if (y >= rows.size()) {
|
||||||
@@ -4897,44 +4890,17 @@ cmd_delete_word_prev(CommandContext &ctx)
|
|||||||
}
|
}
|
||||||
std::size_t start_y = y;
|
std::size_t start_y = y;
|
||||||
std::size_t start_x = x;
|
std::size_t start_x = x;
|
||||||
// If at start of line and not first line, move to end of previous line
|
if (!word_back(rows, x, y))
|
||||||
if (x == 0) {
|
|
||||||
if (y == 0)
|
|
||||||
break;
|
break;
|
||||||
--y;
|
|
||||||
x = rows[y].size();
|
|
||||||
}
|
|
||||||
// Move left one first
|
|
||||||
if (x > 0)
|
|
||||||
--x;
|
|
||||||
// Skip any whitespace leftwards
|
|
||||||
while (y < rows.size() && (x > 0 || (x == 0 && y > 0))) {
|
|
||||||
if (x == 0) {
|
|
||||||
--y;
|
|
||||||
x = rows[y].size();
|
|
||||||
if (x == 0)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
unsigned char c = x > 0 ? static_cast<unsigned char>(rows[y][x - 1]) : 0;
|
|
||||||
if (!std::isspace(c))
|
|
||||||
break;
|
|
||||||
--x;
|
|
||||||
}
|
|
||||||
// Skip word characters leftwards
|
|
||||||
while (y < rows.size() && (x > 0 || (x == 0 && y > 0))) {
|
|
||||||
if (x == 0)
|
|
||||||
break;
|
|
||||||
unsigned char c = static_cast<unsigned char>(rows[y][x - 1]);
|
|
||||||
if (!is_word_char(c))
|
|
||||||
break;
|
|
||||||
--x;
|
|
||||||
}
|
|
||||||
// Now delete from (x, y) to (start_x, start_y) using PieceTable
|
// Now delete from (x, y) to (start_x, start_y) using PieceTable
|
||||||
std::string deleted = extract_region_text(*buf, x, y, start_x, start_y);
|
killed.push_back(extract_region_text(*buf, x, y, start_x, start_y));
|
||||||
delete_region(*buf, x, y, start_x, start_y, buf->Undo());
|
delete_region(*buf, x, y, start_x, start_y, buf->Undo());
|
||||||
// Prepend to killed_total (since we're deleting backwards)
|
|
||||||
killed_total = deleted + killed_total;
|
|
||||||
}
|
}
|
||||||
|
// Deleted backwards: the last piece comes first (joined once; prepending
|
||||||
|
// each piece was quadratic in the repeat count).
|
||||||
|
std::string killed_total;
|
||||||
|
for (auto it = killed.rbegin(); it != killed.rend(); ++it)
|
||||||
|
killed_total += *it;
|
||||||
buf->SetCursor(x, y);
|
buf->SetCursor(x, y);
|
||||||
buf->SetDirty(true);
|
buf->SetDirty(true);
|
||||||
ensure_cursor_visible(ctx.editor, *buf);
|
ensure_cursor_visible(ctx.editor, *buf);
|
||||||
@@ -4969,38 +4935,7 @@ cmd_delete_word_next(CommandContext &ctx)
|
|||||||
break;
|
break;
|
||||||
std::size_t start_y = y;
|
std::size_t start_y = y;
|
||||||
std::size_t start_x = x;
|
std::size_t start_x = x;
|
||||||
// First, if currently on a word, skip to its end
|
word_forward(rows, x, y);
|
||||||
while (y < rows.size()) {
|
|
||||||
if (x < rows[y].size() && is_word_char(static_cast<unsigned char>(rows[y][x]))) {
|
|
||||||
++x;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (x >= rows[y].size()) {
|
|
||||||
if (y + 1 >= rows.size())
|
|
||||||
break;
|
|
||||||
++y;
|
|
||||||
x = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Then, skip any non-word characters (including punctuation and whitespace)
|
|
||||||
while (y < rows.size()) {
|
|
||||||
if (x < rows[y].size()) {
|
|
||||||
unsigned char c = static_cast<unsigned char>(rows[y][x]);
|
|
||||||
if (is_word_char(c))
|
|
||||||
break;
|
|
||||||
++x;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (x >= rows[y].size()) {
|
|
||||||
if (y + 1 >= rows.size())
|
|
||||||
break;
|
|
||||||
++y;
|
|
||||||
x = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now delete from (start_x, start_y) to (x, y) using PieceTable
|
// Now delete from (start_x, start_y) to (x, y) using PieceTable
|
||||||
std::string deleted = extract_region_text(*buf, start_x, start_y, x, y);
|
std::string deleted = extract_region_text(*buf, start_x, start_y, x, y);
|
||||||
delete_region(*buf, start_x, start_y, x, y, buf->Undo());
|
delete_region(*buf, start_x, start_y, x, y, buf->Undo());
|
||||||
|
|||||||
@@ -131,3 +131,55 @@ TEST(CommandSemantics_Syntax_OnOff_SetsUserOverride)
|
|||||||
ASSERT_EQ(b.SyntaxEnabled(), true);
|
ASSERT_EQ(b.SyntaxEnabled(), true);
|
||||||
ASSERT_EQ(b.SyntaxUserOverride(), 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 "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user