From 11260e6d37fa3f5dcc99154d4958b6693291c832 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 19 Dec 2024 09:48:54 -0300 Subject: [PATCH] Match keymap-style action names in command palette (#22149) For example, `editor::TabPrev` matches "editor: tab prev". Release Notes: - Added support for searching command palette using keymap-style action names. --------- Co-authored-by: Peter Tripp --- crates/command_palette/src/command_palette.rs | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 405c7e7228..f065a986a1 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -36,21 +36,26 @@ pub struct CommandPalette { picker: View>, } -fn trim_consecutive_whitespaces(input: &str) -> String { +/// Removes subsequent whitespace characters and double colons from the query. +/// +/// This improves the likelihood of a match by either humanized name or keymap-style name. +fn normalize_query(input: &str) -> String { let mut result = String::with_capacity(input.len()); - let mut last_char_was_whitespace = false; + let mut last_char = None; for char in input.trim().chars() { - if char.is_whitespace() { - if !last_char_was_whitespace { - result.push(char); + match (last_char, char) { + (Some(':'), ':') => continue, + (Some(last_char), char) if last_char.is_whitespace() && char.is_whitespace() => { + continue + } + _ => { + last_char = Some(char); } - last_char_was_whitespace = true; - } else { - result.push(char); - last_char_was_whitespace = false; } + result.push(char); } + result } @@ -258,7 +263,7 @@ impl PickerDelegate for CommandPaletteDelegate { let mut commands = self.all_commands.clone(); let hit_counts = cx.global::().clone(); let executor = cx.background_executor().clone(); - let query = trim_consecutive_whitespaces(query.as_str()); + let query = normalize_query(query.as_str()); async move { commands.sort_by_key(|action| { ( @@ -463,6 +468,25 @@ mod tests { ); } + #[test] + fn test_normalize_query() { + assert_eq!(normalize_query("editor: backspace"), "editor: backspace"); + assert_eq!(normalize_query("editor: backspace"), "editor: backspace"); + assert_eq!(normalize_query("editor: backspace"), "editor: backspace"); + assert_eq!( + normalize_query("editor::GoToDefinition"), + "editor:GoToDefinition" + ); + assert_eq!( + normalize_query("editor::::GoToDefinition"), + "editor:GoToDefinition" + ); + assert_eq!( + normalize_query("editor: :GoToDefinition"), + "editor: :GoToDefinition" + ); + } + #[gpui::test] async fn test_command_palette(cx: &mut TestAppContext) { let app_state = init_test(cx); @@ -533,6 +557,40 @@ mod tests { assert!(palette.delegate.matches.is_empty()) }); } + #[gpui::test] + async fn test_normalized_matches(cx: &mut TestAppContext) { + let app_state = init_test(cx); + let project = Project::test(app_state.fs.clone(), [], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); + + let editor = cx.new_view(|cx| { + let mut editor = Editor::single_line(cx); + editor.set_text("abc", cx); + editor + }); + + workspace.update(cx, |workspace, cx| { + workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx); + editor.update(cx, |editor, cx| editor.focus(cx)) + }); + + // Test normalize (trimming whitespace and double colons) + cx.simulate_keystrokes("cmd-shift-p"); + + let palette = workspace.update(cx, |workspace, cx| { + workspace + .active_modal::(cx) + .unwrap() + .read(cx) + .picker + .clone() + }); + + cx.simulate_input("Editor:: Backspace"); + palette.update(cx, |palette, _| { + assert_eq!(palette.delegate.matches[0].string, "editor: backspace"); + }); + } #[gpui::test] async fn test_go_to_line(cx: &mut TestAppContext) {