From c4af7819a3974eb5aadcb918c679b23a9c22767a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 15 Jan 2022 00:20:16 +0100 Subject: [PATCH 1/3] feat(editor): add config for search wrap_around Fixes: https://github.com/helix-editor/helix/issues/1489 --- helix-term/src/commands.rs | 39 ++++++++++++++++++++++++++++---------- helix-view/src/editor.rs | 17 +++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 71ac8f093513..93ea410f9963 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1465,6 +1465,7 @@ fn search_impl( movement: Movement, direction: Direction, scrolloff: usize, + wrap_around: bool, ) { let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -1490,16 +1491,22 @@ fn search_impl( // use find_at to find the next match after the cursor, loop around the end // Careful, `Regex` uses `bytes` as offsets, not character indices! - let mat = match direction { - Direction::Forward => regex - .find_at(contents, start) - .or_else(|| regex.find(contents)), - Direction::Backward => regex.find_iter(&contents[..start]).last().or_else(|| { - offset = start; - regex.find_iter(&contents[start..]).last() - }), + let mut mat = match direction { + Direction::Forward => regex.find_at(contents, start), + Direction::Backward => regex.find_iter(&contents[..start]).last(), }; - // TODO: message on wraparound + + if wrap_around && mat.is_none() { + mat = match direction { + Direction::Forward => regex.find(contents), + Direction::Backward => { + offset = start; + regex.find_iter(&contents[start..]).last() + } + } + // TODO: message on wraparound + } + if let Some(mat) = mat { let start = text.byte_to_char(mat.start() + offset); let end = text.byte_to_char(mat.end() + offset); @@ -1552,6 +1559,7 @@ fn rsearch(cx: &mut Context) { fn searcher(cx: &mut Context, direction: Direction) { let reg = cx.register.unwrap_or('/'); let scrolloff = cx.editor.config.scrolloff; + let wrap_around = cx.editor.config.search.wrap_around; let doc = doc!(cx.editor); @@ -1585,6 +1593,7 @@ fn searcher(cx: &mut Context, direction: Direction) { Movement::Move, direction, scrolloff, + wrap_around, ); }, ); @@ -1604,11 +1613,21 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir } else { false }; + let wrap_around = cx.editor.config.search.wrap_around; if let Ok(regex) = RegexBuilder::new(query) .case_insensitive(case_insensitive) .build() { - search_impl(doc, view, &contents, ®ex, movement, direction, scrolloff); + search_impl( + doc, + view, + &contents, + ®ex, + movement, + direction, + scrolloff, + wrap_around, + ); } else { // get around warning `mutable_borrow_reservation_conflict` // which will be a hard error in the future diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index f4b0f73e72a8..73b8c2fa8a81 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -107,6 +107,16 @@ pub struct Config { pub file_picker: FilePickerConfig, /// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`. pub true_color: bool, + /// Search configuration. + #[serde(default)] + pub search: SearchConfig, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct SearchConfig { + /// Whether the search should wrap after depleting the matches. Default to true. + pub wrap_around: bool, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -152,10 +162,17 @@ impl Default for Config { auto_info: true, file_picker: FilePickerConfig::default(), true_color: false, + search: SearchConfig::default(), } } } +impl Default for SearchConfig { + fn default() -> Self { + Self { wrap_around: true } + } +} + pub struct Motion(pub Box); impl Motion { pub fn run(&self, e: &mut Editor) { From b7fa4f1028c9f9eaba33a7bac7a40f3fc508a6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 15 Jan 2022 00:29:57 +0100 Subject: [PATCH 2/3] Move search settings into separate config --- helix-term/src/commands.rs | 10 ++++++---- helix-term/src/ui/mod.rs | 2 +- helix-view/src/editor.rs | 10 ++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 93ea410f9963..910f38763a05 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1608,12 +1608,13 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir if let Some(query) = registers.read('/') { let query = query.last().unwrap(); let contents = doc.text().slice(..).to_string(); - let case_insensitive = if cx.editor.config.smart_case { + let search_config = &cx.editor.config.search; + let case_insensitive = if search_config.smart_case { !query.chars().any(char::is_uppercase) } else { false }; - let wrap_around = cx.editor.config.search.wrap_around; + let wrap_around = search_config.wrap_around; if let Ok(regex) = RegexBuilder::new(query) .case_insensitive(case_insensitive) .build() @@ -1666,7 +1667,7 @@ fn search_selection(cx: &mut Context) { fn global_search(cx: &mut Context) { let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); - let smart_case = cx.editor.config.smart_case; + let smart_case = cx.editor.config.search.smart_case; let file_picker_config = cx.editor.config.file_picker.clone(); let completions = search_completions(cx, None); @@ -2705,12 +2706,13 @@ pub mod cmd { "mouse" => runtime_config.mouse = arg.parse()?, "line-number" => runtime_config.line_number = arg.parse()?, "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, - "smart-case" => runtime_config.smart_case = arg.parse()?, "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, "auto-completion" => runtime_config.auto_completion = arg.parse()?, "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, "auto-info" => runtime_config.auto_info = arg.parse()?, "true-color" => runtime_config.true_color = arg.parse()?, + "search.smart-case" => runtime_config.search.smart_case = arg.parse()?, + "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, _ => anyhow::bail!("Unknown key `{}`.", args[0]), } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 9ff9118f545e..ba89870951b6 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -65,7 +65,7 @@ pub fn regex_prompt( return; } - let case_insensitive = if cx.editor.config.smart_case { + let case_insensitive = if cx.editor.config.search.smart_case { !input.chars().any(char::is_uppercase) } else { false diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 73b8c2fa8a81..c8fb9aa4cb97 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -92,8 +92,6 @@ pub struct Config { pub line_number: LineNumber, /// Middle click paste support. Defaults to true. pub middle_click_paste: bool, - /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true. - pub smart_case: bool, /// Automatic insertion of pairs to parentheses, brackets, etc. Defaults to true. pub auto_pairs: bool, /// Automatic auto-completion, automatically pop up without user trigger. Defaults to true. @@ -115,6 +113,8 @@ pub struct Config { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct SearchConfig { + /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true. + pub smart_case: bool, /// Whether the search should wrap after depleting the matches. Default to true. pub wrap_around: bool, } @@ -154,7 +154,6 @@ impl Default for Config { }, line_number: LineNumber::Absolute, middle_click_paste: true, - smart_case: true, auto_pairs: true, auto_completion: true, idle_timeout: Duration::from_millis(400), @@ -169,7 +168,10 @@ impl Default for Config { impl Default for SearchConfig { fn default() -> Self { - Self { wrap_around: true } + Self { + wrap_around: true, + smart_case: true, + } } } From 0a206e2fce88e4a74c2cdddcf83df4f642417f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Thu, 20 Jan 2022 12:48:24 +0100 Subject: [PATCH 3/3] Disable linter --- helix-term/src/commands.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 910f38763a05..f38c20c52eee 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1457,6 +1457,7 @@ fn split_selection_on_newline(cx: &mut Context) { doc.set_selection(view.id, selection); } +#[allow(clippy::too_many_arguments)] fn search_impl( doc: &mut Document, view: &mut View,