From 870a61dd4dfde3be49eff252452dd46b1fa51060 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 Apr 2024 01:43:46 +0300 Subject: [PATCH] Add "Open in Terminal" context menu entries for project panel, editor and tab context menus (#10741) Closes https://github.com/zed-industries/zed/issues/4566 Pane tabs (does not exist for multibuffer tabs): Screenshot 2024-04-18 at 23 01 08 Editor context menu: Screenshot 2024-04-18 at 23 01 14 Project panel context menu (was not shown for file entries before this): Screenshot 2024-04-18 at 23 01 18 Release Notes: - (breaking change) Moved `project_panel::OpenInTerminal` into `workspace::OpenInTerminal` action and add it in editors, tab context menus and proper panel file entries ([4566](https://github.com/zed-industries/zed/issues/4566)) --- crates/editor/src/editor.rs | 21 ++++++- crates/editor/src/element.rs | 1 + crates/editor/src/mouse_context_menu.rs | 2 + crates/project_panel/src/project_panel.rs | 26 +++++---- crates/terminal_view/src/terminal_panel.rs | 28 +++------ crates/workspace/src/pane.rs | 67 +++++++++++++++++----- crates/workspace/src/workspace.rs | 1 + 7 files changed, 101 insertions(+), 45 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1134567a84e4c4a0371f4fa90057b060a50cff15..a7e3827ecb8d1f184e6fdb159d7ac1f24bc6e105 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -131,10 +131,10 @@ use ui::{ use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::item::ItemHandle; use workspace::notifications::NotificationId; -use workspace::Toast; use workspace::{ searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId, }; +use workspace::{OpenInTerminal, OpenTerminal, Toast}; use crate::hover_links::find_url; @@ -4943,6 +4943,25 @@ impl Editor { } } + pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext) { + if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + let project_path = buffer.read(cx).project_path(cx)?; + let project = self.project.as_ref()?.read(cx); + let entry = project.entry_for_path(&project_path, cx)?; + let abs_path = project.absolute_path(&project_path, cx)?; + let parent = if entry.is_symlink { + abs_path.canonicalize().ok()? + } else { + abs_path + } + .parent()? + .to_path_buf(); + Some(parent) + }) { + cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone()); + } + } + fn gather_revert_changes( &mut self, selections: &[Selection], diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2e957f313c0b4a5984b4a0b3940ce095a1555cba..fa05745171ce6e107f15a0da32b0d6a501e81c18 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -363,6 +363,7 @@ impl EditorElement { register_action(view, cx, Editor::unique_lines_case_sensitive); register_action(view, cx, Editor::accept_partial_inline_completion); register_action(view, cx, Editor::revert_selected_hunks); + register_action(view, cx, Editor::open_active_item_in_terminal) } fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) { diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index cc50e6a6038f72b1ab938445b92da845e5225e48..ac97fa0dc07482f58db6f074bad4bfc5100c2222 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -3,6 +3,7 @@ use crate::{ GoToTypeDefinition, Rename, RevealInFinder, SelectMode, ToggleCodeActions, }; use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext}; +use workspace::OpenInTerminal; pub struct MouseContextMenu { pub(crate) position: Point, @@ -83,6 +84,7 @@ pub fn deploy_context_menu( ) .separator() .action("Reveal in Finder", Box::new(RevealInFinder)) + .action("Open in Terminal", Box::new(OpenInTerminal)) }) }; let mouse_context_menu = MouseContextMenu::new(position, context_menu, cx); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 830f0226072bbffae790d794193d08f22273a51c..89f3c7e8d9b992796225c3b5bcc0fad6dcb3f58a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -37,7 +37,7 @@ use util::{maybe, NumericPrefixWithSuffix, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, notifications::DetachAndPromptErr, - Workspace, + OpenInTerminal, Workspace, }; const PROJECT_PANEL_KEY: &str = "ProjectPanel"; @@ -127,7 +127,6 @@ actions!( CopyPath, CopyRelativePath, RevealInFinder, - OpenInTerminal, Cut, Paste, Rename, @@ -441,9 +440,7 @@ impl ProjectPanel { .action("New Folder", Box::new(NewDirectory)) .separator() .action("Reveal in Finder", Box::new(RevealInFinder)) - .when(is_dir, |menu| { - menu.action("Open in Terminal…", Box::new(OpenInTerminal)) - }) + .action("Open in Terminal", Box::new(OpenInTerminal)) .when(is_dir, |menu| { menu.separator() .action("Find in Folder…", Box::new(NewSearchInDirectory)) @@ -1131,13 +1128,20 @@ impl ProjectPanel { fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext) { if let Some((worktree, entry)) = self.selected_entry(cx) { - let path = worktree.abs_path().join(&entry.path); - cx.dispatch_action( - workspace::OpenTerminal { - working_directory: path, + let abs_path = worktree.abs_path().join(&entry.path); + let working_directory = if entry.is_dir() { + Some(abs_path) + } else { + if entry.is_symlink { + abs_path.canonicalize().ok() + } else { + Some(abs_path) } - .boxed_clone(), - ) + .and_then(|path| Some(path.parent()?.to_path_buf())) + }; + if let Some(working_directory) = working_directory { + cx.dispatch_action(workspace::OpenTerminal { working_directory }.boxed_clone()) + } } } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5effeda337970419d692ee3082575d3c8b1172a3..749c4f0b9315cca381330300e23ca35c8f8485c4 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -98,7 +98,6 @@ impl TerminalPanel { .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&workspace::ToggleZoom, cx); })) - // TODO kb .tooltip(move |cx| { Tooltip::for_action( if zoomed { "Zoom Out" } else { "Zoom In" }, @@ -292,13 +291,13 @@ impl TerminalPanel { action: &workspace::OpenTerminal, cx: &mut ViewContext, ) { - let Some(this) = workspace.focus_panel::(cx) else { + let Some(terminal_panel) = workspace.panel::(cx) else { return; }; - - this.update(cx, |this, cx| { - this.add_terminal(Some(action.working_directory.clone()), None, cx) - }) + terminal_panel.update(cx, |panel, cx| { + panel.add_terminal(Some(action.working_directory.clone()), None, cx) + }); + workspace.focus_panel::(cx); } fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext) { @@ -427,26 +426,17 @@ impl TerminalPanel { } } - ///Create a new Terminal in the current working directory or the user's home directory + /// Create a new Terminal in the current working directory or the user's home directory fn new_terminal( workspace: &mut Workspace, _: &workspace::NewTerminal, cx: &mut ViewContext, ) { - let has_no_terminals = workspace - .panel::(cx) - .map(|terminal_panel| terminal_panel.update(cx, |panel, cx| panel.has_no_terminals(cx))) - .unwrap_or(true); - let Some(this) = workspace.focus_panel::(cx) else { + let Some(terminal_panel) = workspace.panel::(cx) else { return; }; - if has_no_terminals { - // `set_active` on focus, will already add a new terminal - // into an empty terminal pane, no need to add another one - return; - } - - this.update(cx, |this, cx| this.add_terminal(None, None, cx)) + terminal_panel.update(cx, |this, cx| this.add_terminal(None, None, cx)); + workspace.focus_panel::(cx); } fn terminals_for_task( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 26626f5ba16c8697d0d09ebfa23d861e0e0ffeb8..bf32f7c0c24fa7ebaaa6d5143c7a14d805753329 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -5,7 +5,8 @@ use crate::{ }, toolbar::Toolbar, workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings}, - NewCenterTerminal, NewFile, NewSearch, OpenVisible, SplitDirection, ToggleZoom, Workspace, + NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible, + SplitDirection, ToggleZoom, Workspace, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; @@ -1597,20 +1598,58 @@ impl Pane { ); if let Some(entry) = single_entry_to_resolve { + let parent_abs_path = pane + .update(cx, |pane, cx| { + pane.workspace.update(cx, |workspace, cx| { + let project = workspace.project().read(cx); + project.worktree_for_entry(entry, cx).and_then(|worktree| { + let worktree = worktree.read(cx); + let entry = worktree.entry_for_id(entry)?; + let abs_path = worktree.absolutize(&entry.path).ok()?; + let parent = if entry.is_symlink { + abs_path.canonicalize().ok()? + } else { + abs_path + } + .parent()? + .to_path_buf(); + Some(parent) + }) + }) + }) + .ok() + .flatten(); + let entry_id = entry.to_proto(); - menu = menu.separator().entry( - "Reveal In Project Panel", - Some(Box::new(RevealInProjectPanel { - entry_id: Some(entry_id), - })), - cx.handler_for(&pane, move |pane, cx| { - pane.project.update(cx, |_, cx| { - cx.emit(project::Event::RevealInProjectPanel( - ProjectEntryId::from_proto(entry_id), - )) - }); - }), - ); + menu = menu + .separator() + .entry( + "Reveal In Project Panel", + Some(Box::new(RevealInProjectPanel { + entry_id: Some(entry_id), + })), + cx.handler_for(&pane, move |pane, cx| { + pane.project.update(cx, |_, cx| { + cx.emit(project::Event::RevealInProjectPanel( + ProjectEntryId::from_proto(entry_id), + )) + }); + }), + ) + .when_some(parent_abs_path, |menu, abs_path| { + menu.entry( + "Open in Terminal", + Some(Box::new(OpenInTerminal)), + cx.handler_for(&pane, move |_, cx| { + cx.dispatch_action( + OpenTerminal { + working_directory: abs_path.clone(), + } + .boxed_clone(), + ); + }), + ) + }); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1a0e9cc740884758cc6049ce63ee2831ade03b58..9c6e07b941a86cbdbc3f72f3cf24baa368be481d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -112,6 +112,7 @@ actions!( workspace, [ Open, + OpenInTerminal, NewFile, NewWindow, CloseWindow,