mirror of https://github.com/helix-editor/helix
Add extend_search_all command.
This allows me to replicate my kakoune config: map global normal <a-%> '*%s<ret>' With helix config: "A-%" = ["search_selection", "extend_search_all"]pull/13683/head
parent
f6878f62f7
commit
6a20aae363
|
@ -374,6 +374,7 @@ impl MappableCommand {
|
||||||
search_prev, "Select previous search match",
|
search_prev, "Select previous search match",
|
||||||
extend_search_next, "Add next search match to selection",
|
extend_search_next, "Add next search match to selection",
|
||||||
extend_search_prev, "Add previous 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, "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",
|
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",
|
make_search_word_bounded, "Modify current search to make it word bounded",
|
||||||
|
@ -2132,6 +2133,7 @@ fn search_impl(
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
regex: &rope::Regex,
|
regex: &rope::Regex,
|
||||||
movement: Movement,
|
movement: Movement,
|
||||||
|
start_location: Direction,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
scrolloff: usize,
|
scrolloff: usize,
|
||||||
wrap_around: bool,
|
wrap_around: bool,
|
||||||
|
@ -2143,7 +2145,7 @@ fn search_impl(
|
||||||
|
|
||||||
// Get the right side of the primary block cursor for forward search, or the
|
// Get the right side of the primary block cursor for forward search, or the
|
||||||
// grapheme before the start of the selection for reverse search.
|
// 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(
|
Direction::Forward => text.char_to_byte(graphemes::ensure_grapheme_boundary_next(
|
||||||
text,
|
text,
|
||||||
selection.primary().to(),
|
selection.primary().to(),
|
||||||
|
@ -2263,6 +2265,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
|
||||||
®ex,
|
®ex,
|
||||||
movement,
|
movement,
|
||||||
direction,
|
direction,
|
||||||
|
direction,
|
||||||
scrolloff,
|
scrolloff,
|
||||||
wrap_around,
|
wrap_around,
|
||||||
false,
|
false,
|
||||||
|
@ -2300,6 +2303,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
|
||||||
®ex,
|
®ex,
|
||||||
movement,
|
movement,
|
||||||
direction,
|
direction,
|
||||||
|
direction,
|
||||||
scrolloff,
|
scrolloff,
|
||||||
wrap_around,
|
wrap_around,
|
||||||
true,
|
true,
|
||||||
|
@ -2327,6 +2331,68 @@ fn extend_search_prev(cx: &mut Context) {
|
||||||
search_next_or_prev_impl(cx, Movement::Extend, Direction::Backward);
|
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,
|
||||||
|
®ex,
|
||||||
|
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,
|
||||||
|
®ex,
|
||||||
|
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) {
|
fn search_selection(cx: &mut Context) {
|
||||||
search_selection_impl(cx, false)
|
search_selection_impl(cx, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use helix_term::application::Application;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
mod extend_search_all;
|
||||||
mod insert;
|
mod insert;
|
||||||
mod movement;
|
mod movement;
|
||||||
mod write;
|
mod write;
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
Loading…
Reference in New Issue