Added 'open in terminal' action to the project panel context menu

Mikayla created

Also slightly re-arranged the project panel context menu

Change summary

crates/project_panel/src/project_panel.rs  | 38 +++++++++++++++++++---
crates/terminal_view/src/terminal_panel.rs | 40 ++++++++++++++++++------
crates/workspace/src/workspace.rs          | 10 +++++
3 files changed, 72 insertions(+), 16 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -122,6 +122,7 @@ actions!(
         CopyPath,
         CopyRelativePath,
         RevealInFinder,
+        OpenInTerminal,
         Cut,
         Paste,
         Delete,
@@ -156,6 +157,7 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
     cx.add_action(ProjectPanel::copy_path);
     cx.add_action(ProjectPanel::copy_relative_path);
     cx.add_action(ProjectPanel::reveal_in_finder);
+    cx.add_action(ProjectPanel::open_in_terminal);
     cx.add_action(ProjectPanel::new_search_in_directory);
     cx.add_action(
         |this: &mut ProjectPanel, action: &Paste, cx: &mut ViewContext<ProjectPanel>| {
@@ -423,24 +425,30 @@ impl ProjectPanel {
             menu_entries.push(ContextMenuItem::Separator);
             menu_entries.push(ContextMenuItem::action("Cut", Cut));
             menu_entries.push(ContextMenuItem::action("Copy", Copy));
+            if let Some(clipboard_entry) = self.clipboard_entry {
+                if clipboard_entry.worktree_id() == worktree.id() {
+                    menu_entries.push(ContextMenuItem::action("Paste", Paste));
+                }
+            }
             menu_entries.push(ContextMenuItem::Separator);
             menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath));
             menu_entries.push(ContextMenuItem::action(
                 "Copy Relative Path",
                 CopyRelativePath,
             ));
+
+            if entry.is_dir() {
+                menu_entries.push(ContextMenuItem::Separator);
+            }
             menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder));
             if entry.is_dir() {
+                menu_entries.push(ContextMenuItem::action("Open in Terminal", OpenInTerminal));
                 menu_entries.push(ContextMenuItem::action(
                     "Search Inside",
                     NewSearchInDirectory,
                 ));
             }
-            if let Some(clipboard_entry) = self.clipboard_entry {
-                if clipboard_entry.worktree_id() == worktree.id() {
-                    menu_entries.push(ContextMenuItem::action("Paste", Paste));
-                }
-            }
+
             menu_entries.push(ContextMenuItem::Separator);
             menu_entries.push(ContextMenuItem::action("Rename", Rename));
             if !is_root {
@@ -965,6 +973,26 @@ impl ProjectPanel {
         }
     }
 
+    fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
+        if let Some((worktree, entry)) = self.selected_entry(cx) {
+            let window = cx.window();
+            let view_id = cx.view_id();
+            let path = worktree.abs_path().join(&entry.path);
+
+            cx.app_context()
+                .spawn(|mut cx| async move {
+                    window.dispatch_action(
+                        view_id,
+                        &workspace::OpenTerminal {
+                            working_directory: path,
+                        },
+                        &mut cx,
+                    );
+                })
+                .detach();
+        }
+    }
+
     pub fn new_search_in_directory(
         &mut self,
         _: &NewSearchInDirectory,

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1,4 +1,4 @@
-use std::sync::Arc;
+use std::{path::PathBuf, sync::Arc};
 
 use crate::TerminalView;
 use db::kvp::KEY_VALUE_STORE;
@@ -23,6 +23,7 @@ actions!(terminal_panel, [ToggleFocus]);
 
 pub fn init(cx: &mut AppContext) {
     cx.add_action(TerminalPanel::new_terminal);
+    cx.add_action(TerminalPanel::open_terminal);
 }
 
 #[derive(Debug)]
@@ -79,7 +80,7 @@ impl TerminalPanel {
                             cx.window_context().defer(move |cx| {
                                 if let Some(this) = this.upgrade(cx) {
                                     this.update(cx, |this, cx| {
-                                        this.add_terminal(cx);
+                                        this.add_terminal(None, cx);
                                     });
                                 }
                             })
@@ -230,6 +231,21 @@ impl TerminalPanel {
         }
     }
 
+    pub fn open_terminal(
+        workspace: &mut Workspace,
+        action: &workspace::OpenTerminal,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let Some(this) = workspace.focus_panel::<Self>(cx) else {
+            return;
+        };
+
+        this.update(cx, |this, cx| {
+            this.add_terminal(Some(action.working_directory.clone()), cx)
+        })
+    }
+
+    ///Create a new Terminal in the current working directory or the user's home directory
     fn new_terminal(
         workspace: &mut Workspace,
         _: &workspace::NewTerminal,
@@ -239,19 +255,23 @@ impl TerminalPanel {
             return;
         };
 
-        this.update(cx, |this, cx| this.add_terminal(cx))
+        this.update(cx, |this, cx| this.add_terminal(None, cx))
     }
 
-    fn add_terminal(&mut self, cx: &mut ViewContext<Self>) {
+    fn add_terminal(&mut self, working_directory: Option<PathBuf>, cx: &mut ViewContext<Self>) {
         let workspace = self.workspace.clone();
         cx.spawn(|this, mut cx| async move {
             let pane = this.read_with(&cx, |this, _| this.pane.clone())?;
             workspace.update(&mut cx, |workspace, cx| {
-                let working_directory_strategy = settings::get::<TerminalSettings>(cx)
-                    .working_directory
-                    .clone();
-                let working_directory =
-                    crate::get_working_directory(workspace, cx, working_directory_strategy);
+                let working_directory = if let Some(working_directory) = working_directory {
+                    Some(working_directory)
+                } else {
+                    let working_directory_strategy = settings::get::<TerminalSettings>(cx)
+                        .working_directory
+                        .clone();
+                    crate::get_working_directory(workspace, cx, working_directory_strategy)
+                };
+
                 let window = cx.window();
                 if let Some(terminal) = workspace.project().update(cx, |project, cx| {
                     project
@@ -389,7 +409,7 @@ impl Panel for TerminalPanel {
 
     fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
         if active && self.pane.read(cx).items_len() == 0 {
-            self.add_terminal(cx)
+            self.add_terminal(None, cx)
         }
     }
 

crates/workspace/src/workspace.rs 🔗

@@ -203,7 +203,15 @@ impl Clone for Toast {
     }
 }
 
-impl_actions!(workspace, [ActivatePane, ActivatePaneInDirection, Toast]);
+#[derive(Clone, Deserialize, PartialEq)]
+pub struct OpenTerminal {
+    pub working_directory: PathBuf,
+}
+
+impl_actions!(
+    workspace,
+    [ActivatePane, ActivatePaneInDirection, Toast, OpenTerminal]
+);
 
 pub type WorkspaceId = i64;