Update default vim substitute command behavior and add support for 'g' flag (#28138)

This Pull Request updates the default behavior of the substitute (`s`)
command in vim mode to only replace the next match by default, instead
of all, and replace all matches only when the `g` flag is provided,
making it more similar to NeoVim's behavior.

In order to achieve this, the following changes were introduced:

- Update `BufferSearchBar::replace_next` to be a public method, so it
can be called from `Vim::replace_command` .
- Update the `Replacement::parse` to set the `should_replace_all` field
to `false` by default, and only set it to `true` if the `'g'` flag is
present in the query.
- Add support for when the `Replacement.should_replace_all` is set to
`false` in `Vim::replace_command`, so as to have it only replace the
next occurrence instead of all occurrences in the line.
- Introduce `BufferSearchBar::select_first_match` so as to activate the
first match on the line under the cursor.

Closes #24450 

Release Notes:

- Improved vim's substitute command so as to only replace the first
match by default, and replace all matches if the `'g'` flag is provided

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Dino 2025-04-09 21:34:51 +01:00 committed by GitHub
parent 60c420a2da
commit af5318df98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 79 additions and 26 deletions

View file

@ -1540,8 +1540,24 @@ impl SearchableItem for Editor {
let text = self.buffer.read(cx);
let text = text.snapshot(cx);
let mut edits = vec![];
let mut last_point: Option<Point> = None;
for m in matches {
let point = m.start.to_point(&text);
let text = text.text_for_range(m.clone()).collect::<Vec<_>>();
// Check if the row for the current match is different from the last
// match. If that's not the case and we're still replacing matches
// in the same row/line, skip this match if the `one_match_per_line`
// option is enabled.
if last_point.is_none() {
last_point = Some(point);
} else if last_point.is_some() && point.row != last_point.unwrap().row {
last_point = Some(point);
} else if query.one_match_per_line().is_some_and(|enabled| enabled) {
continue;
}
let text: Cow<_> = if text.len() == 1 {
text.first().cloned().unwrap().into()
} else {