Fix cmd-e on macOS to behave more like it should (#54451)

Conrad Irwin created

Closes #50578

Release Notes:

- Fixed cmd-e on macOS to work when `seed_search_query_from_cursor` has
been changed

Change summary

assets/keymaps/default-macos.json                    |  2 
crates/debugger_tools/src/dap_log.rs                 |  9 ++
crates/editor/src/items.rs                           | 13 +++
crates/editor/src/split.rs                           | 12 +++-
crates/language_tools/src/lsp_log_view.rs            |  9 ++
crates/markdown_preview/src/markdown_preview_view.rs |  7 ++
crates/search/src/buffer_search.rs                   | 41 ++++++++++++-
crates/search/src/project_search.rs                  |  2 
crates/terminal_view/src/terminal_view.rs            |  7 ++
crates/vim/src/normal/search.rs                      |  6 -
crates/vim/src/vim.rs                                | 30 ---------
crates/workspace/src/searchable.rs                   | 15 +++-
crates/zed_actions/src/lib.rs                        |  4 +
13 files changed, 101 insertions(+), 56 deletions(-)

Detailed changes

assets/keymaps/default-macos.json 🔗

@@ -171,7 +171,7 @@
       "cmd-f": "buffer_search::Deploy",
       "cmd-alt-f": "buffer_search::DeployReplace",
       "cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
-      "cmd-e": ["buffer_search::Deploy", { "focus": false }],
+      "cmd-e": "buffer_search::UseSelectionForFind",
       "cmd->": "agent::AddSelectionToThread",
       "cmd-alt-e": "editor::SelectEnclosingSymbol",
       "alt-enter": "editor::OpenSelectionsInMultibuffer",

crates/debugger_tools/src/dap_log.rs 🔗

@@ -1028,9 +1028,14 @@ impl SearchableItem for DapLogView {
         })
     }
 
