fix: retain current occurrence when submitting search

This commit is contained in:
Valdemar Lorenzen
2026-10-15 12:31:13 +02:00
parent adc44d22db
commit f5c6d098f8
5 changed files with 66 additions and 14 deletions
+26 -10
View File
@@ -8426,7 +8426,7 @@ impl Editor {
self.active.view.search_query = command;
self.active.view.search_whole_word = false;
}
self.seek_search(Action::SearchNext, 1);
self.seek_search(Action::Search, 1);
} else {
return self.execute(&command);
}
@@ -8531,7 +8531,16 @@ impl Editor {
cursor.row,
grapheme_byte(&self.active.lines[cursor.row], cursor.column),
);
let first = if forward {
let current = (action == Action::Search)
.then(|| matches.partition_point(|position| *position <= origin))
.and_then(|end| end.checked_sub(1))
.filter(|index| {
let found = matches[*index];
found.0 == origin.0 && origin.1 < found.1 + query.len()
});
let first = if let Some(current) = current {
current
} else if forward {
matches.partition_point(|position| *position <= origin) % matches.len()
} else {
matches
@@ -8546,15 +8555,18 @@ impl Editor {
(first + matches.len() - distance) % matches.len()
};
let found = matches[destination];
target.active = Cursor {
let start = Cursor {
row: found.0,
column: column_after_byte(&self.active.lines[found.0], found.1),
};
target.preferred_columns = None;
if current.is_none() {
target.active = start;
target.preferred_columns = None;
}
if index == self.active.view.primary {
self.active.view.search_match = Some(SearchMatch {
start: target.active,
end_column: target.active.column + grapheme_count(query),
start,
end_column: start.column + grapheme_count(query),
});
}
}
@@ -16112,7 +16124,7 @@ mod tests {
type_text(&mut editor, "Vjy");
assert!(editor.clipboard_linewise);
assert_eq!(editor.clipboard, ["alpha beta alpha\nsecond line\n"]);
type_text(&mut editor, "gt/alpha\n");
type_text(&mut editor, "gt/alpha\nn");
assert_eq!(editor.cursor(), Cursor { row: 0, column: 11 });
editor.handle(Input::Char('n'));
assert_eq!(editor.cursor(), Cursor::default());
@@ -17364,7 +17376,7 @@ mod tests {
editor
.apply(vec![Effect::Replace("hit\n中👩‍💻 hit\nhit".into())])
.unwrap();
type_text(&mut editor, "/hit\n");
type_text(&mut editor, "/hit\nn");
assert_eq!(editor.cursor(), Cursor { row: 1, column: 3 });
type_text(&mut editor, "nN");
assert_eq!(editor.jumps.len(), 4);
@@ -17789,7 +17801,7 @@ mod tests {
editor
.apply(vec![Effect::Replace("hit\nhit".into())])
.unwrap();
type_text(&mut editor, "/hit\n");
type_text(&mut editor, "/hit\nn");
editor.handle(Input::Ctrl('k'));
assert_eq!(editor.carets().len(), 2);
let targets = editor.active.view.targets.clone();
@@ -22061,6 +22073,7 @@ mod tests {
.unwrap();
set_carets(&mut editor, &[(0, 0), (1, 0)], 1);
submit_search(&mut editor, "cat");
editor.handle(Input::Char('n'));
type_text(&mut editor, "2n");
assert_eq!(
editor.carets(),
@@ -22771,6 +22784,7 @@ mod tests {
.apply(vec![Effect::Replace("hit a\nz\nhit b\nz\nhit c\n".into())])
.unwrap();
submit_search(&mut editor, "hit");
editor.handle(Input::Char('n'));
assert_eq!(editor.cursor().row, 2);
type_text(&mut editor, "Vn");
assert_eq!(editor.linewise_rows().collect::<Vec<_>>(), [2..=4]);
@@ -23477,6 +23491,7 @@ mod tests {
assert_eq!(editor.buffer_id(), 2);
set_carets(&mut editor, &[(0, 0), (1, 0)], 0);
submit_search(&mut editor, "dog");
editor.handle(Input::Char('n'));
let b_carets = editor.carets();
let b_primary = editor.cursor();
editor.handle(Input::Char('i'));
@@ -24794,6 +24809,7 @@ mod tests {
assert_eq!(editor.selection_count(), 2);
assert_eq!(editor.cursor(), Cursor { row: 1, column: 0 });
submit_search(&mut editor, "cat");
editor.handle(Input::Char('n'));
assert_eq!(
editor.carets(),
vec![Cursor { row: 0, column: 4 }, Cursor { row: 1, column: 4 }]
@@ -24950,7 +24966,7 @@ mod tests {
assert_eq!(editor.search_match(), active);
assert!(editor.command_line().is_empty());
submit_search(&mut editor, "");
assert_eq!(editor.cursor(), Cursor { row: 1, column: 4 });
assert_eq!(editor.cursor(), cursor);
let cursor = editor.cursor();
submit_search(&mut editor, "missing");
+1 -1
View File
@@ -47,7 +47,7 @@ Insert: Ctrl+n requests LSP completion at primary caret, then cycles next; Ctrl+
Snippets expand fully: type replaces argument, Tab/Shift+Tab next/previous, final $0 ends
Outside snippets Enter inserts newline, Tab inserts a tab or configured spaces; Esc returns to Normal
Insert/Command/Search: Ctrl+u deletes backward to the logical line start
Search: Enter finds next literal match, Esc cancels (case-sensitive, wraps)
Search: Enter accepts current/next literal match, Esc cancels (case-sensitive, wraps)
Commands: :save [PATH], :force_save [PATH], :quit, :force_quit,
:save_and_quit [PATH], :force_save_and_quit [PATH], :plugins, :PLUGIN [ARGS]
Aliases: :w=save, :w!=force_save, :q=quit, :q!=force_quit,
+2
View File
@@ -4311,6 +4311,8 @@ end
session.handle(Input::Enter);
assert_eq!(session.editor().mode(), crate::editor::Mode::Search);
session.handle(Input::Ctrl('x'));
assert_eq!(session.editor().cursor().row, 0);
session.handle(Input::Ctrl('n'));
assert_eq!(session.editor().cursor().row, 1);
session.handle(Input::Ctrl('n'));
assert_eq!(session.editor().cursor().row, 2);