diff --git a/book/src/keymap.md b/book/src/keymap.md index 28113ea0b..10693b208 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -154,13 +154,13 @@ ### Search Search commands all operate on the `/` register by default. To use a different register, use `"`. -| Key | Description | Command | -| ----- | ----------- | ------- | -| `/` | Search for regex pattern | `search` | -| `?` | Search for previous pattern | `rsearch` | -| `n` | Select next search match | `search_next` | -| `N` | Select previous search match | `search_prev` | -| `*` | Use current selection as the search pattern | `search_selection` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `/` | Search for regex pattern | `search` | +| `?` | Search for previous pattern | `rsearch` | +| `n` | Select next search match | `search_next` | +| `N` | Select previous search match | `search_prev` | +| `*` | Use current selection as the search pattern, if only a single char is selected the current word (miW) is used instead | `search_selection` | ### Minor modes diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 61855d356..c847ca3df 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2266,19 +2266,37 @@ fn extend_search_prev(cx: &mut Context) { fn search_selection(cx: &mut Context) { let register = cx.register.unwrap_or('/'); + let count = cx.count(); let (view, doc) = current!(cx.editor); let contents = doc.text().slice(..); - let regex = doc - .selection(view.id) - .iter() - .map(|selection| regex::escape(&selection.fragment(contents))) - .collect::>() // Collect into hashset to deduplicate identical regexes - .into_iter() - .collect::>() - .join("|"); + // Checks whether there is only one selection with a width of 1 + let selections = doc.selection(view.id); + let primary = selections.primary(); + let regex = if selections.len() == 1 && primary.len() == 1 { + let text = doc.text(); + let text_slice = text.slice(..); + // In this case select the WORD under the cursor + let current_word = textobject::textobject_word( + text_slice, + primary, + textobject::TextObject::Inside, + count, + false, + ); + let text_to_search = current_word.fragment(text_slice).to_string(); + regex::escape(&text_to_search) + } else { + selections + .iter() + .map(|selection| regex::escape(&selection.fragment(contents))) + .collect::>() // Collect into hashset to deduplicate identical regexes + .into_iter() + .collect::>() + .join("|") + }; - let msg = format!("register '{}' set to '{}'", register, ®ex); + let msg = format!("register '{}' set to '{}'", '/', ®ex); match cx.editor.registers.push(register, regex) { Ok(_) => { cx.editor.registers.last_search_register = register; diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index f71ae308d..d8e5f2a98 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -178,6 +178,56 @@ fn match_paths(app: &Application, matches: Vec<&str>) -> usize { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn test_search_selection() -> anyhow::Result<()> { + // Single selection with a length of 1: search for the whole word + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some("ifoobar::baz3bl*"), // 3b places the cursor on the first letter of 'foobar', then move one to the right for good measure + Some(&|app| { + assert!( + r#"register '/' set to 'foobar'"# == app.editor.get_status().unwrap().0 + && Some(&"foobar".to_string()) == app.editor.registers.first('/') + ); + }), + false, + ) + .await?; + + // Single selection with a length greather than 1: only search for the selection + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some("ifoobar::baz3blvll*"), // 3b places the cursor on the first letter of 'foobar', then move one to the right for good measure, then select two more chars for a total of three + Some(&|app| { + assert!( + r#"register '/' set to 'oob'"# == app.editor.get_status().unwrap().0 + && Some(&"oob".to_string()) == app.editor.registers.first('/') + ); + }), + false, + ) + .await?; + + // Multiple selection of length 1 each : should still only search for the selection + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some("ifoobar::bazbar::cruxk3blC*"), // k3b places the cursor on the first letter of 'foobar', then move one to the right for good measure, then adds a cursor on the line below + Some(&|app| { + assert!( + // The selections don't seem to be ordered, so we have to test for the two possible orders. + (r#"register '/' set to 'o|a'"# == app.editor.get_status().unwrap().0 + || r#"register '/' set to 'a|o'"# == app.editor.get_status().unwrap().0) + && (Some(&"o|a".to_string()) == app.editor.registers.first('/') + || Some(&"a|o".to_string()) == app.editor.registers.first('/')) + ); + }), + false, + ) + .await?; + + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn test_multi_selection_paste() -> anyhow::Result<()> { test((