pull/13683/merge
Andrew Browne 2025-06-14 14:15:47 -07:00 committed by GitHub
commit df3f66d723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 146 additions and 1 deletions

View File

@ -374,6 +374,7 @@ impl MappableCommand {
search_prev, "Select previous search match",
extend_search_next, "Add next search match to selection",
extend_search_prev, "Add previous search match to selection",
extend_search_all, "Add every search match to selection",
search_selection, "Use current selection as search pattern",
search_selection_detect_word_boundaries, "Use current selection as the search pattern, automatically wrapping with `\\b` on word boundaries",
make_search_word_bounded, "Modify current search to make it word bounded",
@ -2134,6 +2135,7 @@ fn search_impl(
editor: &mut Editor,
regex: &rope::Regex,
movement: Movement,
start_location: Direction,
direction: Direction,
scrolloff: usize,
wrap_around: bool,
@ -2145,7 +2147,7 @@ fn search_impl(
// Get the right side of the primary block cursor for forward search, or the
// grapheme before the start of the selection for reverse search.
let start = match direction {
let start = match start_location {
Direction::Forward => text.char_to_byte(graphemes::ensure_grapheme_boundary_next(
text,
selection.primary().to(),
@ -2265,6 +2267,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
&regex,
movement,
direction,
direction,
scrolloff,
wrap_around,
false,
@ -2302,6 +2305,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
&regex,
movement,
direction,
direction,
scrolloff,
wrap_around,
true,
@ -2329,6 +2333,68 @@ fn extend_search_prev(cx: &mut Context) {
search_next_or_prev_impl(cx, Movement::Extend, Direction::Backward);
}
fn extend_search_all(cx: &mut Context) {
let register = cx
.register
.unwrap_or(cx.editor.registers.last_search_register);
let config = cx.editor.config();
let scrolloff = config.scrolloff;
if let Some(query) = cx.editor.registers.first(register, cx.editor) {
let search_config = &config.search;
let case_insensitive = if search_config.smart_case {
!query.chars().any(char::is_uppercase)
} else {
false
};
if let Ok(regex) = rope::RegexBuilder::new()
.syntax(
rope::Config::new()
.case_insensitive(case_insensitive)
.multi_line(true),
)
.build(&query)
{
search_impl(
cx.editor,
&regex,
Movement::Extend,
// Start the first search before the current location.
// This is so after ["search_selection", "extend_search_all"]
// the primary selection remains where it started.
Direction::Backward,
Direction::Forward,
scrolloff,
true,
true,
);
// extend_search_next until we reach the first match
let (view, doc) = current_ref!(cx.editor);
let first_match = doc.selection(view.id).primary();
loop {
search_impl(
cx.editor,
&regex,
Movement::Extend,
Direction::Forward,
Direction::Forward,
scrolloff,
true,
true,
);
let (view, doc) = current_ref!(cx.editor);
let current_match = doc.selection(view.id).primary();
if current_match == first_match {
break;
}
}
} else {
let error = format!("Invalid regex: {}", query);
cx.editor.set_error(error);
}
}
}
fn search_selection(cx: &mut Context) {
search_selection_impl(cx, false)
}

View File

@ -2,6 +2,7 @@ use helix_term::application::Application;
use super::*;
mod extend_search_all;
mod insert;
mod movement;
mod write;

View File

@ -0,0 +1,78 @@
use super::*;
use helix_core::hashmap;
use helix_term::keymap;
use helix_view::document::Mode;
#[tokio::test(flavor = "multi_thread")]
async fn extend_search_all() -> anyhow::Result<()> {
let mut config = Config::default();
config.keys.insert(
Mode::Normal,
keymap!({"Normal Mode"
// Typically you would want to bind these to the same key.
// e.g. "key" = ["search_selection", "extend_search_all"]
"a" => search_selection,
"b" => extend_search_all,
}),
);
// Test extend_search_all
test_with_config(
AppBuilder::new().with_config(config.clone()),
(
indoc! {"\
one_cat one cat three
two_dog two dog xthreex
#[three|]#_cow three cow
"},
"ab",
indoc! {"\
one_cat one cat #(three|)#
two_dog two dog x#(three|)#x
#[three|]#_cow #(three|)# cow
"},
),
)
.await?;
// Test extend_search_all preserves selection direction.
test_with_config(
AppBuilder::new().with_config(config.clone()),
(
indoc! {"\
one_cat one cat three
two_dog two dog xthreex
#[|three]#_cow three cow
"},
"ab",
indoc! {"\
one_cat one cat #(|three)#
two_dog two dog x#(|three)#x
#[|three]#_cow #(|three)# cow
"},
),
)
.await?;
// Test extend_search_all with multiple queries.
test_with_config(
AppBuilder::new().with_config(config.clone()),
(
indoc! {"\
one_#(|cat)# one cat three
two_dog two dog xthreex
#[|three]#_cow three cow cattle
"},
"ab",
indoc! {"\
one_#(|cat)# one #(|cat)# #(|three)#
two_dog two dog x#(|three)#x
#[|three]#_cow #(|three)# cow #(|cat)#tle
"},
),
)
.await?;
Ok(())
}