Merge pull request #1556 from zed-industries/term-scroll-to-edge

Mikayla Maki created

Added dragging and scrolling integration in terminal

Change summary

crates/editor/src/element.rs            |   2 
crates/terminal/src/mappings/mouse.rs   |  11 -
crates/terminal/src/terminal.rs         | 159 +++++++++++++++++---------
crates/terminal/src/terminal_element.rs |   4 
4 files changed, 111 insertions(+), 65 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -2006,7 +2006,7 @@ impl HighlightedRange {
     }
 }
 
-fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
+pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
     delta.powf(1.5) / 100.0
 }
 

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

@@ -33,12 +33,11 @@ impl Modifiers {
         }
     }
 
-    //TODO: Determine if I should add modifiers into the ScrollWheelEvent type
-    fn from_scroll() -> Self {
+    fn from_scroll(scroll: &ScrollWheelEvent) -> Self {
         Modifiers {
-            ctrl: false,
-            shift: false,
-            alt: false,
+            ctrl: scroll.ctrl,
+            shift: scroll.shift,
+            alt: scroll.alt,
         }
     }
 }
@@ -123,7 +122,7 @@ pub fn scroll_report(
             point,
             MouseButton::from_scroll(e),
             true,
-            Modifiers::from_scroll(),
+            Modifiers::from_scroll(e),
             MouseFormat::from_mode(mode),
         )
         .map(|report| repeat(report).take(max(scroll_lines, 1) as usize))

crates/terminal/src/terminal.rs 🔗

@@ -18,7 +18,6 @@ use alacritty_terminal::{
     Term,
 };
 use anyhow::{bail, Result};
-
 use futures::{
     channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
     FutureExt,
@@ -29,11 +28,21 @@ use mappings::mouse::{
 };
 use modal::deploy_modal;
 use settings::{AlternateScroll, Settings, Shell, TerminalBlink};
-use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration};
+use std::{
+    collections::{HashMap, VecDeque},
+    fmt::Display,
+    ops::Sub,
+    path::PathBuf,
+    sync::Arc,
+    time::Duration,
+};
 use thiserror::Error;
 
 use gpui::{
-    geometry::vector::{vec2f, Vector2F},
+    geometry::{
+        rect::RectF,
+        vector::{vec2f, Vector2F},
+    },
     keymap::Keystroke,
     ClipboardItem, Entity, ModelContext, MouseButton, MouseButtonEvent, MouseMovedEvent,
     MutableAppContext, ScrollWheelEvent,
@@ -82,7 +91,7 @@ enum InternalEvent {
     Clear,
     Scroll(Scroll),
     SetSelection(Option<Selection>),
-    UpdateSelection((Point, Direction)),
+    UpdateSelection(Vector2F),
     Copy,
 }
 
@@ -310,11 +319,8 @@ impl TerminalBuilder {
             term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor)
         }
 
