Keep selection in `SwitchToHelixNormalMode` (#41583)

Andrew Farkas created

Closes #41125

Release Notes:

- Fixed `SwitchToHelixNormalMode` to keep selection
- Added default keybinds for `SwitchToHelixNormalMode` when in Helix
mode

Change summary

assets/keymaps/vim.json                   |  6 ++
crates/agent_ui/src/text_thread_editor.rs |  3 
crates/debugger_tools/src/dap_log.rs      |  6 +
crates/editor/src/editor.rs               | 41 ++++++++++-----
crates/editor/src/items.rs                |  3 
crates/language_tools/src/lsp_log_view.rs |  6 +
crates/search/src/buffer_search.rs        | 21 +++++--
crates/search/src/project_search.rs       | 10 ++-
crates/terminal_view/src/terminal_view.rs |  1 
crates/vim/src/helix.rs                   | 64 +++++++++++++++++++++++-
crates/vim/src/motion.rs                  | 45 ++++++++++-------
crates/vim/src/normal/search.rs           | 19 ++++--
crates/vim/src/state.rs                   |  8 ++
crates/vim/src/vim.rs                     |  4 -
crates/vim/src/visual.rs                  |  8 --
crates/workspace/src/searchable.rs        |  5 +
16 files changed, 179 insertions(+), 71 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -421,6 +421,12 @@
       "ctrl-[": "editor::Cancel"
     }
   },
