Merge pull request #1471 from zed-industries/terminal-context-menu

Mikayla Maki created

Terminal context menu

Change summary

Cargo.lock                            |  1 
crates/terminal/Cargo.toml            |  1 
crates/terminal/src/connected_el.rs   | 10 ++++++
crates/terminal/src/connected_view.rs | 41 ++++++++++++++++++++++++++--
crates/terminal/src/terminal.rs       |  3 -
crates/terminal/src/terminal_view.rs  | 10 ++++--
6 files changed, 56 insertions(+), 10 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5356,6 +5356,7 @@ dependencies = [
  "alacritty_terminal",
  "anyhow",
  "client",
+ "context_menu",
  "dirs 4.0.0",
  "editor",
  "futures",

crates/terminal/Cargo.toml 🔗

@@ -16,6 +16,7 @@ theme = { path = "../theme" }
 settings = { path = "../settings" }
 workspace = { path = "../workspace" }
 project = { path = "../project" }
+context_menu = { path = "../context_menu" }
 smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2.5"
 mio-extras = "2.0.6"

crates/terminal/src/connected_el.rs 🔗

@@ -33,7 +33,9 @@ use std::{
 use std::{fmt::Debug, ops::Sub};
 
 use crate::{
-    connected_view::ConnectedView, mappings::colors::convert_color, Terminal, TerminalSize,
+    connected_view::{ConnectedView, DeployContextMenu},
+    mappings::colors::convert_color,
+    Terminal, TerminalSize,
 };
 
 ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
@@ -463,6 +465,12 @@ impl TerminalEl {
                         }
                     },
                 )
+                .on_click(
+                    MouseButton::Right,
+                    move |MouseButtonEvent { position, .. }, cx| {
+                        cx.dispatch_action(DeployContextMenu { position });
+                    },
+                )
                 .on_drag(
                     MouseButton::Left,
                     move |_, MouseMovedEvent { position, .. }, cx| {

crates/terminal/src/connected_view.rs 🔗

@@ -1,8 +1,14 @@
 use alacritty_terminal::term::TermMode;
+use context_menu::{ContextMenu, ContextMenuItem};
 use gpui::{
-    actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext,
-    View, ViewContext,
+    actions,
+    elements::{ChildView, ParentElement, Stack},
+    geometry::vector::Vector2F,
+    impl_internal_actions,
+    keymap::Keystroke,
+    AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
 };
+use workspace::pane;
 
 use crate::{connected_el::TerminalEl, Event, Terminal};
 
@@ -10,10 +16,16 @@ use crate::{connected_el::TerminalEl, Event, Terminal};
 #[derive(Clone, Debug, PartialEq)]
 pub struct ScrollTerminal(pub i32);
 
+#[derive(Clone, PartialEq)]
+pub struct DeployContextMenu {
+    pub position: Vector2F,
+}
+
 actions!(
     terminal,
     [Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,]
 );
+impl_internal_actions!(project_panel, [DeployContextMenu]);
 
 pub fn init(cx: &mut MutableAppContext) {
     //Global binding overrrides
@@ -23,6 +35,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(ConnectedView::escape);
     cx.add_action(ConnectedView::enter);
     //Useful terminal views
+    cx.add_action(ConnectedView::deploy_context_menu);
     cx.add_action(ConnectedView::copy);
     cx.add_action(ConnectedView::paste);
     cx.add_action(ConnectedView::clear);
@@ -36,6 +49,7 @@ pub struct ConnectedView {
     has_bell: bool,
     // Only for styling purposes. Doesn't effect behavior
     modal: bool,
+    context_menu: ViewHandle<ContextMenu>,
 }
 
 impl ConnectedView {
@@ -67,6 +81,7 @@ impl ConnectedView {
             has_new_content: true,
             has_bell: false,
             modal,
+            context_menu: cx.add_view(|cx| ContextMenu::new(cx)),
         }
     }
 
@@ -87,6 +102,18 @@ impl ConnectedView {
         cx.emit(Event::Wakeup);
     }
 
+    pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
+        let menu_entries = vec![
+            ContextMenuItem::item("Clear Buffer", Clear),
+            ContextMenuItem::item("Close Terminal", pane::CloseActiveItem),
+        ];
+
+        self.context_menu
+            .update(cx, |menu, cx| menu.show(action.position, menu_entries, cx));
+
+        cx.notify();
+    }
+
     fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
         self.terminal.update(cx, |term, _| term.clear());
     }
@@ -151,8 +178,14 @@ impl View for ConnectedView {
 
     fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
         let terminal_handle = self.terminal.clone().downgrade();
-        TerminalEl::new(cx.handle(), terminal_handle, self.modal)
-            .contained()
+
+        Stack::new()
+            .with_child(
+                TerminalEl::new(cx.handle(), terminal_handle, self.modal)
+                    .contained()
+                    .boxed(),
+            )
+            .with_child(ChildView::new(&self.context_menu).boxed())
             .boxed()
     }
 

crates/terminal/src/terminal.rs 🔗

@@ -27,7 +27,6 @@ use futures::{
 use modal::deploy_modal;
 use settings::{Settings, Shell};
 use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration};
-use terminal_view::TerminalView;
 use thiserror::Error;
 
 use gpui::{
@@ -43,9 +42,9 @@ use crate::mappings::{
 
 ///Initialize and register all of our action handlers
 pub fn init(cx: &mut MutableAppContext) {
-    cx.add_action(TerminalView::deploy);
     cx.add_action(deploy_modal);
 
+    terminal_view::init(cx);
     connected_view::init(cx);
 }
 

crates/terminal/src/terminal_view.rs 🔗

@@ -1,9 +1,10 @@
 use crate::connected_view::ConnectedView;
 use crate::{Event, Terminal, TerminalBuilder, TerminalError};
+
 use dirs::home_dir;
 use gpui::{
-    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, View, ViewContext,
-    ViewHandle,
+    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, View,
+    ViewContext, ViewHandle,
 };
 use workspace::{Item, Workspace};
 
@@ -17,6 +18,10 @@ use crate::connected_el::TerminalEl;
 
 actions!(terminal, [DeployModal]);
 
+pub fn init(cx: &mut MutableAppContext) {
+    cx.add_action(TerminalView::deploy);
+}
+
 //Make terminal view an enum, that can give you views for the error and non-error states
 //Take away all the result unwrapping in the current TerminalView by making it 'infallible'
 //Bubble up to deploy(_modal)() calls
@@ -138,7 +143,6 @@ impl View for TerminalView {
             TerminalContent::Connected(connected) => ChildView::new(connected),
             TerminalContent::Error(error) => ChildView::new(error),
         };
-
         if self.modal {
             let settings = cx.global::<Settings>();
             let container_style = settings.theme.terminal.modal_container;