zed2: Position terminal context menu, wire up dismis, and unbork context menu (#3561)

Julia created

Release Notes:

- N/A

Change summary

crates/terminal_view2/src/terminal_element.rs | 16 ---
crates/terminal_view2/src/terminal_view.rs    | 98 +++++++++++---------
crates/ui2/src/components/context_menu.rs     |  6 -
3 files changed, 56 insertions(+), 64 deletions(-)

Detailed changes

crates/terminal_view2/src/terminal_element.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
     FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
     IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
     PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
-    TextRun, TextStyle, TextSystem, UnderlineStyle, View, WhiteSpace, WindowContext,
+    TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
 };
 use itertools::Itertools;
 use language::CursorShape;
@@ -27,8 +27,6 @@ use ui::Tooltip;
 use std::mem;
 use std::{fmt::Debug, ops::RangeInclusive};
 
-use crate::TerminalView;
-
 ///The information generated during layout that is necessary for painting
 pub struct LayoutState {
     cells: Vec<LayoutCell>,
@@ -149,7 +147,6 @@ impl LayoutRect {
 ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
 pub struct TerminalElement {
     terminal: Model<Terminal>,
-    terminal_view: View<TerminalView>,
     focus: FocusHandle,
     focused: bool,
     cursor_visible: bool,
@@ -168,7 +165,6 @@ impl StatefulInteractiveElement for TerminalElement {}
 impl TerminalElement {
     pub fn new(
         terminal: Model<Terminal>,
-        terminal_view: View<TerminalView>,
         focus: FocusHandle,
         focused: bool,
         cursor_visible: bool,
@@ -176,7 +172,6 @@ impl TerminalElement {
     ) -> TerminalElement {
         TerminalElement {
             terminal,
-            terminal_view,
             focused,
             focus: focus.clone(),
             cursor_visible,
@@ -774,18 +769,11 @@ impl Element for TerminalElement {
         (layout_id, interactive_state)
     }
 
-    fn paint(
-        mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut Self::State,
-        cx: &mut WindowContext<'_>,
-    ) {
+    fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext<'_>) {
         let mut layout = self.compute_layout(bounds, cx);
 
         let theme = cx.theme();
 
-        let dispatch_context = self.terminal_view.read(cx).dispatch_context(cx);
-        self.interactivity().key_context = Some(dispatch_context);
         cx.paint_quad(
             bounds,
             Default::default(),

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -9,9 +9,10 @@ pub mod terminal_panel;
 // use crate::terminal_element::TerminalElement;
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, Focusable,
-    FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton,
-    MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView,
+    div, overlay, Action, AnyElement, AppContext, DismissEvent, Div, EventEmitter, FocusEvent,
+    FocusHandle, Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke,
+    Model, MouseButton, MouseDownEvent, Pixels, Render, Styled, Subscription, Task, View,
+    VisualContext, WeakView,
 };
 use language::Bias;
 use persistence::TERMINAL_DB;
@@ -81,7 +82,7 @@ pub struct TerminalView {
     has_new_content: bool,
     //Currently using iTerm bell, show bell emoji in tab until input is received
     has_bell: bool,
-    context_menu: Option<View<ContextMenu>>,
+    context_menu: Option<(View<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
     blink_state: bool,
     blinking_on: bool,
     blinking_paused: bool,
@@ -312,14 +313,24 @@ impl TerminalView {
         position: gpui::Point<Pixels>,
         cx: &mut ViewContext<Self>,
     ) {
-        self.context_menu = Some(ContextMenu::build(cx, |menu, cx| {
+        let context_menu = ContextMenu::build(cx, |menu, cx| {
             menu.action("Clear", Box::new(Clear))
                 .action("Close", Box::new(CloseActiveItem { save_intent: None }))
-        }));
-        // todo!(context menus)
-        //     self.context_menu
-        //         .show(position, AnchorCorner::TopLeft, menu_entries, cx);
-        //     cx.notify();
+        });
+
+        cx.focus_view(&context_menu);
+        let subscription =
+            cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
+                if this.context_menu.as_ref().is_some_and(|context_menu| {
+                    context_menu.0.focus_handle(cx).contains_focused(cx)
+                }) {
+                    cx.focus_self();
+                }
+                this.context_menu.take();
+                cx.notify();
+            });
+
+        self.context_menu = Some((context_menu, position, subscription));
     }
 
     fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
@@ -621,7 +632,6 @@ impl Render for TerminalView {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let terminal_handle = self.terminal.clone();
-        let this_view = cx.view().clone();
 
         let self_id = cx.entity_id();
         let focused = self.focus_handle.is_focused(cx);
@@ -629,43 +639,41 @@ impl Render for TerminalView {
         div()
             .size_full()
             .relative()
-            .child(
-                div()
-                    .z_index(0)
-                    .absolute()
-                    .size_full()
-                    .on_key_down(cx.listener(Self::key_down))
-                    .on_action(cx.listener(TerminalView::send_text))
-                    .on_action(cx.listener(TerminalView::send_keystroke))
-                    .on_action(cx.listener(TerminalView::copy))
-                    .on_action(cx.listener(TerminalView::paste))
-                    .on_action(cx.listener(TerminalView::clear))
-                    .on_action(cx.listener(TerminalView::show_character_palette))
-                    .on_action(cx.listener(TerminalView::select_all))
-                    .child(TerminalElement::new(
-                        terminal_handle,
-                        this_view,
-                        self.focus_handle.clone(),
-                        focused,
-                        self.should_show_cursor(focused, cx),
-                        self.can_navigate_to_selected_word,
-                    ))
-                    .on_mouse_down(
-                        MouseButton::Right,
-                        cx.listener(|this, event: &MouseDownEvent, cx| {
-                            this.deploy_context_menu(event.position, cx);
-                            cx.notify();
-                        }),
-                    ),
-            )
-            .children(
-                self.context_menu
-                    .clone()
-                    .map(|context_menu| div().z_index(1).absolute().child(context_menu)),
-            )
             .track_focus(&self.focus_handle)
+            .key_context(self.dispatch_context(cx))
+            .on_action(cx.listener(TerminalView::send_text))
+            .on_action(cx.listener(TerminalView::send_keystroke))
+            .on_action(cx.listener(TerminalView::copy))
+            .on_action(cx.listener(TerminalView::paste))
+            .on_action(cx.listener(TerminalView::clear))
+            .on_action(cx.listener(TerminalView::show_character_palette))
+            .on_action(cx.listener(TerminalView::select_all))
             .on_focus_in(cx.listener(Self::focus_in))
             .on_focus_out(cx.listener(Self::focus_out))
+            .on_key_down(cx.listener(Self::key_down))
+            .on_mouse_down(
+                MouseButton::Right,
+                cx.listener(|this, event: &MouseDownEvent, cx| {
+                    this.deploy_context_menu(event.position, cx);
+                    cx.notify();
+                }),
+            )
+            .child(
+                // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu
+                div().size_full().child(TerminalElement::new(
+                    terminal_handle,
+                    self.focus_handle.clone(),
+                    focused,
+                    self.should_show_cursor(focused, cx),
+                    self.can_navigate_to_selected_word,
+                )),
+            )
+            .children(self.context_menu.as_ref().map(|(menu, positon, _)| {
+                overlay()
+                    .position(*positon)
+                    .anchor(gpui::AnchorCorner::TopLeft)
+                    .child(menu.clone())
+            }))
     }
 }
 

crates/ui2/src/components/context_menu.rs 🔗

@@ -239,7 +239,6 @@ impl Render for ContextMenu {
                                 action,
                             } => {
                                 let handler = handler.clone();
-                                let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent));
 
                                 let label_element = if let Some(icon) = icon {
                                     h_stack()
@@ -263,10 +262,7 @@ impl Render for ContextMenu {
                                             })),
                                     )
                                     .selected(Some(ix) == self.selected_index)
-                                    .on_click(move |event, cx| {
-                                        handler(cx);
-                                        dismiss(event, cx)
-                                    })
+                                    .on_click(move |_, cx| handler(cx))
                                     .into_any_element()
                             }
                         },