-        //Start alternate_scroll if we need to
-        if let AlternateScroll::On = alternate_scroll {
-            term.set_mode(alacritty_terminal::ansi::Mode::AlternateScroll)
-        } else {
-            //Alacritty turns it on by default, so we need to turn it off.
+        //Alacritty defaults to alternate scrolling being on, so we just need to turn it off.
+        if let AlternateScroll::Off = alternate_scroll {
             term.unset_mode(alacritty_terminal::ansi::Mode::AlternateScroll)
         }
 
@@ -362,13 +368,14 @@ impl TerminalBuilder {
         let terminal = Terminal {
             pty_tx: Notifier(pty_tx),
             term,
-            events: vec![],
+            events: VecDeque::with_capacity(10), //Should never get this high.
             title: shell_txt.clone(),
             default_title: shell_txt,
             last_mode: TermMode::NONE,
             cur_size: initial_size,
             last_mouse: None,
             last_offset: 0,
+            current_selection: false,
         };
 
         Ok(TerminalBuilder {
@@ -433,13 +440,14 @@ impl TerminalBuilder {
 pub struct Terminal {
     pty_tx: Notifier,
     term: Arc<FairMutex<Term<ZedListener>>>,
-    events: Vec<InternalEvent>,
+    events: VecDeque<InternalEvent>,
     default_title: String,
     title: String,
     cur_size: TerminalSize,
     last_mode: TermMode,
     last_offset: usize,
     last_mouse: Option<(Point, Direction)>,
+    current_selection: bool,
 }
 
 impl Terminal {
@@ -479,9 +487,9 @@ impl Terminal {
                 cx.emit(Event::Wakeup);
                 cx.notify();
             }
-            AlacTermEvent::ColorRequest(_, _) => {
-                self.events.push(InternalEvent::TermEvent(event.clone()))
-            }
+            AlacTermEvent::ColorRequest(_, _) => self
+                .events
+                .push_back(InternalEvent::TermEvent(event.clone())),
         }
     }
 
@@ -513,11 +521,16 @@ impl Terminal {
                 self.write_to_pty("\x0c".to_string());
                 term.clear_screen(ClearMode::Saved);
             }
-            InternalEvent::Scroll(scroll) => term.scroll_display(*scroll),
+            InternalEvent::Scroll(scroll) => {
+                term.scroll_display(*scroll);
+            }
             InternalEvent::SetSelection(sel) => term.selection = sel.clone(),
-            InternalEvent::UpdateSelection((point, side)) => {
+            InternalEvent::UpdateSelection(position) => {
                 if let Some(mut selection) = term.selection.take() {
-                    selection.update(*point, *side);
+                    let point = mouse_point(*position, self.cur_size, term.grid().display_offset());
+                    let side = mouse_side(*position, self.cur_size);
+
+                    selection.update(point, side);
                     term.selection = Some(selection);
                 }
             }
@@ -530,24 +543,48 @@ impl Terminal {
         }
     }
 
-    pub fn input(&mut self, input: String) {
-        self.events.push(InternalEvent::Scroll(Scroll::Bottom));
-        self.events.push(InternalEvent::SetSelection(None));
-        self.write_to_pty(input);
+    fn begin_select(&mut self, sel: Selection) {
+        self.current_selection = true;
+        self.events
+            .push_back(InternalEvent::SetSelection(Some(sel)));
     }
 
-    ///Write the Input payload to the tty.
-    fn write_to_pty(&self, input: String) {
-        self.pty_tx.notify(input.into_bytes());
+    fn continue_selection(&mut self, location: Vector2F) {
+        self.events
+            .push_back(InternalEvent::UpdateSelection(location))
+    }
+
+    fn end_select(&mut self) {
+        self.current_selection = false;
+        self.events.push_back(InternalEvent::SetSelection(None));
+    }
+
+    fn scroll(&mut self, scroll: Scroll) {
+        self.events.push_back(InternalEvent::Scroll(scroll));
+    }
+
+    pub fn copy(&mut self) {
+        self.events.push_back(InternalEvent::Copy);
+    }
+
+    pub fn clear(&mut self) {
+        self.events.push_back(InternalEvent::Clear)
     }
 
     ///Resize the terminal and the PTY.
     pub fn set_size(&mut self, new_size: TerminalSize) {
-        self.events.push(InternalEvent::Resize(new_size))
+        self.events.push_back(InternalEvent::Resize(new_size))
     }
 
-    pub fn clear(&mut self) {
-        self.events.push(InternalEvent::Clear)
+    ///Write the Input payload to the tty.
+    fn write_to_pty(&self, input: String) {
+        self.pty_tx.notify(input.into_bytes());
+    }
+
+    pub fn input(&mut self, input: String) {
+        self.scroll(Scroll::Bottom);
+        self.end_select();
+        self.write_to_pty(input);
     }
 
     pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
@@ -570,10 +607,6 @@ impl Terminal {
         self.input(paste_text)
     }
 
-    pub fn copy(&mut self) {
-        self.events.push(InternalEvent::Copy);
-    }
-
     pub fn render_lock<F, T>(&mut self, cx: &mut ModelContext<Self>, f: F) -> T
     where
         F: FnOnce(RenderableContent, char) -> T,
@@ -581,7 +614,8 @@ impl Terminal {
         let m = self.term.clone(); //Arc clone
         let mut term = m.lock();
 
-        while let Some(e) = self.events.pop() {
+        //Note that this ordering matters for
+        while let Some(e) = self.events.pop_front() {
             self.process_terminal_event(&e, &mut term, cx)
         }
 
@@ -642,15 +676,32 @@ impl Terminal {
         }
     }
 
-    pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F) {
+    pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F, bounds: RectF) {
         let position = e.position.sub(origin);
 
         if !self.mouse_mode(e.shift) {
-            let point = mouse_point(position, self.cur_size, self.last_offset);
-            let side = mouse_side(position, self.cur_size);
+            // Alacritty has the same ordering, of first updating the selection
+            // then scrolling 15ms later
+            self.continue_selection(position);
+
+            // Doesn't make sense to scroll the alt screen
+            if !self.last_mode.contains(TermMode::ALT_SCREEN) {
+                //TODO: Why do these need to be doubled?
+                let top = bounds.origin_y() + (self.cur_size.line_height * 2.);
+                let bottom = bounds.lower_left().y() - (self.cur_size.line_height * 2.);
+
+                let scroll_delta = if e.position.y() < top {
+                    (top - e.position.y()).powf(1.1)
+                } else if e.position.y() > bottom {
+                    -((e.position.y() - bottom).powf(1.1))
+                } else {
+                    return; //Nothing to do
+                };
 
-            self.events
-                .push(InternalEvent::UpdateSelection((point, side)));
+                let scroll_lines = (scroll_delta / self.cur_size.line_height) as i32;
+                self.scroll(Scroll::Delta(scroll_lines));
+                self.continue_selection(position)
+            }
         }
     }
 
@@ -664,12 +715,7 @@ impl Terminal {
                 self.pty_tx.notify(bytes);
             }
         } else if e.button == MouseButton::Left {
-            self.events
-                .push(InternalEvent::SetSelection(Some(Selection::new(
-                    SelectionType::Simple,
-                    point,
-                    side,
-                ))));
+            self.begin_select(Selection::new(SelectionType::Simple, point, side));
         }
     }
 
@@ -691,7 +737,9 @@ impl Terminal {
             let selection =
                 selection_type.map(|selection_type| Selection::new(selection_type, point, side));
 
-            self.events.push(InternalEvent::SetSelection(selection));
+            if let Some(sel) = selection {
+                self.begin_select(sel);
+            }
         }
     }
 
@@ -711,17 +759,16 @@ impl Terminal {
     }
 
     ///Scroll the terminal
-    pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) {
-        if self.mouse_mode(scroll.shift) {
+    pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Vector2F) {
+        if self.mouse_mode(e.shift) {
             //TODO: Currently this only sends the current scroll reports as they come in. Alacritty
             //Sends the *entire* scroll delta on *every* scroll event, only resetting it when
             //The scroll enters 'TouchPhase::Started'. Do I need to replicate this?
             //This would be consistent with a scroll model based on 'distance from origin'...
-            let scroll_lines = (scroll.delta.y() / self.cur_size.line_height) as i32;
-            let point = mouse_point(scroll.position.sub(origin), self.cur_size, self.last_offset);
+            let scroll_lines = (e.delta.y() / self.cur_size.line_height) as i32;
+            let point = mouse_point(e.position.sub(origin), self.cur_size, self.last_offset);
 
-            if let Some(scrolls) = scroll_report(point, scroll_lines as i32, scroll, self.last_mode)
-            {
+            if let Some(scrolls) = scroll_report(point, scroll_lines as i32, e, self.last_mode) {
                 for scroll in scrolls {
                     self.pty_tx.notify(scroll);
                 }
@@ -729,19 +776,19 @@ impl Terminal {
         } else if self
             .last_mode
             .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
-            && !scroll.shift
+            && !e.shift
         {
             //TODO: See above TODO, also applies here.
-            let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
-                / self.cur_size.line_height) as i32;
+            let scroll_lines =
+                ((e.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) / self.cur_size.line_height) as i32;
 
             self.pty_tx.notify(alt_scroll(scroll_lines))
         } else {
-            let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
-                / self.cur_size.line_height) as i32;
+            let scroll_lines =
+                ((e.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) / self.cur_size.line_height) as i32;
             if scroll_lines != 0 {
                 let scroll = Scroll::Delta(scroll_lines);
-                self.events.push(InternalEvent::Scroll(scroll));
+                self.scroll(scroll);
             }
         }
     }

crates/terminal/src/terminal_element.rs 🔗

@@ -457,7 +457,7 @@ impl TerminalElement {
                 if cx.is_parent_view_focused() {
                     if let Some(conn_handle) = connection.upgrade(cx.app) {
                         conn_handle.update(cx.app, |terminal, cx| {
-                            terminal.mouse_drag(event, origin);
+                            terminal.mouse_drag(event, origin, visible_bounds);
                             cx.notify();
                         })
                     }
@@ -830,7 +830,7 @@ impl Element for TerminalElement {
                     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(e, origin));
+                        terminal.update(cx.app, |term, _| term.scroll_wheel(e, origin));
                         cx.notify();
                     }
                 })