-    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
+    fn query_suggestion(
+        &mut self,
+        ignore_settings: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String {
         self.editor
-            .update(cx, |e, cx| e.query_suggestion(window, cx))
+            .update(cx, |e, cx| e.query_suggestion(ignore_settings, window, cx))
     }
 
     fn activate_match(

crates/editor/src/items.rs 🔗

@@ -1660,8 +1660,17 @@ impl SearchableItem for Editor {
         }
     }
 
-    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
-        let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
+    fn query_suggestion(
+        &mut self,
+        ignore_settings: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String {
+        let setting = if ignore_settings {
+            SeedQuerySetting::Always
+        } else {
+            EditorSettings::get_global(cx).seed_search_query_from_cursor
+        };
         let snapshot = self.snapshot(window, cx);
         let selection = self.selections.newest_adjusted(&snapshot.display_snapshot);
         let buffer_snapshot = snapshot.buffer_snapshot();

crates/editor/src/split.rs 🔗

@@ -1901,9 +1901,15 @@ impl SearchableItem for SplittableEditor {
         }
     }
 
-    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
-        self.focused_editor()
-            .update(cx, |editor, cx| editor.query_suggestion(window, cx))
+    fn query_suggestion(
+        &mut self,
+        ignore_settings: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String {
+        self.focused_editor().update(cx, |editor, cx| {
+            editor.query_suggestion(ignore_settings, window, cx)
+        })
     }
 
     fn activate_match(

crates/language_tools/src/lsp_log_view.rs 🔗

@@ -822,9 +822,14 @@ impl SearchableItem for LspLogView {
         })
     }
 
-    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
+    fn query_suggestion(
+        &mut self,
+        ignore_settings: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String {
         self.editor
-            .update(cx, |e, cx| e.query_suggestion(window, cx))
+            .update(cx, |e, cx| e.query_suggestion(ignore_settings, window, cx))
     }
 
     fn activate_match(

crates/markdown_preview/src/markdown_preview_view.rs 🔗

@@ -956,7 +956,12 @@ impl SearchableItem for MarkdownPreviewView {
         }
     }
 
-    fn query_suggestion(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> String {
+    fn query_suggestion(
+        &mut self,
+        _ignore_settings: bool,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String {
         self.markdown.read(cx).selected_text().unwrap_or_default()
     }
 

crates/search/src/buffer_search.rs 🔗

@@ -56,7 +56,9 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar};
 
 const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
 
-pub use zed_actions::buffer_search::{Deploy, DeployReplace, Dismiss, FocusEditor};
+pub use zed_actions::buffer_search::{
+    Deploy, DeployReplace, Dismiss, FocusEditor, UseSelectionForFind,
+};
 
 pub enum Event {
     UpdateLocation,
@@ -839,6 +841,16 @@ impl BufferSearchBar {
                 this.deploy(&Deploy::replace(), window, cx);
             }
         }));
+        registrar.register_handler(ForDeployed(
+            |this, action: &UseSelectionForFind, window, cx| {
+                this.use_selection_for_find(action, window, cx);
+            },
+        ));
+        registrar.register_handler(ForDismissed(
+            |this, action: &UseSelectionForFind, window, cx| {
+                this.use_selection_for_find(action, window, cx);
+            },
+        ));
     }
 
     pub fn new(
@@ -991,7 +1003,7 @@ impl BufferSearchBar {
                 let mut handle = self.query_editor.focus_handle(cx);
                 let mut select_query = true;
 
-                let has_seed_text = self.query_suggestion(window, cx).is_some();
+                let has_seed_text = self.query_suggestion(false, window, cx).is_some();
                 if deploy.replace_enabled && has_seed_text {
                     handle = self.replacement_editor.focus_handle(cx);
                     select_query = false;
@@ -1100,7 +1112,7 @@ impl BufferSearchBar {
     }
 
     pub fn search_suggested(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let search = self.query_suggestion(window, cx).map(|suggestion| {
+        let search = self.query_suggestion(false, window, cx).map(|suggestion| {
             self.search(&suggestion, Some(self.default_options), true, window, cx)
         });
 
@@ -1154,12 +1166,13 @@ impl BufferSearchBar {
 
     pub fn query_suggestion(
         &mut self,
+        ignore_settings: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Option<String> {
         self.active_searchable_item
             .as_ref()
-            .map(|searchable_item| searchable_item.query_suggestion(window, cx))
+            .map(|searchable_item| searchable_item.query_suggestion(ignore_settings, window, cx))
             .filter(|suggestion| !suggestion.is_empty())
     }
 
@@ -1224,6 +1237,26 @@ impl BufferSearchBar {
         ));
     }
 
+    pub fn use_selection_for_find(
+        &mut self,
+        _: &UseSelectionForFind,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let Some(search_text) = self.query_suggestion(true, window, cx) else {
+            return;
+        };
+        self.query_editor.update(cx, |query_editor, cx| {
+            query_editor.buffer().update(cx, |query_buffer, cx| {
+                let len = query_buffer.len(cx);
+                query_buffer.edit([(MultiBufferOffset(0)..len, search_text)], None, cx);
+            });
+        });
+        #[cfg(target_os = "macos")]
+        self.update_find_pasteboard(cx);
+        cx.notify();
+    }
+
     pub fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) {
         if let Some(active_editor) = self.active_searchable_item.as_ref() {
             let handle = active_editor.item_focus_handle(cx);

crates/search/src/project_search.rs 🔗

@@ -1181,7 +1181,7 @@ impl ProjectSearchView {
             }
 
             let editor = item.act_as::<Editor>(cx)?;
-            let query = editor.query_suggestion(window, cx);
+            let query = editor.query_suggestion(false, window, cx);
             if query.is_empty() { None } else { Some(query) }
         });
 

crates/terminal_view/src/terminal_view.rs 🔗

@@ -1852,7 +1852,12 @@ impl SearchableItem for TerminalView {
     }
 
     /// Returns the selection content to pre-load into this search
-    fn query_suggestion(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> String {
+    fn query_suggestion(
+        &mut self,
+        _ignore_settings: bool,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String {
         self.terminal()
             .read(cx)
             .last_content

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

@@ -434,7 +434,6 @@ impl Vim {
         let count = Vim::take_count(cx).unwrap_or(1);
         Vim::take_forced_motion(cx);
         let prior_selections = self.editor_selections(window, cx);
-        let cursor_word = self.editor_cursor_word(window, cx);
         let vim = cx.entity();
 
         let searched = pane.update(cx, |pane, cx| {
@@ -456,10 +455,7 @@ impl Vim {
                 if !search_bar.show(window, cx) {
                     return None;
                 }
-                let Some(query) = search_bar
-                    .query_suggestion(window, cx)
-                    .or_else(|| cursor_word)
-                else {
+                let Some(query) = search_bar.query_suggestion(true, window, cx) else {
                     drop(search_bar.search("", None, false, window, cx));
                     return None;
                 };

crates/vim/src/vim.rs 🔗

@@ -33,9 +33,7 @@ use gpui::{
     KeystrokeEvent, Render, Subscription, Task, WeakEntity, Window, actions,
 };
 use insert::{NormalBefore, TemporaryNormal};
-use language::{
-    CharKind, CharScopeContext, CursorShape, Point, Selection, SelectionGoal, TransactionId,
-};
+use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
 pub use mode_indicator::ModeIndicator;
 use motion::Motion;
 use multi_buffer::ToPoint as _;
@@ -1595,32 +1593,6 @@ impl Vim {
         .unwrap_or_default()
     }
 
-    fn editor_cursor_word(
-        &mut self,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Option<String> {
-        self.update_editor(cx, |_, editor, cx| {
-            let snapshot = &editor.snapshot(window, cx);
-            let selection = editor
-                .selections
-                .newest::<MultiBufferOffset>(&snapshot.display_snapshot);
-
-            let snapshot = snapshot.buffer_snapshot();
-            let (range, kind) =
-                snapshot.surrounding_word(selection.start, Some(CharScopeContext::Completion));
-            if kind == Some(CharKind::Word) {
-                let text: String = snapshot.text_for_range(range).collect();
-                if !text.trim().is_empty() {
-                    return Some(text);
-                }
-            }
-
-            None
-        })
-        .unwrap_or_default()
-    }
-
     /// When doing an action that modifies the buffer, we start recording so that `.`
     /// will replay the action.
     pub fn start_recording(&mut self, cx: &mut Context<Self>) {

crates/workspace/src/searchable.rs 🔗

@@ -116,7 +116,12 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
         window: &mut Window,
         cx: &mut Context<Self>,
     );
-    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
+    fn query_suggestion(
+        &mut self,
+        ignore_settings: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> String;
     fn activate_match(
         &mut self,
         index: usize,
@@ -221,7 +226,7 @@ pub trait SearchableItemHandle: ItemHandle {
         window: &mut Window,
         cx: &mut App,
     );
-    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
+    fn query_suggestion(&self, ignore_settings: bool, window: &mut Window, cx: &mut App) -> String;
     fn activate_match(
         &self,
         index: usize,
@@ -335,8 +340,10 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
             this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
         });
     }
-    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
-        self.update(cx, |this, cx| this.query_suggestion(window, cx))
+    fn query_suggestion(&self, ignore_settings: bool, window: &mut Window, cx: &mut App) -> String {
+        self.update(cx, |this, cx| {
+            this.query_suggestion(ignore_settings, window, cx)
+        })
     }
     fn activate_match(
         &self,

crates/zed_actions/src/lib.rs 🔗

@@ -467,7 +467,9 @@ pub mod buffer_search {
             /// Dismisses the search bar.
             Dismiss,
             /// Focuses back on the editor.
-            FocusEditor
+            FocusEditor,
+            /// Sets the search query to the current selection without opening the search bar or running a search.
+            UseSelectionForFind,
         ]
     );
 }