Fix more bugs around terminal2 and focus (#3534)

Kirill Bulatov created

Change summary

crates/gpui2/src/app.rs                        |  4 +
crates/gpui2/src/interactive.rs                |  6 ++
crates/gpui2/src/window.rs                     | 18 ++++----
crates/gpui2_macros/src/action.rs              |  1 
crates/recent_projects2/src/projects.rs        |  1 
crates/recent_projects2/src/recent_projects.rs |  7 +-
crates/terminal2/src/terminal2.rs              | 12 +++++
crates/terminal_view2/src/terminal_element.rs  | 41 ++++++++++++++-----
crates/terminal_view2/src/terminal_view.rs     | 10 +--
crates/workspace2/src/dock.rs                  | 19 ++++++--
crates/workspace2/src/workspace2.rs            |  6 +-
11 files changed, 87 insertions(+), 38 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -1110,6 +1110,10 @@ impl AppContext {
             }
         }
     }
+
+    pub fn has_active_drag(&self) -> bool {
+        self.active_drag.is_some()
+    }
 }
 
 impl Context for AppContext {

crates/gpui2/src/interactive.rs 🔗

@@ -193,6 +193,12 @@ impl Deref for MouseExitEvent {
 #[derive(Debug, Clone, Default)]
 pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
 
+impl ExternalPaths {
+    pub fn paths(&self) -> &[PathBuf] {
+        &self.0
+    }
+}
+
 impl Render for ExternalPaths {
     type Element = Div;
 

crates/gpui2/src/window.rs 🔗

@@ -4,12 +4,12 @@ use crate::{
     DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
     EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
     ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
-    ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
-    MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
-    PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
-    UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path,
+    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
+    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
+    Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
+    WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Context as _, Result};
 use collections::HashMap;
@@ -1269,10 +1269,9 @@ impl<'a> WindowContext<'a> {
                             cursor_offset: position,
                         });
                     }
-                    InputEvent::MouseDown(MouseDownEvent {
+                    InputEvent::MouseMove(MouseMoveEvent {
                         position,
-                        button: MouseButton::Left,
-                        click_count: 1,
+                        pressed_button: Some(MouseButton::Left),
                         modifiers: Modifiers::default(),
                     })
                 }
@@ -1285,6 +1284,7 @@ impl<'a> WindowContext<'a> {
                     })
                 }
                 FileDropEvent::Submit { position } => {
+                    self.activate(true);
                     self.window.mouse_position = position;
                     InputEvent::MouseUp(MouseUpEvent {
                         button: MouseButton::Left,

crates/gpui2_macros/src/action.rs 🔗

@@ -38,6 +38,7 @@ pub fn action(input: TokenStream) -> TokenStream {
 
     let build_impl = if is_unit_struct {
         quote! {
+            let _ = value;
             Ok(std::boxed::Box::new(Self {}))
         }
     } else {

crates/recent_projects2/src/recent_projects.rs 🔗

@@ -1,9 +1,10 @@
 mod highlighted_workspace_location;
+mod projects;
 
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task,
-    View, ViewContext, WeakView,
+    AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View,
+    ViewContext, WeakView,
 };
 use highlighted_workspace_location::HighlightedWorkspaceLocation;
 use ordered_float::OrderedFloat;
@@ -16,7 +17,7 @@ use workspace::{
     WORKSPACE_DB,
 };
 
-actions!(OpenRecent);
+pub use projects::OpenRecent;
 
 pub fn init(cx: &mut AppContext) {
     cx.observe_new_views(RecentProjects::register).detach();

crates/terminal2/src/terminal2.rs 🔗

@@ -50,7 +50,7 @@ use std::{
 use thiserror::Error;
 
 use gpui::{
-    px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
+    actions, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
     ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
     Point, ScrollWheelEvent, Size, Task, TouchPhase,
 };
@@ -58,6 +58,16 @@ use gpui::{
 use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
 use lazy_static::lazy_static;
 
+actions!(
+    Clear,
+    Copy,
+    Paste,
+    ShowCharacterPalette,
+    SearchTest,
+    SendText,
+    SendKeystroke,
+);
+
 ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
 ///Scroll multiplier that is set to 3 by default. This will be removed when I
 ///Implement scroll bars.

crates/terminal_view2/src/terminal_element.rs 🔗

@@ -1,9 +1,9 @@
 use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext,
-    AvailableSpace, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle,
-    FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement,
-    LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
+    AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
+    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,
 };
@@ -643,13 +643,11 @@ impl TerminalElement {
                 let connection = connection.clone();
                 let focus = focus.clone();
                 move |e, cx| {
-                    if e.pressed_button.is_some() {
-                        if focus.is_focused(cx) {
-                            connection.update(cx, |terminal, cx| {
-                                terminal.mouse_drag(e, origin, bounds);
-                                cx.notify();
-                            })
-                        }
+                    if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
+                        connection.update(cx, |terminal, cx| {
+                            terminal.mouse_drag(e, origin, bounds);
+                            cx.notify();
+                        })
                     }
                 }
             })
@@ -806,7 +804,28 @@ impl Element for TerminalElement {
                 .map(|cursor| cursor.bounding_rect(origin)),
         };
 
-        let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx);
+        let terminal_focus_handle = self.focus.clone();
+        let terminal_handle = self.terminal.clone();
+        let mut this: TerminalElement = self
+            .register_mouse_listeners(origin, layout.mode, bounds, cx)
+            .drag_over::<ExternalPaths>(|style| {
+                // todo!() why does not it work? z-index of elements?
+                style.bg(cx.theme().colors().ghost_element_hover)
+            })
+            .on_drop::<ExternalPaths>(move |external_paths, cx| {
+                cx.focus(&terminal_focus_handle);
+                let mut new_text = external_paths
+                    .read(cx)
+                    .paths()
+                    .iter()
+                    .map(|path| format!(" {path:?}"))
+                    .join("");
+                new_text.push(' ');
+                terminal_handle.update(cx, |terminal, _| {
+                    // todo!() long paths are not displayed properly albeit the text is there
+                    terminal.paste(&new_text);
+                });
+            });
 
         let interactivity = mem::take(&mut this.interactivity);
 

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -9,9 +9,9 @@ pub mod terminal_panel;
 // use crate::terminal_element::TerminalElement;
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions, 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, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, Focusable,
+    FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton,
+    MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView,
 };
 use language::Bias;
 use persistence::TERMINAL_DB;
@@ -22,7 +22,7 @@ use terminal::{
         term::{search::RegexSearch, TermMode},
     },
     terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
-    Event, MaybeNavigationTarget, Terminal,
+    Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
 };
 use terminal_element::TerminalElement;
 use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
@@ -60,8 +60,6 @@ pub struct SendText(String);
 #[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)]
 pub struct SendKeystroke(String);
 
-actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
-
 pub fn init(cx: &mut AppContext) {
     terminal_panel::init(cx);
     terminal::init(cx);

crates/workspace2/src/dock.rs 🔗

@@ -133,13 +133,13 @@ pub struct Dock {
     panel_entries: Vec<PanelEntry>,
     is_open: bool,
     active_panel_index: usize,
+    focus_handle: FocusHandle,
+    focus_subscription: Subscription,
 }
 
 impl FocusableView for Dock {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.panel_entries[self.active_panel_index]
-            .panel
-            .focus_handle(cx)
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
     }
 }
 
@@ -190,12 +190,20 @@ pub struct PanelButtons {
 }
 
 impl Dock {
-    pub fn new(position: DockPosition) -> Self {
+    pub fn new(position: DockPosition, cx: &mut ViewContext<'_, Self>) -> Self {
+        let focus_handle = cx.focus_handle();
+        let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
+            if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
+                active_entry.panel.focus_handle(cx).focus(cx)
+            }
+        });
         Self {
             position,
             panel_entries: Default::default(),
             active_panel_index: 0,
             is_open: false,
+            focus_handle,
+            focus_subscription,
         }
     }
 
@@ -207,6 +215,7 @@ impl Dock {
         self.is_open
     }
 
+    // todo!()
     //     pub fn has_focus(&self, cx: &WindowContext) -> bool {
     //         self.visible_panel()
     //             .map_or(false, |panel| panel.has_focus(cx))

crates/workspace2/src/workspace2.rs 🔗

@@ -566,9 +566,9 @@ impl Workspace {
 
         cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 
-        let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
-        let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
-        let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
+        let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx));
+        let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx));
+        let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx));
         let left_dock_buttons =
             cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
         let bottom_dock_buttons =