From c7669317ec6c345ef5e6cbd93636d2fcb1fa4650 Mon Sep 17 00:00:00 2001 From: Alex Viscreanu Date: Fri, 14 Jul 2023 21:41:23 +0200 Subject: [PATCH] feat(workspace): allow alternative actions to open files and symbols in split Co-authored-by: Mikayla Maki --- assets/keymaps/atom.json | 1 + assets/keymaps/default.json | 3 + assets/keymaps/jetbrains.json | 3 +- assets/keymaps/sublime_text.json | 1 + assets/keymaps/textmate.json | 1 + crates/collab/src/tests/integration_tests.rs | 6 +- crates/collab_ui/src/contact_finder.rs | 2 +- crates/command_palette/src/command_palette.rs | 2 +- crates/editor/src/editor.rs | 49 ++++++++-- crates/editor/src/element.rs | 6 +- crates/editor/src/link_go_to_definition.rs | 15 +-- crates/file_finder/src/file_finder.rs | 90 ++++++++++------- .../src/language_selector.rs | 2 +- crates/menu/src/menu.rs | 1 + crates/outline/src/outline.rs | 2 +- crates/picker/src/picker.rs | 20 ++-- crates/project_panel/src/project_panel.rs | 28 +++++- crates/project_symbols/src/project_symbols.rs | 9 +- crates/recent_projects/src/recent_projects.rs | 2 +- crates/theme_selector/src/theme_selector.rs | 2 +- crates/vcs_menu/src/lib.rs | 2 +- crates/vector_store/src/modal.rs | 2 +- crates/welcome/src/base_keymap_picker.rs | 2 +- crates/workspace/src/workspace.rs | 98 ++++++++++++++++++- crates/zed/src/zed.rs | 8 +- 25 files changed, 277 insertions(+), 80 deletions(-) diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index af845ae4f2..c2beb71b56 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -9,6 +9,7 @@ "context": "Editor", "bindings": { "cmd-b": "editor::GoToDefinition", + "alt-cmd-b": "editor::GoToDefinitionSplit", "cmd-<": "editor::ScrollCursorCenter", "cmd-g": [ "editor::SelectNext", diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index c044f4b600..1a13d8cdb3 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -13,6 +13,7 @@ "cmd-up": "menu::SelectFirst", "cmd-down": "menu::SelectLast", "enter": "menu::Confirm", + "cmd-enter": "menu::SecondaryConfirm", "escape": "menu::Cancel", "ctrl-c": "menu::Cancel", "cmd-{": "pane::ActivatePrevItem", @@ -298,7 +299,9 @@ "shift-f8": "editor::GoToPrevDiagnostic", "f2": "editor::Rename", "f12": "editor::GoToDefinition", + "alt-f12": "editor::GoToDefinitionSplit", "cmd-f12": "editor::GoToTypeDefinition", + "alt-cmd-f12": "editor::GoToTypeDefinitionSplit", "alt-shift-f12": "editor::FindAllReferences", "ctrl-m": "editor::MoveToEnclosingBracket", "alt-cmd-[": "editor::Fold", diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index b3e8f989a4..ab093a8deb 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -46,8 +46,9 @@ "alt-f7": "editor::FindAllReferences", "cmd-alt-f7": "editor::FindAllReferences", "cmd-b": "editor::GoToDefinition", - "cmd-alt-b": "editor::GoToDefinition", + "cmd-alt-b": "editor::GoToDefinitionSplit", "cmd-shift-b": "editor::GoToTypeDefinition", + "cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit", "alt-enter": "editor::ToggleCodeActions", "f2": "editor::GoToDiagnostic", "cmd-f2": "editor::GoToPrevDiagnostic", diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index ca20802295..a70a61af55 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -20,6 +20,7 @@ "cmd-shift-a": "editor::SelectLargerSyntaxNode", "shift-f12": "editor::FindAllReferences", "alt-cmd-down": "editor::GoToDefinition", + "ctrl-alt-cmd-down": "editor::GoToDefinitionSplit", "alt-shift-cmd-down": "editor::FindAllReferences", "ctrl-.": "editor::GoToHunk", "ctrl-,": "editor::GoToPrevHunk", diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 1f28c05158..90eb090211 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -12,6 +12,7 @@ "cmd-l": "go_to_line::Toggle", "ctrl-shift-d": "editor::DuplicateLine", "cmd-b": "editor::GoToDefinition", + "alt-cmd-b": "editor::GoToDefinition", "cmd-j": "editor::ScrollCursorCenter", "cmd-shift-l": "editor::SelectLine", "cmd-shift-t": "outline::Toggle", diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index c32129818f..ab94f16a07 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7217,7 +7217,7 @@ async fn test_peers_following_each_other( // Clients A and B follow each other in split panes workspace_a.update(cx_a, |workspace, cx| { - workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); + workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx); }); workspace_a .update(cx_a, |workspace, cx| { @@ -7228,7 +7228,7 @@ async fn test_peers_following_each_other( .await .unwrap(); workspace_b.update(cx_b, |workspace, cx| { - workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); + workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx); }); workspace_b .update(cx_b, |workspace, cx| { @@ -7455,7 +7455,7 @@ async fn test_auto_unfollowing( // When client B activates a different pane, it continues following client A in the original pane. workspace_b.update(cx_b, |workspace, cx| { - workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx) + workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx) }); assert_eq!( workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)), diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index af59817ece..3264a144ed 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -67,7 +67,7 @@ impl PickerDelegate for ContactFinderDelegate { }) } - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if let Some(user) = self.potential_contacts.get(self.selected_index) { let user_store = self.user_store.read(cx); match user_store.contact_request_status(user) { diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 77dde09875..7461fb28c7 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -160,7 +160,7 @@ impl PickerDelegate for CommandPaletteDelegate { fn dismissed(&mut self, _cx: &mut ViewContext>) {} - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if !self.matches.is_empty() { let window_id = cx.window_id(); let focused_view_id = self.focused_view_id; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 388f1aae88..b8a7853a93 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -271,7 +271,9 @@ actions!( SelectLargerSyntaxNode, SelectSmallerSyntaxNode, GoToDefinition, + GoToDefinitionSplit, GoToTypeDefinition, + GoToTypeDefinitionSplit, MoveToEnclosingBracket, UndoSelection, RedoSelection, @@ -407,7 +409,9 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::go_to_hunk); cx.add_action(Editor::go_to_prev_hunk); cx.add_action(Editor::go_to_definition); + cx.add_action(Editor::go_to_definition_split); cx.add_action(Editor::go_to_type_definition); + cx.add_action(Editor::go_to_type_definition_split); cx.add_action(Editor::fold); cx.add_action(Editor::fold_at); cx.add_action(Editor::unfold_lines); @@ -6185,14 +6189,31 @@ impl Editor { } pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext) { - self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, cx); + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx); } pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext) { - self.go_to_definition_of_kind(GotoDefinitionKind::Type, cx); + self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx); } - fn go_to_definition_of_kind(&mut self, kind: GotoDefinitionKind, cx: &mut ViewContext) { + pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx); + } + + pub fn go_to_type_definition_split( + &mut self, + _: &GoToTypeDefinitionSplit, + cx: &mut ViewContext, + ) { + self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx); + } + + fn go_to_definition_of_kind( + &mut self, + kind: GotoDefinitionKind, + split: bool, + cx: &mut ViewContext, + ) { let Some(workspace) = self.workspace(cx) else { return }; let buffer = self.buffer.read(cx); let head = self.selections.newest::(cx).head(); @@ -6211,7 +6232,7 @@ impl Editor { cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move { let definitions = definitions.await?; editor.update(&mut cx, |editor, cx| { - editor.navigate_to_definitions(definitions, cx); + editor.navigate_to_definitions(definitions, split, cx); })?; Ok::<(), anyhow::Error>(()) }) @@ -6221,6 +6242,7 @@ impl Editor { pub fn navigate_to_definitions( &mut self, mut definitions: Vec, + split: bool, cx: &mut ViewContext, ) { let Some(workspace) = self.workspace(cx) else { return }; @@ -6240,7 +6262,11 @@ impl Editor { } else { cx.window_context().defer(move |cx| { let target_editor: ViewHandle = workspace.update(cx, |workspace, cx| { - workspace.open_project_item(definition.target.buffer.clone(), cx) + if split { + workspace.split_project_item(definition.target.buffer.clone(), cx) + } else { + workspace.open_project_item(definition.target.buffer.clone(), cx) + } }); target_editor.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history @@ -6276,7 +6302,9 @@ impl Editor { .map(|definition| definition.target) .collect(); workspace.update(cx, |workspace, cx| { - Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) + Self::open_locations_in_multibuffer( + workspace, locations, replica_id, title, split, cx, + ) }); }); } @@ -6321,7 +6349,7 @@ impl Editor { }) .unwrap(); Self::open_locations_in_multibuffer( - workspace, locations, replica_id, title, cx, + workspace, locations, replica_id, title, false, cx, ); })?; @@ -6336,6 +6364,7 @@ impl Editor { mut locations: Vec, replica_id: ReplicaId, title: String, + split: bool, cx: &mut ViewContext, ) { // If there are multiple definitions, open them in a multibuffer @@ -6382,7 +6411,11 @@ impl Editor { cx, ); }); - workspace.add_item(Box::new(editor), cx); + if split { + workspace.split_item(Box::new(editor), cx); + } else { + workspace.add_item(Box::new(editor), cx); + } } pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6420c56ece..4f4aa7477d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -156,6 +156,7 @@ impl EditorElement { event.position, event.cmd, event.shift, + event.alt, position_map.as_ref(), text_bounds, cx, @@ -308,6 +309,7 @@ impl EditorElement { position: Vector2F, cmd: bool, shift: bool, + alt: bool, position_map: &PositionMap, text_bounds: RectF, cx: &mut EventContext, @@ -324,9 +326,9 @@ impl EditorElement { if point == target_point { if shift { - go_to_fetched_type_definition(editor, point, cx); + go_to_fetched_type_definition(editor, point, alt, cx); } else { - go_to_fetched_definition(editor, point, cx); + go_to_fetched_definition(editor, point, alt, cx); } return true; diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index f3ee76dc96..31df11a019 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -246,23 +246,26 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { pub fn go_to_fetched_definition( editor: &mut Editor, point: DisplayPoint, + split: bool, cx: &mut ViewContext, ) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, cx); + go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx); } pub fn go_to_fetched_type_definition( editor: &mut Editor, point: DisplayPoint, + split: bool, cx: &mut ViewContext, ) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, cx); + go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx); } fn go_to_fetched_definition_of_kind( kind: LinkDefinitionKind, editor: &mut Editor, point: DisplayPoint, + split: bool, cx: &mut ViewContext, ) { let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); @@ -275,7 +278,7 @@ fn go_to_fetched_definition_of_kind( cx.focus_self(); } - editor.navigate_to_definitions(cached_definitions, cx); + editor.navigate_to_definitions(cached_definitions, split, cx); } else { editor.select( SelectPhase::Begin { @@ -403,7 +406,7 @@ mod tests { }); cx.update_editor(|editor, cx| { - go_to_fetched_type_definition(editor, hover_point, cx); + go_to_fetched_type_definition(editor, hover_point, false, cx); }); requests.next().await; cx.foreground().run_until_parked(); @@ -614,7 +617,7 @@ mod tests { // Cmd click with existing definition doesn't re-request and dismisses highlight cx.update_editor(|editor, cx| { - go_to_fetched_definition(editor, hover_point, cx); + go_to_fetched_definition(editor, hover_point, false, cx); }); // Assert selection moved to to definition cx.lsp @@ -655,7 +658,7 @@ mod tests { ]))) }); cx.update_editor(|editor, cx| { - go_to_fetched_definition(editor, hover_point, cx); + go_to_fetched_definition(editor, hover_point, false, cx); }); requests.next().await; cx.foreground().run_until_parked(); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 3f6bd83760..b6701f12d6 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -442,53 +442,71 @@ impl PickerDelegate for FileFinderDelegate { } } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext) { if let Some(m) = self.matches.get(self.selected_index()) { if let Some(workspace) = self.workspace.upgrade(cx) { - let open_task = workspace.update(cx, |workspace, cx| match m { - Match::History(history_match) => { - let worktree_id = history_match.project.worktree_id; - if workspace - .project() - .read(cx) - .worktree_for_id(worktree_id, cx) - .is_some() - { - workspace.open_path( - ProjectPath { - worktree_id, - path: Arc::clone(&history_match.project.path), - }, - None, - true, - cx, - ) + let open_task = workspace.update(cx, move |workspace, cx| { + let split_or_open = |workspace: &mut Workspace, project_path, cx| { + if secondary { + workspace.split_path(project_path, cx) } else { - match history_match.absolute.as_ref() { - Some(abs_path) => { - workspace.open_abs_path(abs_path.to_path_buf(), false, cx) - } - None => workspace.open_path( + workspace.open_path(project_path, None, true, cx) + } + }; + match m { + Match::History(history_match) => { + let worktree_id = history_match.project.worktree_id; + if workspace + .project() + .read(cx) + .worktree_for_id(worktree_id, cx) + .is_some() + { + split_or_open( + workspace, ProjectPath { worktree_id, path: Arc::clone(&history_match.project.path), }, - None, - true, cx, - ), + ) + } else { + match history_match.absolute.as_ref() { + Some(abs_path) => { + if secondary { + workspace.split_abs_path( + abs_path.to_path_buf(), + false, + cx, + ) + } else { + workspace.open_abs_path( + abs_path.to_path_buf(), + false, + cx, + ) + } + } + None => split_or_open( + workspace, + ProjectPath { + worktree_id, + path: Arc::clone(&history_match.project.path), + }, + cx, + ), + } } } + Match::Search(m) => split_or_open( + workspace, + ProjectPath { + worktree_id: WorktreeId::from_usize(m.worktree_id), + path: m.path.clone(), + }, + cx, + ), } - Match::Search(m) => workspace.open_path( - ProjectPath { - worktree_id: WorktreeId::from_usize(m.worktree_id), - path: m.path.clone(), - }, - None, - true, - cx, - ), }); let row = self diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index 6362b8247d..b5336d5b3b 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -93,7 +93,7 @@ impl PickerDelegate for LanguageSelectorDelegate { self.matches.len() } - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if let Some(mat) = self.matches.get(self.selected_index) { let language_name = &self.candidates[mat.candidate_id].string; let language = self.language_registry.language_for_name(language_name); diff --git a/crates/menu/src/menu.rs b/crates/menu/src/menu.rs index 81716028b9..b0f1a9c6c8 100644 --- a/crates/menu/src/menu.rs +++ b/crates/menu/src/menu.rs @@ -3,6 +3,7 @@ gpui::actions!( [ Cancel, Confirm, + SecondaryConfirm, SelectPrev, SelectNext, SelectFirst, diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index f93fa10052..18e10678fa 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -177,7 +177,7 @@ impl PickerDelegate for OutlineViewDelegate { Task::ready(()) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { self.prev_scroll_position.take(); self.active_editor.update(cx, |active_editor, cx| { if let Some(rows) = active_editor.highlighted_rows() { diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index d09de5320c..6efa33e961 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -7,7 +7,7 @@ use gpui::{ AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext, ViewHandle, }; -use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; +use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use parking_lot::Mutex; use std::{cmp, sync::Arc}; use util::ResultExt; @@ -34,7 +34,7 @@ pub trait PickerDelegate: Sized + 'static { fn selected_index(&self) -> usize; fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; - fn confirm(&mut self, cx: &mut ViewContext>); + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); fn dismissed(&mut self, cx: &mut ViewContext>); fn render_match( &self, @@ -118,8 +118,8 @@ impl View for Picker { // Capture mouse events .on_down(MouseButton::Left, |_, _, _| {}) .on_up(MouseButton::Left, |_, _, _| {}) - .on_click(MouseButton::Left, move |_, picker, cx| { - picker.select_index(ix, cx); + .on_click(MouseButton::Left, move |click, picker, cx| { + picker.select_index(ix, click.cmd, cx); }) .with_cursor_style(CursorStyle::PointingHand) .into_any() @@ -175,6 +175,7 @@ impl Picker { cx.add_action(Self::select_next); cx.add_action(Self::select_prev); cx.add_action(Self::confirm); + cx.add_action(Self::secondary_confirm); cx.add_action(Self::cancel); } @@ -288,11 +289,11 @@ impl Picker { cx.notify(); } - pub fn select_index(&mut self, index: usize, cx: &mut ViewContext) { + pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext) { if self.delegate.match_count() > 0 { self.confirmed = true; self.delegate.set_selected_index(index, cx); - self.delegate.confirm(cx); + self.delegate.confirm(cmd, cx); } } @@ -330,7 +331,12 @@ impl Picker { pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { self.confirmed = true; - self.delegate.confirm(cx); + self.delegate.confirm(false, cx); + } + + pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext) { + self.confirmed = true; + self.delegate.confirm(true, cx); } fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c329ae4e51..3e764b695d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -159,6 +159,9 @@ pub enum Event { entry_id: ProjectEntryId, focus_opened_item: bool, }, + SplitEntry { + entry_id: ProjectEntryId, + }, DockPositionChanged, Focus, } @@ -290,6 +293,21 @@ impl ProjectPanel { } } } + &Event::SplitEntry { entry_id } => { + if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { + if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { + workspace + .split_path( + ProjectPath { + worktree_id: worktree.read(cx).id(), + path: entry.path.clone(), + }, + cx, + ) + .detach_and_log_err(cx); + } + } + } _ => {} } }) @@ -620,6 +638,10 @@ impl ProjectPanel { }); } + fn split_entry(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext) { + cx.emit(Event::SplitEntry { entry_id }); + } + fn new_file(&mut self, _: &NewFile, cx: &mut ViewContext) { self.add_entry(false, cx) } @@ -1333,7 +1355,11 @@ impl ProjectPanel { if kind.is_dir() { this.toggle_expanded(entry_id, cx); } else { - this.open_entry(entry_id, event.click_count > 1, cx); + if event.cmd && event.click_count > 1 { + this.split_entry(entry_id, cx); + } else if !event.cmd { + this.open_entry(entry_id, event.click_count > 1, cx); + } } } }) diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index fc17b57c6d..cbf914230d 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -104,7 +104,7 @@ impl PickerDelegate for ProjectSymbolsDelegate { "Search project symbols...".into() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext) { if let Some(symbol) = self .matches .get(self.selected_match_index) @@ -122,7 +122,12 @@ impl PickerDelegate for ProjectSymbolsDelegate { .read(cx) .clip_point_utf16(symbol.range.start, Bias::Left); - let editor = workspace.open_project_item::(buffer, cx); + let editor = if secondary { + workspace.split_project_item::(buffer, cx) + } else { + workspace.open_project_item::(buffer, cx) + }; + editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::center()), cx, |s| { s.select_ranges([position..position]) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index cd512f1e57..5bf9ba6ccf 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -161,7 +161,7 @@ impl PickerDelegate for RecentProjectsDelegate { Task::ready(()) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { if let Some((selected_match, workspace)) = self .matches .get(self.selected_index()) diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 5775f1b3e7..5510005733 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -120,7 +120,7 @@ impl PickerDelegate for ThemeSelectorDelegate { self.matches.len() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { self.selection_completed = true; let theme_name = theme::current(cx).meta.name.clone(); diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index f363f94983..384b622469 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -182,7 +182,7 @@ impl PickerDelegate for BranchListDelegate { }) } - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { let current_pick = self.selected_index(); let Some(current_pick) = self.matches.get(current_pick).map(|pick| pick.string.clone()) else { return; diff --git a/crates/vector_store/src/modal.rs b/crates/vector_store/src/modal.rs index 9225fe8786..0116f1d2e5 100644 --- a/crates/vector_store/src/modal.rs +++ b/crates/vector_store/src/modal.rs @@ -51,7 +51,7 @@ impl PickerDelegate for SemanticSearchDelegate { "Search repository in natural language...".into() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { if let Some(search_result) = self.matches.get(self.selected_match_index) { // Open Buffer let search_result = search_result.clone(); diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index cf24a9127e..021e3b86a0 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -120,7 +120,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { }) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); update_settings_file::(self.fs.clone(), cx, move |setting| { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 885c686ddc..5781db6f2e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1821,6 +1821,13 @@ impl Workspace { .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); } + pub fn split_item(&mut self, item: Box, cx: &mut ViewContext) { + let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx); + new_pane.update(cx, move |new_pane, cx| { + new_pane.add_item(item, true, true, None, cx) + }) + } + pub fn open_abs_path( &mut self, abs_path: PathBuf, @@ -1851,6 +1858,21 @@ impl Workspace { }) } + pub fn split_abs_path( + &mut self, + abs_path: PathBuf, + visible: bool, + cx: &mut ViewContext, + ) -> Task>> { + let project_path_task = + Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); + cx.spawn(|this, mut cx| async move { + let (_, path) = project_path_task.await?; + this.update(&mut cx, |this, cx| this.split_path(path, cx))? + .await + }) + } + pub fn open_path( &mut self, path: impl Into, @@ -1876,6 +1898,38 @@ impl Workspace { }) } + pub fn split_path( + &mut self, + path: impl Into, + cx: &mut ViewContext, + ) -> Task, anyhow::Error>> { + let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }); + + if let Member::Pane(center_pane) = &self.center.root { + if center_pane.read(cx).items_len() == 0 { + return self.open_path(path, Some(pane), true, cx); + } + } + + let task = self.load_path(path.into(), cx); + cx.spawn(|this, mut cx| async move { + let (project_entry_id, build_item) = task.await?; + this.update(&mut cx, move |this, cx| -> Option<_> { + let pane = pane.upgrade(cx)?; + let new_pane = this.split_pane(pane, SplitDirection::Right, cx); + new_pane.update(cx, |new_pane, cx| { + Some(new_pane.open_item(project_entry_id, true, cx, build_item)) + }) + }) + .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? + }) + } + pub(crate) fn load_path( &mut self, path: ProjectPath, @@ -1926,6 +1980,30 @@ impl Workspace { item } + pub fn split_project_item( + &mut self, + project_item: ModelHandle, + cx: &mut ViewContext, + ) -> ViewHandle + 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.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + self.split_item(Box::new(item.clone()), cx); + item + } + pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { self.active_pane.update(cx, |pane, cx| { @@ -1953,7 +2031,7 @@ impl Workspace { if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { cx.focus(&pane); } else { - self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx); + self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); } } @@ -2006,7 +2084,7 @@ impl Workspace { match event { pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), pane::Event::Split(direction) => { - self.split_pane(pane, *direction, cx); + self.split_and_clone(pane, *direction, cx); } pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::ActivateItem { local } => { @@ -2057,6 +2135,20 @@ impl Workspace { } pub fn split_pane( + &mut self, + pane_to_split: ViewHandle, + split_direction: SplitDirection, + cx: &mut ViewContext, + ) -> ViewHandle { + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, split_direction) + .unwrap(); + cx.notify(); + new_pane + } + + pub fn split_and_clone( &mut self, pane: ViewHandle, direction: SplitDirection, @@ -4246,7 +4338,7 @@ mod tests { }); workspace - .split_pane(left_pane.clone(), SplitDirection::Right, cx) + .split_and_clone(left_pane.clone(), SplitDirection::Right, cx) .unwrap(); left_pane diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 09bdbf65be..1621ae276a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1021,7 +1021,7 @@ mod tests { // Split the pane with the first entry, then open the second entry again. workspace .update(cx, |w, cx| { - w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx); + w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, cx); w.open_path(file2.clone(), None, true, cx) }) .await @@ -1344,7 +1344,11 @@ mod tests { cx.dispatch_action(window_id, NewFile); workspace .update(cx, |workspace, cx| { - workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); + workspace.split_and_clone( + workspace.active_pane().clone(), + SplitDirection::Right, + cx, + ); workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx) }) .await