History

Improve Find/Replace shortcuts (#10297)

This PR changes ways the Find/Replace functionality in the Buffer/Project Search is accessible via shortcuts. It makes those panels work the same way as in VS Code and Sublime Text. The details are described in the issue: [Make Find/Replace easier to use](https://github.com/zed-industries/zed/issues/9142) There's a difficulty with the Linux keybindings: VS Code uses on MacOS (this PR replicates it): | Action | Buffer Search | Project Search | | --- | --- | --- | | Find | `cmd-f` | `cmd-shift-f` | | Replace | `cmd-alt-f` | `cmd-shift-h` | VS Code uses on Linux (this PR replicates all but one): | Action | Buffer Search | Project Search | | --- | --- | --- | | Find | `ctrl-f` | `ctrl-shift-f` | | Replace | `ctrl-h` ❗ | `ctrl-shift-h` | The problem is that `ctrl-h` is already taken by the `editor::Backspace` action in Zed on Linux. There's two options here: 1. Change keybinding for `editor::Backspace` on Linux to something else, and use `ctrl-h` for the "replace in buffer" action. 2. Use some other keybinding on Linux in Zed. This PR introduces `ctrl-r` for this purpose, though I'm not sure it's the best choice. What do you think? fixes #9142 Release Notes: - Improved access to "Find/Replace in Buffer" and "Find/Replace in Files" via shortcuts (#9142). Optionally, include screenshots / media showcasing your addition that can be included in the release notes. - N/A

Andrew Lygin committed 2 years ago

Commit 935e0d51 parent cc367d48 files · +103 −28Browse files

Unified · Split

8 files
  1. assets/keymaps/default-linux.json +14 −5
  2. assets/keymaps/default-macos.json +13 −3
  3. crates/quick_action_bar/src/quick_action_bar.rs +2 −2
  4. crates/search/src/buffer_search.rs +32 −5
  5. crates/search/src/project_search.rs +24 −10
  6. crates/search/src/search.rs +1 −0
  7. crates/workspace/src/pane.rs +16 −2
  8. crates/zed/src/zed/app_menus.rs +1 −1
assets/keymaps/default-linux.json+14−5
@@ -28,7 +28,7 @@ · 27 unmodified lines
2828 "ctrl-0": "zed::ResetBufferFontSize",
2929 "ctrl-,": "zed::OpenSettings",
3030 "ctrl-q": "zed::Quit",
31− "ctrl-h": "zed::Hide",
31+ "alt-f9": "zed::Hide",
3232 "f11": "zed::ToggleFullScreen"
3333 }
3434 },
@@ -38,7 +38,6 @@ · 3 unmodified lines
3838 "escape": "editor::Cancel",
3939 "backspace": "editor::Backspace",
4040 "shift-backspace": "editor::Backspace",
41− "ctrl-h": "editor::Backspace",
4241 "delete": "editor::Delete",
4342 "ctrl-d": "editor::Delete",
4443 "tab": "editor::Tab",
@@ -150,10 +149,11 @@ · 105 unmodified lines
150149 "ctrl-shift-enter": "editor::NewlineBelow",
151150 "ctrl-enter": "editor::NewlineAbove",
152151 "alt-z": "editor::ToggleSoftWrap",
153− "ctrl-f": [
152+ "ctrl-f": "buffer_search::Deploy",
153+ "ctrl-h": [
154154 "buffer_search::Deploy",
155155 {
156− "focus": true
156+ "replace_enabled": true
157157 }
158158 ],
159159 // "cmd-e": [
@@ -212,7 +212,9 @@ · 52 unmodified lines
212212 "enter": "search::SelectNextMatch",
213213 "shift-enter": "search::SelectPrevMatch",
214214 "alt-enter": "search::SelectAllMatches",
215− "alt-tab": "search::CycleMode"
215+ "alt-tab": "search::CycleMode",
216+ "ctrl-f": "search::FocusSearch",
217+ "ctrl-h": "search::ToggleReplace"
216218 }
217219 },
218220 {
@@ -234,6 +236,7 @@ · 15 unmodified lines
234236 "bindings": {
235237 "escape": "project_search::ToggleFocus",
236238 "alt-tab": "search::CycleMode",
239+ "ctrl-shift-f": "search::FocusSearch",
237240 "ctrl-shift-h": "search::ToggleReplace",
238241 "alt-ctrl-g": "search::ActivateRegexMode",
239242 "alt-ctrl-x": "search::ActivateTextMode"
@@ -419,6 +422,12 @@ · 179 unmodified lines
419422 "ctrl-j": "workspace::ToggleBottomDock",
420423 "ctrl-alt-y": "workspace::CloseAllDocks",
421424 "ctrl-shift-f": "pane::DeploySearch",
425+ "ctrl-shift-h": [
426+ "pane::DeploySearch",
427+ {
428+ "replace_enabled": true
429+ }
430+ ],
422431 "ctrl-k ctrl-s": "zed::OpenKeymap",
423432 "ctrl-k ctrl-t": "theme_selector::Toggle",
424433 "ctrl-shift-t": "project_symbols::Toggle",
assets/keymaps/default-macos.json+13−3
@@ -170,10 +170,11 @@ · 169 unmodified lines
170170 "cmd-shift-enter": "editor::NewlineAbove",
171171 "cmd-enter": "editor::NewlineBelow",
172172 "alt-z": "editor::ToggleSoftWrap",
173− "cmd-f": [
173+ "cmd-f": "buffer_search::Deploy",
174+ "cmd-alt-f": [
174175 "buffer_search::Deploy",
175176 {
176− "focus": true
177+ "replace_enabled": true
177178 }
178179 ],
179180 "cmd-e": [
@@ -232,7 +233,9 @@ · 52 unmodified lines
232233 "enter": "search::SelectNextMatch",
233234 "shift-enter": "search::SelectPrevMatch",
234235 "alt-enter": "search::SelectAllMatches",
235− "alt-tab": "search::CycleMode"
236+ "alt-tab": "search::CycleMode",
237+ "cmd-f": "search::FocusSearch",
238+ "cmd-alt-f": "search::ToggleReplace"
236239 }
237240 },
238241 {
@@ -254,6 +257,7 @@ · 15 unmodified lines
254257 "bindings": {
255258 "escape": "project_search::ToggleFocus",
256259 "alt-tab": "search::CycleMode",
260+ "cmd-shift-f": "search::FocusSearch",
257261 "cmd-shift-h": "search::ToggleReplace",
258262 "alt-cmd-g": "search::ActivateRegexMode",
259263 "alt-cmd-x": "search::ActivateTextMode"
@@ -436,6 +440,12 @@ · 176 unmodified lines
436440 "cmd-j": "workspace::ToggleBottomDock",
437441 "alt-cmd-y": "workspace::CloseAllDocks",
438442 "cmd-shift-f": "pane::DeploySearch",
443+ "cmd-shift-h": [
444+ "pane::DeploySearch",
445+ {
446+ "replace_enabled": true
447+ }
448+ ],
439449 "cmd-k cmd-s": "zed::OpenKeymap",
440450 "cmd-k cmd-t": "theme_selector::Toggle",
441451 "cmd-t": "project_symbols::Toggle",
crates/quick_action_bar/src/quick_action_bar.rs+2−2
@@ -91,13 +91,13 @@ impl Render for QuickActionBar { · 90 unmodified lines
9191 "toggle buffer search",
9292 IconName::MagnifyingGlass,
9393 !self.buffer_search_bar.read(cx).is_dismissed(),
94− Box::new(buffer_search::Deploy { focus: false }),
94+ Box::new(buffer_search::Deploy::find()),
9595 "Buffer Search",
9696 {
9797 let buffer_search_bar = self.buffer_search_bar.clone();
9898 move |_, cx| {
9999 buffer_search_bar.update(cx, |search_bar, cx| {
100− search_bar.toggle(&buffer_search::Deploy { focus: true }, cx)
100+ search_bar.toggle(&buffer_search::Deploy::find(), cx)
101101 });
102102 }
103103 },
crates/search/src/buffer_search.rs+32−5
@@ -3,9 +3,9 @@ mod registrar; · 2 unmodified lines
33 use crate::{
44 mode::{next_mode, SearchMode},
55 search_bar::render_nav_button,
6− ActivateRegexMode, ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery,
7− ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,
8− ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
6+ ActivateRegexMode, ActivateTextMode, CycleMode, FocusSearch, NextHistoryQuery,
7+ PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches,
8+ SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
99 };
1010 use any_vec::AnyVec;
1111 use collections::HashMap;
@@ -44,15 +44,31 @@ const MIN_INPUT_WIDTH_REMS: f32 = 15.; · 32 unmodified lines
4444 const MAX_INPUT_WIDTH_REMS: f32 = 30.;
4545 const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
4646
47+const fn true_value() -> bool {
48+ true
49+}
50+
4751 #[derive(PartialEq, Clone, Deserialize)]
4852 pub struct Deploy {
53+ #[serde(default = "true_value")]
4954 pub focus: bool,
55+ #[serde(default)]
56+ pub replace_enabled: bool,
5057 }
5158
5259 impl_actions!(buffer_search, [Deploy]);
5360
5461 actions!(buffer_search, [Dismiss, FocusEditor]);
5562
63+impl Deploy {
64+ pub fn find() -> Self {
65+ Self {
66+ focus: true,
67+ replace_enabled: false,
68+ }
69+ }
70+}
71+
5672 pub enum Event {
5773 UpdateLocation,
5874 }
@@ -470,6 +486,9 @@ impl ToolbarItemView for BufferSearchBar { · 411 unmodified lines
470486
471487 impl BufferSearchBar {
472488 pub fn register(registrar: &mut impl SearchActionsRegistrar) {
489+ registrar.register_handler(ForDeployed(|this, _: &FocusSearch, cx| {
490+ this.query_editor.focus_handle(cx).focus(cx);
491+ }));
473492 registrar.register_handler(ForDeployed(|this, action: &ToggleCaseSensitive, cx| {
474493 if this.supported_options().case {
475494 this.toggle_case_sensitive(action, cx);
@@ -583,9 +602,17 @@ impl BufferSearchBar { · 107 unmodified lines
583602 pub fn deploy(&mut self, deploy: &Deploy, cx: &mut ViewContext<Self>) -> bool {
584603 if self.show(cx) {
585604 self.search_suggested(cx);
605+ self.replace_enabled = deploy.replace_enabled;
586606 if deploy.focus {
587− self.select_query(cx);
588− let handle = self.query_editor.focus_handle(cx);
607+ let mut handle = self.query_editor.focus_handle(cx).clone();
608+ let mut select_query = true;
609+ if deploy.replace_enabled && handle.is_focused(cx) {
610+ handle = self.replacement_editor.focus_handle(cx).clone();
611+ select_query = false;
612+ };
613+ if select_query {
614+ self.select_query(cx);
615+ }
589616 cx.focus(&handle);
590617 }
591618 return true;
crates/search/src/project_search.rs+24−10
@@ -1,7 +1,8 @@
11 use crate::{
2− mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode, NextHistoryQuery,
3− PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectNextMatch, SelectPrevMatch,
4− ToggleCaseSensitive, ToggleIncludeIgnored, ToggleReplace, ToggleWholeWord,
2+ mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode, FocusSearch,
3+ NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
4+ SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleReplace,
5+ ToggleWholeWord,
56 };
67 use anyhow::Context as _;
78 use collections::{HashMap, HashSet};
@@ -60,6 +61,9 @@ const SEARCH_CONTEXT: u32 = 2; · 52 unmodified lines
6061 pub fn init(cx: &mut AppContext) {
6162 cx.set_global(ActiveSettings::default());
6263 cx.observe_new_views(|workspace: &mut Workspace, _cx| {
64+ register_workspace_action(workspace, move |search_bar, _: &FocusSearch, cx| {
65+ search_bar.focus_search(cx);
66+ });
6367 register_workspace_action(workspace, move |search_bar, _: &ToggleFilters, cx| {
6468 search_bar.toggle_filters(cx);
6569 });
@@ -797,7 +801,7 @@ impl ProjectSearchView { · 731 unmodified lines
797801 // If no search exists in the workspace, create a new one.
798802 fn deploy_search(
799803 workspace: &mut Workspace,
800− _: &workspace::DeploySearch,
804+ action: &workspace::DeploySearch,
801805 cx: &mut ViewContext<Workspace>,
802806 ) {
803807 let existing = workspace
@@ -806,7 +810,7 @@ impl ProjectSearchView { · 2 unmodified lines
806810 .items()
807811 .find_map(|item| item.downcast::<ProjectSearchView>());
808812
809− Self::existing_or_new_search(workspace, existing, cx)
813+ Self::existing_or_new_search(workspace, existing, action, cx);
810814 }
811815
812816 fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
@@ -846,12 +850,13 @@ impl ProjectSearchView { · 33 unmodified lines
846850 _: &workspace::NewSearch,
847851 cx: &mut ViewContext<Workspace>,
848852 ) {
849− Self::existing_or_new_search(workspace, None, cx)
853+ Self::existing_or_new_search(workspace, None, &DeploySearch::find(), cx)
850854 }
851855
852856 fn existing_or_new_search(
853857 workspace: &mut Workspace,
854858 existing: Option<View<ProjectSearchView>>,
859+ action: &workspace::DeploySearch,
855860 cx: &mut ViewContext<Workspace>,
856861 ) {
857862 let query = workspace.active_item(cx).and_then(|item| {
@@ -887,6 +892,7 @@ impl ProjectSearchView { · 29 unmodified lines
887892 };
888893
889894 search.update(cx, |search, cx| {
895+ search.replace_enabled = action.replace_enabled;
890896 if let Some(query) = query {
891897 search.set_query(&query, cx);
892898 }
@@ -1172,6 +1178,14 @@ impl ProjectSearchBar { · 279 unmodified lines
11721178 self.cycle_field(Direction::Prev, cx);
11731179 }
11741180
1181+ fn focus_search(&mut self, cx: &mut ViewContext<Self>) {
1182+ if let Some(search_view) = self.active_project_search.as_ref() {
1183+ search_view.update(cx, |search_view, cx| {
1184+ search_view.query_editor.focus_handle(cx).focus(cx);
1185+ });
1186+ }
1187+ }
1188+
11751189 fn cycle_field(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
11761190 let active_project_search = match &self.active_project_search {
11771191 Some(active_project_search) => active_project_search,
@@ -2011,7 +2025,7 @@ pub mod tests { · 833 unmodified lines
20112025 .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
20122026 });
20132027
2014− ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
2028+ ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch::find(), cx)
20152029 })
20162030 .unwrap();
20172031
@@ -2160,7 +2174,7 @@ pub mod tests { · 142 unmodified lines
21602174
21612175 workspace
21622176 .update(cx, |workspace, cx| {
2163− ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
2177+ ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch::find(), cx)
21642178 })
21652179 .unwrap();
21662180 window.update(cx, |_, cx| {
@@ -3259,7 +3273,7 @@ pub mod tests { · 1092 unmodified lines
32593273 .unwrap();
32603274
32613275 // Deploy a new search
3262− cx.dispatch_action(window.into(), DeploySearch);
3276+ cx.dispatch_action(window.into(), DeploySearch::find());
32633277
32643278 // Both panes should now have a project search in them
32653279 window
@@ -3284,7 +3298,7 @@ pub mod tests { · 18 unmodified lines
32843298 .unwrap();
32853299
32863300 // Deploy a new search
3287− cx.dispatch_action(window.into(), DeploySearch);
3301+ cx.dispatch_action(window.into(), DeploySearch::find());
32883302
32893303 // The project search view should now be focused in the second pane
32903304 // And the number of items should be unchanged.
crates/search/src/search.rs+1−0
@@ -22,6 +22,7 @@ actions!( · 21 unmodified lines
2222 search,
2323 [
2424 CycleMode,
25+ FocusSearch,
2526 ToggleWholeWord,
2627 ToggleCaseSensitive,
2728 ToggleIncludeIgnored,
crates/workspace/src/pane.rs+16−2
@@ -86,6 +86,12 @@ pub struct RevealInProjectPanel { · 85 unmodified lines
8686 pub entry_id: Option<u64>,
8787 }
8888
89+#[derive(PartialEq, Clone, Deserialize)]
90+pub struct DeploySearch {
91+ #[serde(default)]
92+ pub replace_enabled: bool,
93+}
94+
8995 impl_actions!(
9096 pane,
9197 [
@@ -93,7 +99,8 @@ impl_actions!( · 1 unmodified line
9399 CloseActiveItem,
94100 CloseInactiveItems,
95101 ActivateItem,
96− RevealInProjectPanel
102+ RevealInProjectPanel,
103+ DeploySearch,
97104 ]
98105 );
99106
@@ -107,7 +114,6 @@ actions!( · 7 unmodified lines
107114 CloseItemsToTheLeft,
108115 CloseItemsToTheRight,
109116 GoBack,
110− DeploySearch,
111117 GoForward,
112118 ReopenClosedItem,
113119 SplitLeft,
@@ -117,6 +123,14 @@ actions!( · 3 unmodified lines
117123 ]
118124 );
119125
126+impl DeploySearch {
127+ pub fn find() -> Self {
128+ Self {
129+ replace_enabled: false,
130+ }
131+ }
132+}
133+
120134 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
121135
122136 pub enum Event {
crates/zed/src/zed/app_menus.rs+1−1
@@ -67,7 +67,7 @@ pub fn app_menus() -> Vec<Menu<'static>> { · 66 unmodified lines
6767 MenuItem::os_action("Copy", editor::actions::Copy, OsAction::Copy),
6868 MenuItem::os_action("Paste", editor::actions::Paste, OsAction::Paste),
6969 MenuItem::separator(),
70− MenuItem::action("Find", search::buffer_search::Deploy { focus: true }),
70+ MenuItem::action("Find", search::buffer_search::Deploy::find()),
7171 MenuItem::action("Find In Project", workspace::NewSearch),
7272 MenuItem::separator(),
7373 MenuItem::action(

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t