Fix mouse scroll in terminal items

K Simmons created

Change summary

crates/terminal/src/mappings/mouse.rs   |  3 
crates/terminal/src/terminal.rs         | 19 +++-
crates/terminal/src/terminal_element.rs | 26 +++----
crates/workspace/src/dock.rs            | 99 +++++++++++++++++++++++++++
4 files changed, 124 insertions(+), 23 deletions(-)

Detailed changes

crates/terminal/src/mappings/mouse.rs 🔗

@@ -6,6 +6,7 @@ use alacritty_terminal::grid::Dimensions;
 /// with modifications for our circumstances
 use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side};
 use alacritty_terminal::term::TermMode;
+use gpui::scene::ScrollWheelRegionEvent;
 use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
 
 use crate::TerminalSize;
@@ -114,7 +115,7 @@ impl MouseButton {
 pub fn scroll_report(
     point: Point,
     scroll_lines: i32,
-    e: &ScrollWheelEvent,
+    e: &ScrollWheelRegionEvent,
     mode: TermMode,
 ) -> Option<impl Iterator<Item = Vec<u8>>> {
     if mode.intersects(TermMode::MOUSE_MODE) {

crates/terminal/src/terminal.rs 🔗

@@ -49,9 +49,10 @@ use thiserror::Error;
 use gpui::{
     geometry::vector::{vec2f, Vector2F},
     keymap::Keystroke,
-    scene::{ClickRegionEvent, DownRegionEvent, DragRegionEvent, UpRegionEvent},
-    ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, MutableAppContext,
-    ScrollWheelEvent, Task,
+    scene::{
+        ClickRegionEvent, DownRegionEvent, DragRegionEvent, ScrollWheelRegionEvent, UpRegionEvent,
+    },
+    ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, MutableAppContext, Task,
 };
 
 use crate::mappings::{
@@ -904,10 +905,10 @@ impl Terminal {
     }
 
     ///Scroll the terminal
-    pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Vector2F) {
+    pub fn scroll_wheel(&mut self, e: ScrollWheelRegionEvent, origin: Vector2F) {
         let mouse_mode = self.mouse_mode(e.shift);
 
-        if let Some(scroll_lines) = self.determine_scroll_lines(e, mouse_mode) {
+        if let Some(scroll_lines) = self.determine_scroll_lines(&e, mouse_mode) {
             if mouse_mode {
                 let point = mouse_point(
                     e.position.sub(origin),
@@ -916,7 +917,7 @@ impl Terminal {
                 );
 
                 if let Some(scrolls) =
-                    scroll_report(point, scroll_lines as i32, e, self.last_content.mode)
+                    scroll_report(point, scroll_lines as i32, &e, self.last_content.mode)
                 {
                     for scroll in scrolls {
                         self.pty_tx.notify(scroll);
@@ -939,7 +940,11 @@ impl Terminal {
         }
     }
 
-    fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option<i32> {
+    fn determine_scroll_lines(
+        &mut self,
+        e: &ScrollWheelRegionEvent,
+        mouse_mode: bool,
+    ) -> Option<i32> {
         let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER };
 
         match e.phase {

crates/terminal/src/terminal_element.rs 🔗

@@ -427,7 +427,14 @@ impl TerminalElement {
                         position: e.position,
                     });
                 }
-            });
+            })
+            .on_scroll(TerminalElement::generic_button_handler(
+                connection,
+                origin,
+                move |terminal, origin, e, _cx| {
+                    terminal.scroll_wheel(e, origin);
+                },
+            ));
 
         // Mouse mode handlers:
         // All mouse modes need the extra click handlers
@@ -742,24 +749,13 @@ impl Element for TerminalElement {
     fn dispatch_event(
         &mut self,
         event: &gpui::Event,
-        bounds: gpui::geometry::rect::RectF,
-        visible_bounds: gpui::geometry::rect::RectF,
-        layout: &mut Self::LayoutState,
+        _bounds: gpui::geometry::rect::RectF,
+        _visible_bounds: gpui::geometry::rect::RectF,
+        _layout: &mut Self::LayoutState,
         _paint: &mut Self::PaintState,
         cx: &mut gpui::EventContext,
     ) -> bool {
         match event {
-            Event::ScrollWheel(e) => visible_bounds
-                .contains_point(e.position)
-                .then(|| {
-                    let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
-
-                    if let Some(terminal) = self.terminal.upgrade(cx.app) {
-                        terminal.update(cx.app, |term, _| term.scroll_wheel(e, origin));
-                        cx.notify();
-                    }
-                })
-                .is_some(),
             Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
                 if !cx.is_parent_view_focused() {
                     return false;

crates/workspace/src/dock.rs 🔗

@@ -369,3 +369,102 @@ impl StatusItemView for ToggleDockButton {
         //Not applicable
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use gpui::{TestAppContext, ViewContext};
+    use project::{FakeFs, Project};
+    use settings::Settings;
+
+    use crate::{tests::TestItem, ItemHandle, Workspace};
+
+    pub fn default_item_factory(
+        _workspace: &mut Workspace,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Box<dyn ItemHandle> {
+        Box::new(cx.add_view(|_| TestItem::new()))
+    }
+
+    #[gpui::test]
+    async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) {
+        cx.foreground().forbid_parking();
+
+        Settings::test_async(cx);
+        let fs = FakeFs::new(cx.background());
+
+        let project = Project::test(fs, [], cx).await;
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
+
+        // Open dock
+        workspace.update(cx, |workspace, cx| {
+            Dock::show(workspace, cx);
+        });
+
+        // Ensure dock has an item in it
+        let dock_item_handle = workspace.read_with(cx, |workspace, cx| {
+            let dock = workspace.dock_pane().read(cx);
+            dock.items()
+                .next()
+                .expect("Dock should have an item in it")
+                .clone()
+        });
+
+        // Close item
+        let close_task = workspace.update(cx, |workspace, cx| {
+            Pane::close_item(
+                workspace,
+                workspace.dock_pane().clone(),
+                dock_item_handle.id(),
+                cx,
+            )
+        });
+        close_task.await.expect("Dock item closed successfully");
+
+        // Ensure dock closes
+        workspace.read_with(cx, |workspace, cx| {
+            assert!(workspace.dock.visible_pane().is_some())
+        });
+
+        // Open again
+        workspace.update(cx, |workspace, cx| {
+            Dock::show(workspace, cx);
+        });
+
+        // Ensure dock has item in it
+        workspace.read_with(cx, |workspace, cx| {
+            let dock = workspace.dock_pane().read(cx);
+            dock.items().next().expect("Dock should have an item in it");
+        });
+    }
+
+    #[gpui::test]
+    async fn test_dock_panel_collisions(cx: &mut TestAppContext) {
+        // Open dock expanded
+        // Open left panel
+        // Ensure dock closes
+        // Open dock to the right
+        // Open left panel
+        // Ensure dock is left open
+        // Open right panel
+        // Ensure dock closes
+        // Open dock bottom
+        // Open left panel
+        // Open right panel
+        // Ensure dock still open
+    }
+
+    #[gpui::test]
+    async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) {
+        // Open item in center pane
+        // Open dock expanded
+        // Focus new item
+        // Ensure the dock gets hidden
+        // Open dock to the right
+        // Focus new item
+        // Ensure dock stays shown but inactive
+        // Add item to dock and hide it
+        // Focus the added item
+        // Ensure the dock is open
+    }
+}