+  {
+    "context": "vim_mode == helix_select && !menu",
+    "bindings": {
+      "escape": "vim::SwitchToHelixNormalMode"
+    }
+  },
   {
     "context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
     "bindings": {

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -2591,11 +2591,12 @@ impl SearchableItem for TextThreadEditor {
         &mut self,
         index: usize,
         matches: &[Self::Match],
+        collapse: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
         self.editor.update(cx, |editor, cx| {
-            editor.activate_match(index, matches, window, cx);
+            editor.activate_match(index, matches, collapse, window, cx);
         });
     }
 

crates/debugger_tools/src/dap_log.rs 🔗

@@ -1029,11 +1029,13 @@ impl SearchableItem for DapLogView {
         &mut self,
         index: usize,
         matches: &[Self::Match],
+        collapse: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        self.editor
-            .update(cx, |e, cx| e.activate_match(index, matches, window, cx))
+        self.editor.update(cx, |e, cx| {
+            e.activate_match(index, matches, collapse, window, cx)
+        })
     }
 
     fn select_matches(

crates/editor/src/editor.rs 🔗

@@ -1069,7 +1069,6 @@ pub struct Editor {
     searchable: bool,
     cursor_shape: CursorShape,
     current_line_highlight: Option<CurrentLineHighlight>,
-    collapse_matches: bool,
     autoindent_mode: Option<AutoindentMode>,
     workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
     input_enabled: bool,
@@ -2119,7 +2118,7 @@ impl Editor {
                 .unwrap_or_default(),
             current_line_highlight: None,
             autoindent_mode: Some(AutoindentMode::EachLine),
-            collapse_matches: false,
+
             workspace: None,
             input_enabled: !is_minimap,
             use_modal_editing: full_mode,
@@ -2272,7 +2271,7 @@ impl Editor {
                     }
                 }
                 EditorEvent::Edited { .. } => {
-                    if !vim_enabled(cx) {
+                    if vim_flavor(cx).is_none() {
                         let display_map = editor.display_snapshot(cx);
                         let selections = editor.selections.all_adjusted_display(&display_map);
                         let pop_state = editor
@@ -2881,12 +2880,12 @@ impl Editor {
         self.current_line_highlight = current_line_highlight;
     }
 
-    pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
-        self.collapse_matches = collapse_matches;
-    }
-
-    pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
-        if self.collapse_matches {
+    pub fn range_for_match<T: std::marker::Copy>(
+        &self,
+        range: &Range<T>,
+        collapse: bool,
+    ) -> Range<T> {
+        if collapse {
             return range.start..range.start;
         }
         range.clone()
@@ -16654,7 +16653,7 @@ impl Editor {
 
                 editor.update_in(cx, |editor, window, cx| {
                     let range = target_range.to_point(target_buffer.read(cx));
-                    let range = editor.range_for_match(&range);
+                    let range = editor.range_for_match(&range, false);
                     let range = collapse_multiline_range(range);
 
                     if !split
@@ -21457,7 +21456,7 @@ impl Editor {
             .and_then(|e| e.to_str())
             .map(|a| a.to_string()));
 
-        let vim_mode = vim_enabled(cx);
+        let vim_mode = vim_flavor(cx).is_some();
 
         let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
         let copilot_enabled = edit_predictions_provider
@@ -22088,10 +22087,26 @@ fn edit_for_markdown_paste<'a>(
     (range, new_text)
 }
 
-fn vim_enabled(cx: &App) -> bool {
-    vim_mode_setting::VimModeSetting::try_get(cx)
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum VimFlavor {
+    Vim,
+    Helix,
+}
+
+pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
+    if vim_mode_setting::HelixModeSetting::try_get(cx)
+        .map(|helix_mode| helix_mode.0)
+        .unwrap_or(false)
+    {
+        Some(VimFlavor::Helix)
+    } else if vim_mode_setting::VimModeSetting::try_get(cx)
         .map(|vim_mode| vim_mode.0)
         .unwrap_or(false)
+    {
+        Some(VimFlavor::Vim)
+    } else {
+        None // neither vim nor helix mode
+    }
 }
 
 fn process_completion_for_edit(

crates/editor/src/items.rs 🔗

@@ -1587,11 +1587,12 @@ impl SearchableItem for Editor {
         &mut self,
         index: usize,
         matches: &[Range<Anchor>],
+        collapse: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
         self.unfold_ranges(&[matches[index].clone()], false, true, cx);
-        let range = self.range_for_match(&matches[index]);
+        let range = self.range_for_match(&matches[index], collapse);
         self.change_selections(Default::default(), window, cx, |s| {
             s.select_ranges([range]);
         })

crates/language_tools/src/lsp_log_view.rs 🔗

@@ -812,11 +812,13 @@ impl SearchableItem for LspLogView {
         &mut self,
         index: usize,
         matches: &[Self::Match],
+        collapse: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        self.editor
-            .update(cx, |e, cx| e.activate_match(index, matches, window, cx))
+        self.editor.update(cx, |e, cx| {
+            e.activate_match(index, matches, collapse, window, cx)
+        })
     }
 
     fn select_matches(

crates/search/src/buffer_search.rs 🔗

@@ -10,8 +10,9 @@ use any_vec::AnyVec;
 use anyhow::Context as _;
 use collections::HashMap;
 use editor::{
-    DisplayPoint, Editor, EditorSettings,
+    DisplayPoint, Editor, EditorSettings, VimFlavor,
     actions::{Backtab, Tab},
+    vim_flavor,
 };
 use futures::channel::oneshot;
 use gpui::{
@@ -825,7 +826,8 @@ impl BufferSearchBar {
                 .searchable_items_with_matches
                 .get(&active_searchable_item.downgrade())
         {
-            active_searchable_item.activate_match(match_ix, matches, window, cx)
+            let collapse = editor::vim_flavor(cx) == Some(VimFlavor::Vim);
+            active_searchable_item.activate_match(match_ix, matches, collapse, window, cx)
         }
     }
 
@@ -970,7 +972,8 @@ impl BufferSearchBar {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        self.select_match(Direction::Next, 1, window, cx);
+        let collapse = vim_flavor(cx) == Some(VimFlavor::Vim);
+        self.select_match(Direction::Next, 1, collapse, window, cx);
     }
 
     fn select_prev_match(
@@ -979,7 +982,8 @@ impl BufferSearchBar {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        self.select_match(Direction::Prev, 1, window, cx);
+        let collapse = vim_flavor(cx) == Some(VimFlavor::Vim);
+        self.select_match(Direction::Prev, 1, collapse, window, cx);
     }
 
     pub fn select_all_matches(
@@ -1004,6 +1008,7 @@ impl BufferSearchBar {
         &mut self,
         direction: Direction,
         count: usize,
+        collapse: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -1026,7 +1031,7 @@ impl BufferSearchBar {
                 .match_index_for_direction(matches, index, direction, count, window, cx);
 
             searchable_item.update_matches(matches, window, cx);
-            searchable_item.activate_match(new_match_index, matches, window, cx);
+            searchable_item.activate_match(new_match_index, matches, collapse, window, cx);
         }
     }
 
@@ -1040,7 +1045,8 @@ impl BufferSearchBar {
                 return;
             }
             searchable_item.update_matches(matches, window, cx);
-            searchable_item.activate_match(0, matches, window, cx);
+            let collapse = vim_flavor(cx) == Some(VimFlavor::Vim);
+            searchable_item.activate_match(0, matches, collapse, window, cx);
         }
     }
 
@@ -1055,7 +1061,8 @@ impl BufferSearchBar {
             }
             let new_match_index = matches.len() - 1;
             searchable_item.update_matches(matches, window, cx);
-            searchable_item.activate_match(new_match_index, matches, window, cx);
+            let collapse = vim_flavor(cx) == Some(VimFlavor::Vim);
+            searchable_item.activate_match(new_match_index, matches, collapse, window, cx);
         }
     }
 

crates/search/src/project_search.rs 🔗

@@ -9,10 +9,10 @@ use anyhow::Context as _;
 use collections::HashMap;
 use editor::{
     Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, PathKey,
-    SelectionEffects,
+    SelectionEffects, VimFlavor,
     actions::{Backtab, SelectAll, Tab},
     items::active_match_index,
-    multibuffer_context_lines,
+    multibuffer_context_lines, vim_flavor,
 };
 use futures::{StreamExt, stream::FuturesOrdered};
 use gpui::{
@@ -1344,7 +1344,8 @@ impl ProjectSearchView {
 
             let range_to_select = match_ranges[new_index].clone();
             self.results_editor.update(cx, |editor, cx| {
-                let range_to_select = editor.range_for_match(&range_to_select);
+                let collapse = vim_flavor(cx) == Some(VimFlavor::Vim);
+                let range_to_select = editor.range_for_match(&range_to_select, collapse);
                 editor.unfold_ranges(std::slice::from_ref(&range_to_select), false, true, cx);
                 editor.change_selections(Default::default(), window, cx, |s| {
                     s.select_ranges([range_to_select])
@@ -1415,9 +1416,10 @@ impl ProjectSearchView {
             let is_new_search = self.search_id != prev_search_id;
             self.results_editor.update(cx, |editor, cx| {
                 if is_new_search {
+                    let collapse = vim_flavor(cx) == Some(VimFlavor::Vim);
                     let range_to_select = match_ranges
                         .first()
-                        .map(|range| editor.range_for_match(range));
+                        .map(|range| editor.range_for_match(range, collapse));
                     editor.change_selections(Default::default(), window, cx, |s| {
                         s.select_ranges(range_to_select)
                     });

crates/vim/src/helix.rs 🔗

@@ -450,7 +450,7 @@ impl Vim {
                         prior_selections,
                         prior_operator: self.operator_stack.last().cloned(),
                         prior_mode: self.mode,
-                        helix_select: true,
+                        is_helix_regex_search: true,
                     }
                 });
             }
@@ -1278,6 +1278,24 @@ mod test {
         cx.assert_state("«one ˇ»two", Mode::HelixSelect);
     }
 
+    #[gpui::test]
+    async fn test_exit_visual_mode(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new(cx, true).await;
+
+        cx.set_state("ˇone two", Mode::Normal);
+        cx.simulate_keystrokes("v w");
+        cx.assert_state("«one tˇ»wo", Mode::Visual);
+        cx.simulate_keystrokes("escape");
+        cx.assert_state("one ˇtwo", Mode::Normal);
+
+        cx.enable_helix();
+        cx.set_state("ˇone two", Mode::HelixNormal);
+        cx.simulate_keystrokes("v w");
+        cx.assert_state("«one ˇ»two", Mode::HelixSelect);
+        cx.simulate_keystrokes("escape");
+        cx.assert_state("«one ˇ»two", Mode::HelixNormal);
+    }
+
     #[gpui::test]
     async fn test_helix_select_regex(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
@@ -1297,9 +1315,47 @@ mod test {
         cx.simulate_keystrokes("enter");
         cx.assert_state("«oneˇ» two «oneˇ»", Mode::HelixNormal);
 
-        cx.set_state("ˇone two one", Mode::HelixNormal);
-        cx.simulate_keystrokes("s o n e enter");
-        cx.assert_state("ˇone two one", Mode::HelixNormal);
+        // TODO: change "search_in_selection" to not perform any search when in helix select mode with no selection
+        // cx.set_state("ˇstuff one two one", Mode::HelixNormal);
+        // cx.simulate_keystrokes("s o n e enter");
+        // cx.assert_state("ˇstuff one two one", Mode::HelixNormal);
+    }
+
+    #[gpui::test]
+    async fn test_helix_select_next_match(cx: &mut gpui::TestAppContext) {
+        let mut cx = VimTestContext::new(cx, true).await;
+
+        cx.set_state("ˇhello two one two one two one", Mode::Visual);
+        cx.simulate_keystrokes("/ o n e");
+        cx.simulate_keystrokes("enter");
+        cx.simulate_keystrokes("n n");
+        cx.assert_state("«hello two one two one two oˇ»ne", Mode::Visual);
+
+        cx.set_state("ˇhello two one two one two one", Mode::Normal);
+        cx.simulate_keystrokes("/ o n e");
+        cx.simulate_keystrokes("enter");
+        cx.simulate_keystrokes("n n");
+        cx.assert_state("hello two one two one two ˇone", Mode::Normal);
+
+        cx.set_state("ˇhello two one two one two one", Mode::Normal);
+        cx.simulate_keystrokes("/ o n e");
+        cx.simulate_keystrokes("enter");
+        cx.simulate_keystrokes("n g n g n");
+        cx.assert_state("hello two one two «one two oneˇ»", Mode::Visual);
+
+        cx.enable_helix();
+
+        cx.set_state("ˇhello two one two one two one", Mode::HelixNormal);
+        cx.simulate_keystrokes("/ o n e");
+        cx.simulate_keystrokes("enter");
+        cx.simulate_keystrokes("n n");
+        cx.assert_state("hello two one two one two «oneˇ»", Mode::HelixNormal);
+
+        cx.set_state("ˇhello two one two one two one", Mode::HelixSelect);
+        cx.simulate_keystrokes("/ o n e");
+        cx.simulate_keystrokes("enter");
+        cx.simulate_keystrokes("n n");
+        cx.assert_state("ˇhello two «oneˇ» two «oneˇ» two «oneˇ»", Mode::HelixSelect);
     }
 
     #[gpui::test]

crates/vim/src/motion.rs 🔗

@@ -672,31 +672,40 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
 
 impl Vim {
     pub(crate) fn search_motion(&mut self, m: Motion, window: &mut Window, cx: &mut Context<Self>) {
-        if let Motion::ZedSearchResult {
-            prior_selections, ..
+        let Motion::ZedSearchResult {
+            prior_selections,
+            new_selections,
         } = &m
-        {
-            match self.mode {
-                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
-                    if !prior_selections.is_empty() {
-                        self.update_editor(cx, |_, editor, cx| {
-                            editor.change_selections(Default::default(), window, cx, |s| {
-                                s.select_ranges(prior_selections.iter().cloned())
-                            })
+        else {
+            return;
+        };
+
+        match self.mode {
+            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
+                if !prior_selections.is_empty() {
+                    self.update_editor(cx, |_, editor, cx| {
+                        editor.change_selections(Default::default(), window, cx, |s| {
+                            s.select_ranges(prior_selections.iter().cloned());
                         });
-                    }
+                    });
                 }
-                Mode::Normal | Mode::Replace | Mode::Insert => {
-                    if self.active_operator().is_none() {
-                        return;
-                    }
+                self.motion(m, window, cx);
+            }
+            Mode::Normal | Mode::Replace | Mode::Insert => {
+                if self.active_operator().is_some() {
+                    self.motion(m, window, cx);
                 }
+            }
 
-                Mode::HelixNormal | Mode::HelixSelect => {}
+            Mode::HelixNormal => {}
+            Mode::HelixSelect => {
+                self.update_editor(cx, |_, editor, cx| {
+                    editor.change_selections(Default::default(), window, cx, |s| {
+                        s.select_ranges(prior_selections.iter().chain(new_selections).cloned());
+                    });
+                });
             }
         }
-
-        self.motion(m, window, cx)
     }
 
     pub(crate) fn motion(&mut self, motion: Motion, window: &mut Window, cx: &mut Context<Self>) {

crates/vim/src/normal/search.rs 🔗

@@ -1,5 +1,6 @@
-use editor::{Editor, EditorSettings};
+use editor::{Editor, EditorSettings, VimFlavor};
 use gpui::{Action, Context, Window, actions};
+
 use language::Point;
 use schemars::JsonSchema;
 use search::{BufferSearchBar, SearchOptions, buffer_search};
@@ -195,7 +196,7 @@ impl Vim {
                         prior_selections,
                         prior_operator: self.operator_stack.last().cloned(),
                         prior_mode,
-                        helix_select: false,
+                        is_helix_regex_search: false,
                     }
                 });
             }
@@ -219,7 +220,7 @@ impl Vim {
         let new_selections = self.editor_selections(window, cx);
         let result = pane.update(cx, |pane, cx| {
             let search_bar = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()?;
-            if self.search.helix_select {
+            if self.search.is_helix_regex_search {
                 search_bar.update(cx, |search_bar, cx| {
                     search_bar.select_all_matches(&Default::default(), window, cx)
                 });
@@ -240,7 +241,8 @@ impl Vim {
                     count = count.saturating_sub(1)
                 }
                 self.search.count = 1;
-                search_bar.select_match(direction, count, window, cx);
+                let collapse = !self.mode.is_helix();
+                search_bar.select_match(direction, count, collapse, window, cx);
                 search_bar.focus_editor(&Default::default(), window, cx);
 
                 let prior_selections: Vec<_> = self.search.prior_selections.drain(..).collect();
@@ -307,7 +309,8 @@ impl Vim {
                 if !search_bar.has_active_match() || !search_bar.show(window, cx) {
                     return false;
                 }
-                search_bar.select_match(direction, count, window, cx);
+                let collapse = !self.mode.is_helix();
+                search_bar.select_match(direction, count, collapse, window, cx);
                 true
             })
         });
@@ -316,6 +319,7 @@ impl Vim {
         }
 
         let new_selections = self.editor_selections(window, cx);
+
         self.search_motion(
             Motion::ZedSearchResult {
                 prior_selections,
@@ -381,7 +385,8 @@ impl Vim {
             cx.spawn_in(window, async move |_, cx| {
                 search.await?;
                 search_bar.update_in(cx, |search_bar, window, cx| {
-                    search_bar.select_match(direction, count, window, cx);
+                    let collapse = editor::vim_flavor(cx) == Some(VimFlavor::Vim);
+                    search_bar.select_match(direction, count, collapse, window, cx);
 
                     vim.update(cx, |vim, cx| {
                         let new_selections = vim.editor_selections(window, cx);
@@ -444,7 +449,7 @@ impl Vim {
                 cx.spawn_in(window, async move |_, cx| {
                     search.await?;
                     search_bar.update_in(cx, |search_bar, window, cx| {
-                        search_bar.select_match(direction, 1, window, cx)
+                        search_bar.select_match(direction, 1, true, window, cx)
                     })?;
                     anyhow::Ok(())
                 })

crates/vim/src/state.rs 🔗

@@ -66,12 +66,16 @@ impl Display for Mode {
 }
 
 impl Mode {
-    pub fn is_visual(&self) -> bool {
+    pub fn is_visual(self) -> bool {
         match self {
             Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true,
             Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
         }
     }
+
+    pub fn is_helix(self) -> bool {
+        matches!(self, Mode::HelixNormal | Mode::HelixSelect)
+    }
 }
 
 impl Default for Mode {
@@ -990,7 +994,7 @@ pub struct SearchState {
     pub prior_selections: Vec<Range<Anchor>>,
     pub prior_operator: Option<Operator>,
     pub prior_mode: Mode,
-    pub helix_select: bool,
+    pub is_helix_regex_search: bool,
 }
 
 impl Operator {

crates/vim/src/vim.rs 🔗

@@ -669,7 +669,7 @@ impl Vim {
                 editor,
                 cx,
                 |vim, _: &SwitchToHelixNormalMode, window, cx| {
-                    vim.switch_mode(Mode::HelixNormal, false, window, cx)
+                    vim.switch_mode(Mode::HelixNormal, true, window, cx)
                 },
             );
             Vim::action(editor, cx, |_, _: &PushForcedMotion, _, cx| {
@@ -953,7 +953,6 @@ impl Vim {
     fn deactivate(editor: &mut Editor, cx: &mut Context<Editor>) {
         editor.set_cursor_shape(CursorShape::Bar, cx);
         editor.set_clip_at_line_ends(false, cx);
-        editor.set_collapse_matches(false);
         editor.set_input_enabled(true);
         editor.set_autoindent(true);
         editor.selections.set_line_mode(false);
@@ -1929,7 +1928,6 @@ impl Vim {
         self.update_editor(cx, |vim, editor, cx| {
             editor.set_cursor_shape(vim.cursor_shape(cx), cx);
             editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
-            editor.set_collapse_matches(true);
             editor.set_input_enabled(vim.editor_input_enabled());
             editor.set_autoindent(vim.should_autoindent());
             editor

crates/vim/src/visual.rs 🔗

@@ -847,9 +847,6 @@ impl Vim {
         let mut start_selection = 0usize;
         let mut end_selection = 0usize;
 
-        self.update_editor(cx, |_, editor, _| {
-            editor.set_collapse_matches(false);
-        });
         if vim_is_normal {
             pane.update(cx, |pane, cx| {
                 if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()
@@ -860,7 +857,7 @@ impl Vim {
                         }
                         // without update_match_index there is a bug when the cursor is before the first match
                         search_bar.update_match_index(window, cx);
-                        search_bar.select_match(direction.opposite(), 1, window, cx);
+                        search_bar.select_match(direction.opposite(), 1, false, window, cx);
                     });
                 }
             });
@@ -878,7 +875,7 @@ impl Vim {
             if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
                 search_bar.update(cx, |search_bar, cx| {
                     search_bar.update_match_index(window, cx);
-                    search_bar.select_match(direction, count, window, cx);
+                    search_bar.select_match(direction, count, false, window, cx);
                     match_exists = search_bar.match_exists(window, cx);
                 });
             }
@@ -905,7 +902,6 @@ impl Vim {
             editor.change_selections(Default::default(), window, cx, |s| {
                 s.select_ranges([start_selection..end_selection]);
             });
-            editor.set_collapse_matches(true);
         });
 
         match self.maybe_pop_operator() {

crates/workspace/src/searchable.rs 🔗

@@ -104,6 +104,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
         &mut self,
         index: usize,
         matches: &[Self::Match],
+        collapse: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     );
@@ -184,6 +185,7 @@ pub trait SearchableItemHandle: ItemHandle {
         &self,
         index: usize,
         matches: &AnyVec<dyn Send>,
+        collapse: bool,
         window: &mut Window,
         cx: &mut App,
     );
@@ -274,12 +276,13 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
         &self,
         index: usize,
         matches: &AnyVec<dyn Send>,
+        collapse: bool,
         window: &mut Window,
         cx: &mut App,
     ) {
         let matches = matches.downcast_ref().unwrap();
         self.update(cx, |this, cx| {
-            this.activate_match(index, matches.as_slice(), window, cx)
+            this.activate_match(index, matches.as_slice(), collapse, window, cx)
         });
     }