From 778b6fb27b125003444d5ed5768a3ed2330d41f7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 28 Feb 2024 19:23:36 -0700 Subject: [PATCH] Add OpenExcerptsSplit (#8574) I would like to keep diagnostics open on one side, and process them on the other. Release Notes: - Added `editor::OpenExcerptsSplit` (bound to `cmd-k enter`) to open the selected excerpts in the adjacent pane - vim: Added `ctrl-w d`, `ctrl-w shift-d` and `ctrl-w space` for `editor::GoTo{,Type}Definition` and `editor::OpenExcerptsSplit` --- assets/keymaps/default-linux.json | 1 + assets/keymaps/default-macos.json | 1 + assets/keymaps/vim.json | 7 ++ .../src/activity_indicator.rs | 2 +- crates/auto_update/src/auto_update.rs | 2 +- crates/collab/src/tests/following_tests.rs | 2 +- crates/command_palette/src/command_palette.rs | 2 +- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/actions.rs | 1 + crates/editor/src/editor.rs | 34 ++++--- crates/editor/src/element.rs | 1 + crates/editor/src/rust_analyzer_ext.rs | 2 +- crates/extensions_ui/src/extensions_ui.rs | 2 +- crates/language_tools/src/lsp_log.rs | 2 +- crates/project_symbols/src/project_symbols.rs | 9 +- crates/search/src/project_search.rs | 6 +- crates/terminal_view/src/terminal_view.rs | 2 +- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/workspace.rs | 94 +++++++++---------- crates/zed/src/zed.rs | 6 +- 20 files changed, 100 insertions(+), 80 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index f55e637655..4af201b9e3 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -467,6 +467,7 @@ "context": "Editor && mode == full", "bindings": { "alt-enter": "editor::OpenExcerpts", + "ctrl-k enter": "editor::OpenExcerptsSplit", "ctrl-f8": "editor::GoToHunk", "ctrl-shift-f8": "editor::GoToPrevHunk", "ctrl-enter": "assistant::InlineAssist" diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index a24a6e44ed..5d6bc1c571 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -507,6 +507,7 @@ "context": "Editor && mode == full", "bindings": { "alt-enter": "editor::OpenExcerpts", + "cmd-k enter": "editor::OpenExcerptsSplit", "cmd-f8": "editor::GoToHunk", "cmd-shift-f8": "editor::GoToPrevHunk", "ctrl-enter": "assistant::InlineAssist" diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index ae936e5bb0..ab738f9fb7 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -288,6 +288,13 @@ "ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes", "ctrl-w n": ["workspace::NewFileInDirection", "Up"], "ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"], + + "ctrl-w d": "editorGoToDefinitionSplit", + "ctrl-w g d": "editorGoToDefinitionSplit", + "ctrl-w shift-d": "editorGoToTypeDefinitionSplit", + "ctrl-w g shift-d": "editorGoToTypeDefinitionSplit", + "ctrl-w space": "editor::OpenExcerptsSplit", + "ctrl-w g space": "editor::OpenExcerptsSplit", "-": "pane::RevealInProjectPanel" } }, diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index ac63592d6b..4f070d31c6 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -97,7 +97,7 @@ impl ActivityIndicator { cx, ); }); - workspace.add_item( + workspace.add_item_to_active_pane( Box::new( cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)), ), diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 3dd10b10a9..23ca550516 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -242,7 +242,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext( ranges_to_highlight, @@ -7506,11 +7506,13 @@ impl Editor { cx.window_context().defer(move |cx| { let target_editor: View = workspace.update(cx, |workspace, cx| { - if split { - workspace.split_project_item(target.buffer.clone(), cx) + let pane = if split { + workspace.adjacent_pane(cx) } else { - workspace.open_project_item(target.buffer.clone(), cx) - } + workspace.active_pane().clone() + }; + + workspace.open_project_item(pane, target.buffer.clone(), cx) }); target_editor.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history @@ -7747,7 +7749,7 @@ impl Editor { if split { workspace.split_item(SplitDirection::Right, Box::new(editor), cx); } else { - workspace.add_item(Box::new(editor), cx); + workspace.add_item_to_active_pane(Box::new(editor), cx); } } @@ -9067,7 +9069,15 @@ impl Editor { self.searchable } + fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext) { + self.open_excerpts_common(true, cx) + } + fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { + self.open_excerpts_common(false, cx) + } + + fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext) { let buffer = self.buffer.read(cx); if buffer.is_singleton() { cx.propagate(); @@ -9094,18 +9104,20 @@ impl Editor { } } - self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx); - // We defer the pane interaction because we ourselves are a workspace item // and activating a new item causes the pane to call a method on us reentrantly, // which panics if we're on the stack. cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { - let pane = workspace.active_pane().clone(); + let pane = if split { + workspace.adjacent_pane(cx) + } else { + workspace.active_pane().clone() + }; pane.update(cx, |pane, _| pane.disable_history()); for (buffer, ranges) in new_selections_by_buffer.into_iter() { - let editor = workspace.open_project_item::(buffer, cx); + let editor = workspace.open_project_item::(pane.clone(), buffer, cx); editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::newest()), cx, |s| { s.select_ranges(ranges); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d5dac7d660..0f3ee3ec62 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -273,6 +273,7 @@ impl EditorElement { register_action(view, cx, Editor::show_completions); register_action(view, cx, Editor::toggle_code_actions); register_action(view, cx, Editor::open_excerpts); + register_action(view, cx, Editor::open_excerpts_in_split); register_action(view, cx, Editor::toggle_soft_wrap); register_action(view, cx, Editor::toggle_inlay_hints); register_action(view, cx, hover_popover::hover); diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 067d09d9ce..31622a8c5e 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -105,7 +105,7 @@ pub fn expand_macro_recursively( let buffer = cx.new_model(|cx| { MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name) }); - workspace.add_item( + workspace.add_item_to_active_pane( Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))), cx, ); diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 235cfb676d..7d3a4db7b7 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -24,7 +24,7 @@ pub fn init(cx: &mut AppContext) { cx.observe_new_views(move |workspace: &mut Workspace, _cx| { workspace.register_action(move |workspace, _: &Extensions, cx| { let extensions_page = ExtensionsPage::new(workspace, cx); - workspace.add_item(Box::new(extensions_page), cx) + workspace.add_item_to_active_pane(Box::new(extensions_page), cx) }); }) .detach(); diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index e7628ce3d1..8723183789 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -93,7 +93,7 @@ pub fn init(cx: &mut AppContext) { workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| { let project = workspace.project().read(cx); if project.is_local() { - workspace.add_item( + workspace.add_item_to_active_pane( Box::new(cx.new_view(|cx| { LspLogView::new(workspace.project().clone(), log_store.clone(), cx) })), diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 61cc7059a6..9ee052b9f7 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -127,13 +127,14 @@ impl PickerDelegate for ProjectSymbolsDelegate { let position = buffer .read(cx) .clip_point_utf16(symbol.range.start, Bias::Left); - - let editor = if secondary { - workspace.split_project_item::(buffer, cx) + let pane = if secondary { + workspace.adjacent_pane(cx) } else { - workspace.open_project_item::(buffer, cx) + workspace.active_pane().clone() }; + let editor = workspace.open_project_item::(pane, buffer, cx); + editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::center()), cx, |s| { s.select_ranges([position..position]) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 87c6a57c0a..3d00c283e6 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -999,7 +999,7 @@ impl ProjectSearchView { let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, None)); - workspace.add_item(Box::new(search.clone()), cx); + workspace.add_item_to_active_pane(Box::new(search.clone()), cx); search.update(cx, |search, cx| { search .included_files_editor @@ -1048,7 +1048,7 @@ impl ProjectSearchView { model.search(new_query, cx); model }); - workspace.add_item( + workspace.add_item_to_active_pane( Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))), cx, ); @@ -1098,7 +1098,7 @@ impl ProjectSearchView { let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings)); - workspace.add_item(Box::new(view.clone()), cx); + workspace.add_item_to_active_pane(Box::new(view.clone()), cx); view }; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 12cd605fa0..3b7ad952b7 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -132,7 +132,7 @@ impl TerminalView { cx, ) }); - workspace.add_item(Box::new(view), cx) + workspace.add_item_to_active_pane(Box::new(view), cx) } } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index aafbd4dbe7..9a97fde6c3 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -28,7 +28,7 @@ pub fn init(cx: &mut AppContext) { cx.observe_new_views(|workspace: &mut Workspace, _cx| { workspace.register_action(|workspace, _: &Welcome, cx| { let welcome_page = WelcomePage::new(workspace, cx); - workspace.add_item(Box::new(welcome_page), cx) + workspace.add_item_to_active_pane(Box::new(welcome_page), cx) }); }) .detach(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 81642952cf..e31f508abf 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1885,15 +1885,23 @@ impl Workspace { } } - pub fn add_item(&mut self, item: Box, cx: &mut WindowContext) { + pub fn add_item_to_active_pane(&mut self, item: Box, cx: &mut WindowContext) { + self.add_item(self.active_pane.clone(), item, cx) + } + + pub fn add_item( + &mut self, + pane: View, + item: Box, + cx: &mut WindowContext, + ) { if let Some(text) = item.telemetry_event_text(cx) { self.client() .telemetry() .report_app_event(format!("{}: open", text)); } - self.active_pane - .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); + pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); } pub fn split_item( @@ -1903,9 +1911,7 @@ impl Workspace { cx: &mut ViewContext, ) { let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx); - new_pane.update(cx, move |new_pane, cx| { - new_pane.add_item(item, true, true, None, cx) - }) + self.add_item(new_pane, item, cx); } pub fn open_abs_path( @@ -2047,6 +2053,7 @@ impl Workspace { pub fn open_project_item( &mut self, + pane: View, project_item: Model, cx: &mut ViewContext, ) -> View @@ -2057,7 +2064,7 @@ impl Workspace { let entry_id = project_item.read(cx).entry_id(cx); if let Some(item) = entry_id - .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + .and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx)) .and_then(|item| item.downcast()) { self.activate_item(&item, cx); @@ -2065,31 +2072,7 @@ impl Workspace { } let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); - self.add_item(Box::new(item.clone()), cx); - item - } - - pub fn split_project_item( - &mut self, - project_item: Model, - cx: &mut ViewContext, - ) -> View - where - T: ProjectItem, - { - use project::Item as _; - - let entry_id = project_item.read(cx).entry_id(cx); - if let Some(item) = entry_id - .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) - .and_then(|item| item.downcast()) - { - self.activate_item(&item, cx); - return item; - } - - let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); - self.split_item(SplitDirection::Right, Box::new(item.clone()), cx); + self.add_item(pane, Box::new(item.clone()), cx); item } @@ -2498,6 +2481,13 @@ impl Workspace { &self.active_pane } + pub fn adjacent_pane(&mut self, cx: &mut ViewContext) -> View { + self.find_pane_in_direction(SplitDirection::Right, cx) + .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx)) + .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx)) + .clone() + } + pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option> { let weak_pane = self.panes_by_item.get(&handle.item_id())?; weak_pane.upgrade() @@ -4702,7 +4692,7 @@ mod tests { item }); workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item1.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item1.clone()), cx); }); item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0))); @@ -4714,7 +4704,7 @@ mod tests { item }); workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item2.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item2.clone()), cx); }); item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); @@ -4728,7 +4718,7 @@ mod tests { item }); workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item3.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item3.clone()), cx); }); item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1))); item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3))); @@ -4771,7 +4761,9 @@ mod tests { }); // Add an item to an empty pane - workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx)); + workspace.update(cx, |workspace, cx| { + workspace.add_item_to_active_pane(Box::new(item1), cx) + }); project.update(cx, |project, cx| { assert_eq!( project.active_entry(), @@ -4783,7 +4775,9 @@ mod tests { assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1")); // Add a second item to a non-empty pane - workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); + workspace.update(cx, |workspace, cx| { + workspace.add_item_to_active_pane(Box::new(item2), cx) + }); assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1")); project.update(cx, |project, cx| { assert_eq!( @@ -4836,7 +4830,9 @@ mod tests { // When there are no dirty items, there's nothing to do. let item1 = cx.new_view(|cx| TestItem::new(cx)); - workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); + workspace.update(cx, |w, cx| { + w.add_item_to_active_pane(Box::new(item1.clone()), cx) + }); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); assert!(task.await.unwrap()); @@ -4849,8 +4845,8 @@ mod tests { .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); workspace.update(cx, |w, cx| { - w.add_item(Box::new(item2.clone()), cx); - w.add_item(Box::new(item3.clone()), cx); + w.add_item_to_active_pane(Box::new(item2.clone()), cx); + w.add_item_to_active_pane(Box::new(item3.clone()), cx); }); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); cx.executor().run_until_parked(); @@ -4894,10 +4890,10 @@ mod tests { .with_project_items(&[TestProjectItem::new_untitled(cx)]) }); let pane = workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item1.clone()), cx); - workspace.add_item(Box::new(item2.clone()), cx); - workspace.add_item(Box::new(item3.clone()), cx); - workspace.add_item(Box::new(item4.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item1.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item2.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item3.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item4.clone()), cx); workspace.active_pane().clone() }); @@ -5019,9 +5015,9 @@ mod tests { // multi-entry items: (3, 4) let left_pane = workspace.update(cx, |workspace, cx| { let left_pane = workspace.active_pane().clone(); - workspace.add_item(Box::new(item_2_3.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), cx); for item in single_entry_items { - workspace.add_item(Box::new(item), cx); + workspace.add_item_to_active_pane(Box::new(item), cx); } left_pane.update(cx, |pane, cx| { pane.activate_item(2, true, true, cx); @@ -5092,7 +5088,7 @@ mod tests { }); let item_id = item.entity_id(); workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item.clone()), cx); }); // Autosave on window change. @@ -5174,7 +5170,7 @@ mod tests { // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item.clone()), cx); }); item.update(cx, |item, cx| { item.project_items[0].update(cx, |item, _| { @@ -5212,7 +5208,7 @@ mod tests { let toolbar_notify_count = Rc::new(RefCell::new(0)); workspace.update(cx, |workspace, cx| { - workspace.add_item(Box::new(item.clone()), cx); + workspace.add_item_to_active_pane(Box::new(item.clone()), cx); let toolbar_notification_count = toolbar_notify_count.clone(); cx.observe(&toolbar, move |_, _, _| { *toolbar_notification_count.borrow_mut() += 1 diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b64452338d..5571273566 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -531,7 +531,7 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { let buffer = cx.new_model(|cx| { MultiBuffer::singleton(buffer, cx).with_title("Log".into()) }); - workspace.add_item( + workspace.add_item_to_active_pane( Box::new( cx.new_view(|cx| { Editor::for_multibuffer(buffer, Some(project), cx) @@ -747,7 +747,7 @@ fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext