From a0d0c84eee7cee4f2eac2ee7196d47cc95e450c1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 19:16:42 -0700 Subject: [PATCH 1/8] Begin mouse mode --- crates/terminal/src/terminal.rs | 71 +++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5254ffbe7fbcdf474249fa80fe46ebd1e3a30cf8..bca67919ae8fa448f4655480a985bbb061cd4d3b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -607,11 +607,6 @@ impl Terminal { f(content, cursor_text) } - ///Scroll the terminal - pub fn scroll(&mut self, scroll: Scroll) { - self.events.push(InternalEvent::Scroll(scroll)); - } - pub fn focus_in(&self) { if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { self.write_to_pty("\x1b[I".to_string()); @@ -624,34 +619,60 @@ impl Terminal { } } - pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { - let selection_type = match clicks { - 0 => return, //This is a release - 1 => Some(SelectionType::Simple), - 2 => Some(SelectionType::Semantic), - 3 => Some(SelectionType::Lines), - _ => None, - }; + ///Scroll the terminal + pub fn scroll(&mut self, scroll: Scroll) { + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } + + self.events.push(InternalEvent::Scroll(scroll)); + } - let selection = - selection_type.map(|selection_type| Selection::new(selection_type, point, side)); + pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } else { + let selection_type = match clicks { + 0 => return, //This is a release + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; + + let selection = + selection_type.map(|selection_type| Selection::new(selection_type, point, side)); + + self.events.push(InternalEvent::SetSelection(selection)); + } + } - self.events.push(InternalEvent::SetSelection(selection)); + pub fn mouse_move(&mut self, point: Point, side: Direction, clicks: usize) { + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } } pub fn drag(&mut self, point: Point, side: Direction) { - self.events - .push(InternalEvent::UpdateSelection((point, side))); + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } else { + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } } - ///TODO: Check if the mouse_down-then-click assumption holds, so this code works as expected pub fn mouse_down(&mut self, point: Point, side: Direction) { - self.events - .push(InternalEvent::SetSelection(Some(Selection::new( - SelectionType::Simple, - point, - side, - )))); + if self.last_mode.intersects(TermMode::MOUSE_MODE) { + //TODE: MOUSE MODE + } else { + self.events + .push(InternalEvent::SetSelection(Some(Selection::new( + SelectionType::Simple, + point, + side, + )))); + } } } From 37ca7a66589851872831e3edd79eda9d6d301817 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 17:37:24 -0700 Subject: [PATCH 2/8] Half way done with mouse reporting --- crates/gpui/src/presenter.rs | 21 +++ crates/gpui/src/scene/mouse_region.rs | 26 +++ crates/terminal/src/connected_el.rs | 183 +++++++++++++------ crates/terminal/src/mappings/keys.rs | 1 + crates/terminal/src/mappings/mod.rs | 1 + crates/terminal/src/mappings/mouse.rs | 248 ++++++++++++++++++++++++++ crates/terminal/src/terminal.rs | 116 +++++------- styles/package-lock.json | 1 - 8 files changed, 469 insertions(+), 128 deletions(-) create mode 100644 crates/terminal/src/mappings/mouse.rs diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 023e013d1393428752cd43ea45fc2f0c61fc101a..8e5a3030867d9f429409ac86dbc75d1b0c7e96ae 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -235,6 +235,7 @@ impl Presenter { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut invalidated_views = Vec::new(); let mut mouse_down_out_handlers = Vec::new(); + let mut mouse_moved_region = None; let mut mouse_down_region = None; let mut clicked_region = None; let mut dragged_region = None; @@ -290,6 +291,15 @@ impl Presenter { *prev_drag_position = *position; } + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(*position) { + invalidated_views.push(region.view_id); + mouse_moved_region = + Some((region.clone(), MouseRegionEvent::Move(e.clone()))); + break; + } + } + self.last_mouse_moved_event = Some(event.clone()); } _ => {} @@ -313,6 +323,17 @@ impl Presenter { } } + if let Some((move_moved_region, region_event)) = mouse_moved_region { + handled = true; + if let Some(mouse_moved_callback) = + move_moved_region.handlers.get(®ion_event.handler_key()) + { + event_cx.with_current_view(move_moved_region.view_id, |event_cx| { + mouse_moved_callback(region_event, event_cx); + }) + } + } + if let Some((clicked_region, region_event)) = clicked_region { handled = true; if let Some(click_callback) = diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 704567450a3a767592f151e7823a21b2dbfaa851..ba7173b4a1cef04f684abf02d0ccf23cde1ed59f 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -1,6 +1,7 @@ use std::{any::TypeId, mem::Discriminant, rc::Rc}; use collections::HashMap; + use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; @@ -97,6 +98,14 @@ impl MouseRegion { self.handlers = self.handlers.on_hover(handler); self } + + pub fn on_move( + mut self, + handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move(handler); + self + } } #[derive(Copy, Clone, Eq, PartialEq, Hash)] @@ -267,6 +276,23 @@ impl HandlerSet { })); self } + + pub fn on_move( + mut self, + handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::move_disc(), None), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Move(move_event)= region_event { + handler(move_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}", + region_event); + } + })); + self + } } #[derive(Debug)] diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index f21727af66eb69dcc2ed67c12054787f7660bdbb..48f58762b043c0fb239942cf275943ae582e6fa0 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -1,9 +1,12 @@ use alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, grid::{Dimensions, Scroll}, - index::{Column as GridCol, Line as GridLine, Point, Side}, + index::{Column as GridCol, Direction, Line as GridLine, Point, Side}, selection::SelectionRange, - term::cell::{Cell, Flags}, + term::{ + cell::{Cell, Flags}, + TermMode, + }, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ @@ -16,8 +19,9 @@ use gpui::{ }, json::json, text_layout::{Line, RunStyle}, - Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, - PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle, + Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent, + MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, + WeakViewHandle, }; use itertools::Itertools; use ordered_float::OrderedFloat; @@ -413,6 +417,32 @@ impl TerminalEl { } } + fn generic_button_handler( + connection: WeakModelHandle, + origin: Vector2F, + cur_size: TerminalSize, + display_offset: usize, + f: impl Fn(&mut Terminal, Point, Direction, MouseButtonEvent, &mut ModelContext), + ) -> impl Fn(MouseButtonEvent, &mut EventContext) { + move |event, cx| { + cx.focus_parent_view(); + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + event.position, + origin, + cur_size, + display_offset, + ); + + f(terminal, point, side, event, cx); + + cx.notify(); + }) + } + } + } + fn attach_mouse_handlers( &self, origin: Vector2F, @@ -422,77 +452,116 @@ impl TerminalEl { display_offset: usize, cx: &mut PaintContext, ) { - let mouse_down_connection = self.terminal; - let click_connection = self.terminal; - let drag_connection = self.terminal; + let connection = self.terminal; cx.scene.push_mouse_region( MouseRegion::new(view_id, None, visible_bounds) - .on_down( - MouseButton::Left, - move |MouseButtonEvent { position, .. }, cx| { - if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) { + .on_move(move |event, cx| { + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { let (point, side) = TerminalEl::mouse_to_cell_data( - position, + event.position, origin, cur_size, display_offset, ); - terminal.mouse_down(point, side); + terminal.mouse_move(point, side, &event); cx.notify(); }) } - }, + } + }) + .on_down( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, _e, _cx| { + terminal.mouse_down(point, side); + }, + ), ) + .on_down( + MouseButton::Right, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, _e, _cx| { + terminal.mouse_down(point, side); + }, + ), + ) + .on_down( + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, _e, _cx| { + terminal.mouse_down(point, side); + }, + ), + ) + //TODO .on_click( MouseButton::Left, - move |MouseButtonEvent { - position, - click_count, - .. - }, - cx| { - cx.focus_parent_view(); - if let Some(conn_handle) = click_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); - - terminal.click(point, side, click_count); - - cx.notify(); - }); - } - }, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, e, _cx| { + terminal.click(point, side, e.click_count); + }, + ), ) .on_click( - MouseButton::Right, - move |MouseButtonEvent { position, .. }, cx| { - cx.dispatch_action(DeployContextMenu { position }); - }, + MouseButton::Middle, + TerminalEl::generic_button_handler( + connection, + origin, + cur_size, + display_offset, + move |terminal, point, side, e, _cx| { + terminal.click(point, side, e.click_count); + }, + ), ) - .on_drag( - MouseButton::Left, - move |_, MouseMovedEvent { position, .. }, cx| { - if let Some(conn_handle) = drag_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); - - terminal.drag(point, side); - - cx.notify() + .on_click( + MouseButton::Right, + move |e @ MouseButtonEvent { position, .. }, cx| { + //Attempt to check the mode + if let Some(conn_handle) = connection.upgrade(cx.app) { + let handled = conn_handle.update(cx.app, |terminal, _cx| { + //Finally, we can check the mode! + if terminal.last_mode.intersects(TermMode::MOUSE_MODE) { + let (point, side) = TerminalEl::mouse_to_cell_data( + position, + origin, + cur_size, + display_offset, + ); + + terminal.click(point, side, e.click_count); + true + } else { + false + } }); + + //If I put this up by the true, then we're in the wrong 'cx' + if !handled { + cx.dispatch_action(DeployContextMenu { position }); + } + } else { + cx.dispatch_action(DeployContextMenu { position }); } }, ), @@ -811,12 +880,12 @@ impl Element for TerminalEl { }) => visible_bounds .contains_point(*position) .then(|| { - let vertical_scroll = + let scroll_lines = (delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER; if let Some(terminal) = self.terminal.upgrade(cx.app) { terminal.update(cx.app, |term, _| { - term.scroll(Scroll::Delta(vertical_scroll.round() as i32)) + term.scroll(Scroll::Delta(scroll_lines.round() as i32)) }); } diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 002759d78ddff932ea928706b7741a45ff4cb0f7..da730e62968d6e3d15ae860068b56aab2c55a22c 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -1,3 +1,4 @@ +/// The mappings defined in this file where created from reading the alacritty source use alacritty_terminal::term::TermMode; use gpui::keymap::Keystroke; diff --git a/crates/terminal/src/mappings/mod.rs b/crates/terminal/src/mappings/mod.rs index cde6c337eace0f35aeea83fc9e71cc0c98ab3807..d58dd27f96fd1a4a4055534ad241fee3d3ffb5eb 100644 --- a/crates/terminal/src/mappings/mod.rs +++ b/crates/terminal/src/mappings/mod.rs @@ -1,2 +1,3 @@ pub mod colors; pub mod keys; +pub mod mouse; diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs new file mode 100644 index 0000000000000000000000000000000000000000..dec67b68f72cd7a9378eb09064cddd9c0490e48a --- /dev/null +++ b/crates/terminal/src/mappings/mouse.rs @@ -0,0 +1,248 @@ +/// Most of the code, and specifically the constants, in this are copied from Alacritty, +/// with modifications for our circumstances +use alacritty_terminal::{index::Point, term::TermMode}; +use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; + +pub struct Modifiers { + ctrl: bool, + shift: bool, + alt: bool, +} + +impl Modifiers { + pub fn from_moved(e: &MouseMovedEvent) -> Self { + Modifiers { + ctrl: e.ctrl, + shift: e.shift, + alt: e.alt, + } + } + + pub fn from_button(e: &MouseButtonEvent) -> Self { + Modifiers { + ctrl: e.ctrl, + shift: e.shift, + alt: e.alt, + } + } + + //TODO: Determine if I should add modifiers into the ScrollWheelEvent type + pub fn from_scroll() -> Self { + Modifiers { + ctrl: false, + shift: false, + alt: false, + } + } +} + +pub enum MouseFormat { + SGR, + Normal(bool), +} + +impl MouseFormat { + pub fn from_mode(mode: TermMode) -> Self { + if mode.contains(TermMode::SGR_MOUSE) { + MouseFormat::SGR + } else if mode.contains(TermMode::UTF8_MOUSE) { + MouseFormat::Normal(true) + } else { + MouseFormat::Normal(false) + } + } +} + +pub enum MouseButton { + LeftButton = 0, + MiddleButton = 1, + RightButton = 2, + LeftMove = 32, + MiddleMove = 33, + RightMove = 34, + NoneMove = 35, + ScrollUp = 64, + ScrollDown = 65, + Other = 99, +} + +impl MouseButton { + pub fn from_move(e: &MouseMovedEvent) -> Self { + match e.pressed_button { + Some(b) => match b { + gpui::MouseButton::Left => MouseButton::LeftMove, + gpui::MouseButton::Middle => MouseButton::MiddleMove, + gpui::MouseButton::Right => MouseButton::RightMove, + gpui::MouseButton::Navigate(_) => MouseButton::Other, + }, + None => MouseButton::NoneMove, + } + } + + pub fn from_button(e: &MouseButtonEvent) -> Self { + match e.button { + gpui::MouseButton::Left => MouseButton::LeftButton, + gpui::MouseButton::Right => MouseButton::MiddleButton, + gpui::MouseButton::Middle => MouseButton::RightButton, + gpui::MouseButton::Navigate(_) => MouseButton::Other, + } + } + + pub fn from_scroll(e: &ScrollWheelEvent) -> Self { + if e.delta.y() > 0. { + MouseButton::ScrollUp + } else { + MouseButton::ScrollDown + } + } + + pub fn is_other(&self) -> bool { + match self { + MouseButton::Other => true, + _ => false, + } + } +} + +pub fn scroll_report( + point: Point, + scroll_lines: i32, + e: &ScrollWheelEvent, + mode: TermMode, +) -> Option>> { + if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { + if let Some(report) = mouse_report( + point, + MouseButton::from_scroll(e), + true, + Modifiers::from_scroll(), + MouseFormat::from_mode(mode), + ) { + let mut res = vec![]; + for _ in 0..scroll_lines.abs() { + res.push(report.clone()); + } + return Some(res); + } + } + + None +} + +pub fn mouse_button_report( + point: Point, + e: &MouseButtonEvent, + pressed: bool, + mode: TermMode, +) -> Option> { + let button = MouseButton::from_button(e); + if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) { + mouse_report( + point, + button, + pressed, + Modifiers::from_button(e), + MouseFormat::from_mode(mode), + ) + } else { + None + } +} + +pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option> { + let button = MouseButton::from_move(e); + if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { + mouse_report( + point, + button, + true, + Modifiers::from_moved(e), + MouseFormat::from_mode(mode), + ) + } else { + None + } +} + +///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode +fn mouse_report( + point: Point, + button: MouseButton, + pressed: bool, + modifiers: Modifiers, + format: MouseFormat, +) -> Option> { + if point.line < 0 { + return None; + } + + let mut mods = 0; + if modifiers.shift { + mods += 4; + } + if modifiers.alt { + mods += 8; + } + if modifiers.ctrl { + mods += 16; + } + + match format { + MouseFormat::SGR => { + Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes()) + } + MouseFormat::Normal(utf8) => { + if pressed { + normal_mouse_report(point, button as u8 + mods, utf8) + } else { + normal_mouse_report(point, 3 + mods, utf8) + } + } + } +} + +fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option> { + let Point { line, column } = point; + let max_point = if utf8 { 2015 } else { 223 }; + + if line >= max_point || column >= max_point { + return None; + } + + let mut msg = vec![b'\x1b', b'[', b'M', 32 + button]; + + let mouse_pos_encode = |pos: usize| -> Vec { + let pos = 32 + 1 + pos; + let first = 0xC0 + pos / 64; + let second = 0x80 + (pos & 63); + vec![first as u8, second as u8] + }; + + if utf8 && column >= 95 { + msg.append(&mut mouse_pos_encode(column.0)); + } else { + msg.push(32 + 1 + column.0 as u8); + } + + if utf8 && line >= 95 { + msg.append(&mut mouse_pos_encode(line.0 as usize)); + } else { + msg.push(32 + 1 + line.0 as u8); + } + + Some(msg) +} + +fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String { + let c = if pressed { 'M' } else { 'm' }; + + let msg = format!( + "\x1b[<{};{};{}{}", + button, + point.column + 1, + point.line + 1, + c + ); + + msg +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index bca67919ae8fa448f4655480a985bbb061cd4d3b..15227951956c8c4dd6f73ae077508fa0f8475da4 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -24,6 +24,7 @@ use futures::{ FutureExt, }; +use mappings::mouse::mouse_moved_report; use modal::deploy_modal; use settings::{Settings, Shell, TerminalBlink}; use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; @@ -32,7 +33,7 @@ use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - ClipboardItem, Entity, ModelContext, MutableAppContext, + ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext, }; use crate::mappings::{ @@ -49,11 +50,9 @@ pub fn init(cx: &mut MutableAppContext) { } const DEBUG_TERMINAL_WIDTH: f32 = 500.; -const DEBUG_TERMINAL_HEIGHT: f32 = 30.; //This needs to be wide enough that the CI & a local dev's prompt can fill the whole space. +const DEBUG_TERMINAL_HEIGHT: f32 = 30.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; -// const MAX_FRAME_RATE: f32 = 60.; -// const BACK_BUFFER_SIZE: usize = 5000; ///Upward flowing events, for changing the title and such #[derive(Clone, Copy, Debug)] @@ -348,7 +347,7 @@ impl TerminalBuilder { default_title: shell_txt, last_mode: TermMode::NONE, cur_size: initial_size, - // utilization: 0., + last_mouse: None, }; Ok(TerminalBuilder { @@ -406,27 +405,6 @@ impl TerminalBuilder { }) .detach(); - // //Render loop - // cx.spawn_weak(|this, mut cx| async move { - // loop { - // let utilization = match this.upgrade(&cx) { - // Some(this) => this.update(&mut cx, |this, cx| { - // cx.notify(); - // this.utilization() - // }), - // None => break, - // }; - - // let utilization = (1. - utilization).clamp(0.1, 1.); - // let delay = cx.background().timer(Duration::from_secs_f32( - // 1.0 / (Terminal::default_fps() * utilization), - // )); - - // delay.await; - // } - // }) - // .detach(); - self.terminal } } @@ -439,19 +417,10 @@ pub struct Terminal { title: String, cur_size: TerminalSize, last_mode: TermMode, - //Percentage, between 0 and 1 - // utilization: f32, + last_mouse: Option<(Point, Direction)>, } impl Terminal { - // fn default_fps() -> f32 { - // MAX_FRAME_RATE - // } - - // fn utilization(&self) -> f32 { - // self.utilization - // } - fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext) { match event { AlacTermEvent::Title(title) => { @@ -494,12 +463,6 @@ impl Terminal { } } - // fn process_events(&mut self, events: Vec, cx: &mut ModelContext) { - // for event in events.into_iter() { - // self.process_event(&event, cx); - // } - // } - ///Takes events from Alacritty and translates them to behavior on this view fn process_terminal_event( &mut self, @@ -507,7 +470,6 @@ impl Terminal { term: &mut Term, cx: &mut ModelContext, ) { - // TODO: Handle is_self_focused in subscription on terminal view match event { InternalEvent::TermEvent(term_event) => { if let AlacTermEvent::ColorRequest(index, format) = term_event { @@ -619,13 +581,46 @@ impl Terminal { } } - ///Scroll the terminal - pub fn scroll(&mut self, scroll: Scroll) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE + pub fn mouse_changed(&mut self, point: Point, side: Direction) -> bool { + match self.last_mouse { + Some((old_point, old_side)) => { + if old_point == point && old_side == side { + false + } else { + self.last_mouse = Some((point, side)); + true + } + } + None => { + self.last_mouse = Some((point, side)); + true + } } + } - self.events.push(InternalEvent::Scroll(scroll)); + /// Handle a mouse move, this is mutually exclusive with drag. + pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { + if self.mouse_changed(point, side) { + if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { + self.pty_tx.notify(bytes); + } + } else if matches!(e.pressed_button, Some(gpui::MouseButton::Left)) { + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } + } + + pub fn mouse_down(&mut self, point: Point, side: Direction) { + if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { + //TODE: MOUSE MODE + } else { + self.events + .push(InternalEvent::SetSelection(Some(Selection::new( + SelectionType::Simple, + point, + side, + )))); + } } pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { @@ -647,32 +642,13 @@ impl Terminal { } } - pub fn mouse_move(&mut self, point: Point, side: Direction, clicks: usize) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } - } - - pub fn drag(&mut self, point: Point, side: Direction) { + ///Scroll the terminal + pub fn scroll(&mut self, scroll: Scroll) { if self.last_mode.intersects(TermMode::MOUSE_MODE) { //TODE: MOUSE MODE - } else { - self.events - .push(InternalEvent::UpdateSelection((point, side))); } - } - pub fn mouse_down(&mut self, point: Point, side: Direction) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } else { - self.events - .push(InternalEvent::SetSelection(Some(Selection::new( - SelectionType::Simple, - point, - side, - )))); - } + self.events.push(InternalEvent::Scroll(scroll)); } } diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c84968a5c1a25ddac5fd3c21ba907353c6d..5499f1852cb4330467268dee6436b53589a90e9b 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From 04600d73fc85ec4ebede797aa09b90ea7d7e940d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 09:50:26 -0700 Subject: [PATCH 3/8] Moved back to the drag handler, selections work again --- crates/terminal/README.md | 5 +++- crates/terminal/src/connected_el.rs | 18 +++++++++++++- crates/terminal/src/connected_view.rs | 6 ++--- crates/terminal/src/terminal.rs | 34 ++++++++++++++++----------- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/crates/terminal/README.md b/crates/terminal/README.md index cdfdaffe854ef51533bd63f2bfb24199fd7b830c..272212a538a06766d254b6bbd675643af62e1175 100644 --- a/crates/terminal/README.md +++ b/crates/terminal/README.md @@ -10,7 +10,7 @@ The TerminalView struct abstracts over failed and successful terminals, passing #Input -There are currently 3 distinct paths for getting keystrokes to the terminal: +There are currently many distinct paths for getting keystrokes to the terminal: 1. Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the `try_keystroke()` method on Terminal @@ -18,3 +18,6 @@ There are currently 3 distinct paths for getting keystrokes to the terminal: 3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`. +4. Pasted text has a seperate pathway. + +Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the `.input()` API (which try_keystroke uses) so we have consistent input handling across the terminal \ No newline at end of file diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 48f58762b043c0fb239942cf275943ae582e6fa0..020c71f4219608bb2c6577f35333e190ab0ff2d1 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -473,6 +473,22 @@ impl TerminalEl { } } }) + .on_drag(MouseButton::Left, move |_prev, event, cx| { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + event.position, + origin, + cur_size, + display_offset, + ); + + terminal.mouse_drag(point, side); + + cx.notify(); + }) + } + }) .on_down( MouseButton::Left, TerminalEl::generic_button_handler( @@ -696,7 +712,7 @@ impl Element for TerminalEl { ( cells, - content.selection, + dbg!(content.selection), content.cursor, content.display_offset, cursor_text, diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 5b521748bb02b869d6d6688e76c8a6d6d8d0068b..703a54ba91e3419fb625bde968db1370f9096b2d 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -251,7 +251,8 @@ impl ConnectedView { ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { if let Some(item) = cx.read_from_clipboard() { - self.terminal.read(cx).paste(item.text()); + self.terminal + .update(cx, |terminal, _cx| terminal.paste(item.text())); } } @@ -359,8 +360,7 @@ impl View for ConnectedView { cx: &mut ViewContext, ) { self.terminal.update(cx, |terminal, _| { - terminal.write_to_pty(text.into()); - terminal.scroll(alacritty_terminal::grid::Scroll::Bottom); + terminal.input(text.into()); }); } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 15227951956c8c4dd6f73ae077508fa0f8475da4..37bf85e78f9dd312734c7975ab9527a38582e88a 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -508,8 +508,14 @@ impl Terminal { } } + pub fn input(&mut self, input: String) { + self.scroll(Scroll::Bottom); + self.events.push(InternalEvent::SetSelection(None)); + self.write_to_pty(input); + } + ///Write the Input payload to the tty. - pub fn write_to_pty(&self, input: String) { + fn write_to_pty(&self, input: String) { self.pty_tx.notify(input.into_bytes()); } @@ -525,8 +531,7 @@ impl Terminal { pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { let esc = to_esc_str(keystroke, &self.last_mode); if let Some(esc) = esc { - self.write_to_pty(esc); - self.scroll(Scroll::Bottom); + self.input(esc); true } else { false @@ -534,14 +539,13 @@ impl Terminal { } ///Paste text into the terminal - pub fn paste(&self, text: &str) { - if self.last_mode.contains(TermMode::BRACKETED_PASTE) { - self.write_to_pty("\x1b[200~".to_string()); - self.write_to_pty(text.replace('\x1b', "")); - self.write_to_pty("\x1b[201~".to_string()); + pub fn paste(&mut self, text: &str) { + let paste_text = if self.last_mode.contains(TermMode::BRACKETED_PASTE) { + format!("{}{}{}", "\x1b[200~", text.replace('\x1b', ""), "\x1b[201~") } else { - self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r")); - } + text.replace("\r\n", "\r").replace('\n', "\r") + }; + self.input(paste_text) } pub fn copy(&mut self) { @@ -598,18 +602,20 @@ impl Terminal { } } - /// Handle a mouse move, this is mutually exclusive with drag. + /// Handle a mouse move pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { if self.mouse_changed(point, side) { if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { self.pty_tx.notify(bytes); } - } else if matches!(e.pressed_button, Some(gpui::MouseButton::Left)) { - self.events - .push(InternalEvent::UpdateSelection((point, side))); } } + pub fn mouse_drag(&mut self, point: Point, side: Direction) { + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } + pub fn mouse_down(&mut self, point: Point, side: Direction) { if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { //TODE: MOUSE MODE From a806634b822f914969e49424ad6c29875d7dc3b7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 11:41:17 -0700 Subject: [PATCH 4/8] Handlers attached, things are looking good 2 go --- crates/terminal/src/connected_el.rs | 259 +++++++------------------- crates/terminal/src/mappings/mouse.rs | 120 +++++++++--- crates/terminal/src/terminal.rs | 127 ++++++++++--- 3 files changed, 263 insertions(+), 243 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 020c71f4219608bb2c6577f35333e190ab0ff2d1..e241cb5822f39579c840b9bf43df7b1d927db40e 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -1,26 +1,22 @@ use alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, - grid::{Dimensions, Scroll}, - index::{Column as GridCol, Direction, Line as GridLine, Point, Side}, + grid::Dimensions, + index::Point, selection::SelectionRange, - term::{ - cell::{Cell, Flags}, - TermMode, - }, + term::cell::{Cell, Flags}, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, - elements::*, fonts::{Properties, Style::Italic, TextStyle, Underline, Weight}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json::json, + serde_json::json, text_layout::{Line, RunStyle}, - Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent, - MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, + Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, + MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle, WeakViewHandle, }; use itertools::Itertools; @@ -29,12 +25,11 @@ use settings::Settings; use theme::TerminalStyle; use util::ResultExt; +use std::fmt::Debug; use std::{ - cmp::min, mem, ops::{Deref, Range}, }; -use std::{fmt::Debug, ops::Sub}; use crate::{ connected_view::{ConnectedView, DeployContextMenu}, @@ -42,11 +37,6 @@ use crate::{ Terminal, TerminalSize, }; -///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. -pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; - ///The information generated during layout that is nescessary for painting pub struct LayoutState { cells: Vec, @@ -56,7 +46,6 @@ pub struct LayoutState { background_color: Color, selection_color: Color, size: TerminalSize, - display_offset: usize, } #[derive(Debug)] @@ -420,22 +409,13 @@ impl TerminalEl { fn generic_button_handler( connection: WeakModelHandle, origin: Vector2F, - cur_size: TerminalSize, - display_offset: usize, - f: impl Fn(&mut Terminal, Point, Direction, MouseButtonEvent, &mut ModelContext), + f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext), ) -> impl Fn(MouseButtonEvent, &mut EventContext) { move |event, cx| { cx.focus_parent_view(); if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - event.position, - origin, - cur_size, - display_offset, - ); - - f(terminal, point, side, event, cx); + f(terminal, origin, event, cx); cx.notify(); }) @@ -448,8 +428,6 @@ impl TerminalEl { origin: Vector2F, view_id: usize, visible_bounds: RectF, - cur_size: TerminalSize, - display_offset: usize, cx: &mut PaintContext, ) { let connection = self.terminal; @@ -459,34 +437,20 @@ impl TerminalEl { if cx.is_parent_view_focused() { if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - event.position, - origin, - cur_size, - display_offset, - ); - - terminal.mouse_move(point, side, &event); - + terminal.mouse_move(&event, origin); cx.notify(); }) } } }) .on_drag(MouseButton::Left, move |_prev, event, cx| { - if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - event.position, - origin, - cur_size, - display_offset, - ); - - terminal.mouse_drag(point, side); - - cx.notify(); - }) + 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); + cx.notify(); + }) + } } }) .on_down( @@ -494,10 +458,8 @@ impl TerminalEl { TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, _e, _cx| { - terminal.mouse_down(point, side); + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); }, ), ) @@ -506,10 +468,8 @@ impl TerminalEl { TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, _e, _cx| { - terminal.mouse_down(point, side); + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); }, ), ) @@ -518,65 +478,61 @@ impl TerminalEl { TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, _e, _cx| { - terminal.mouse_down(point, side); + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); }, ), ) - //TODO - .on_click( + .on_up( MouseButton::Left, TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, e, _cx| { - terminal.click(point, side, e.click_count); + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); }, ), ) - .on_click( + .on_up( + MouseButton::Right, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + .on_up( MouseButton::Middle, TerminalEl::generic_button_handler( connection, origin, - cur_size, - display_offset, - move |terminal, point, side, e, _cx| { - terminal.click(point, side, e.click_count); + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + .on_click( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.left_click(&e, origin); }, ), ) .on_click( MouseButton::Right, move |e @ MouseButtonEvent { position, .. }, cx| { - //Attempt to check the mode - if let Some(conn_handle) = connection.upgrade(cx.app) { - let handled = conn_handle.update(cx.app, |terminal, _cx| { - //Finally, we can check the mode! - if terminal.last_mode.intersects(TermMode::MOUSE_MODE) { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); - - terminal.click(point, side, e.click_count); - true - } else { - false - } - }); - - //If I put this up by the true, then we're in the wrong 'cx' - if !handled { - cx.dispatch_action(DeployContextMenu { position }); - } + let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) } else { + //If we can't get the model handle, probably can't deploy the context menu + true + }; + if !mouse_mode { cx.dispatch_action(DeployContextMenu { position }); } }, @@ -615,47 +571,6 @@ impl TerminalEl { underline: Default::default(), } } - - pub fn mouse_to_cell_data( - pos: Vector2F, - origin: Vector2F, - cur_size: TerminalSize, - display_offset: usize, - ) -> (Point, alacritty_terminal::index::Direction) { - let pos = pos.sub(origin); - let point = { - let col = pos.x() / cur_size.cell_width; //TODO: underflow... - let col = min(GridCol(col as usize), cur_size.last_column()); - - let line = pos.y() / cur_size.line_height; - let line = min(line as i32, cur_size.bottommost_line().0); - - Point::new(GridLine(line - display_offset as i32), col) - }; - - //Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() - let side = { - let x = pos.0.x() as usize; - let cell_x = - x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize; - let half_cell_width = (cur_size.cell_width / 2.0) as usize; - - let additional_padding = - (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; - let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; - //Width: Pixels or columns? - if cell_x > half_cell_width - // Edge case when mouse leaves the window. - || x as f32 >= end_of_grid - { - Side::Right - } else { - Side::Left - } - }; - - (point, side) - } } impl Element for TerminalEl { @@ -712,7 +627,7 @@ impl Element for TerminalEl { ( cells, - dbg!(content.selection), + content.selection, content.cursor, content.display_offset, cursor_text, @@ -794,7 +709,6 @@ impl Element for TerminalEl { size: dimensions, rects, highlights, - display_offset, }, ) } @@ -813,14 +727,7 @@ impl Element for TerminalEl { let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse - self.attach_mouse_handlers( - origin, - self.view.id(), - visible_bounds, - layout.size, - layout.display_offset, - cx, - ); + self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, cx); cx.paint_layer(clip_bounds, |cx| { //Start with a background color @@ -884,28 +791,22 @@ impl Element for TerminalEl { fn dispatch_event( &mut self, event: &gpui::Event, - _bounds: gpui::geometry::rect::RectF, + 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(ScrollWheelEvent { - delta, position, .. - }) => visible_bounds - .contains_point(*position) + Event::ScrollWheel(e) => visible_bounds + .contains_point(e.position) .then(|| { - let scroll_lines = - (delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER; + 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(Scroll::Delta(scroll_lines.round() as i32)) - }); + terminal.update(cx.app, |term, _| term.scroll(e, origin)); + cx.notify(); } - - cx.notify(); }) .is_some(), Event::KeyDown(KeyDownEvent { keystroke, .. }) => { @@ -913,7 +814,6 @@ impl Element for TerminalEl { return false; } - //TODO Talk to keith about how to catch events emitted from an element. if let Some(view) = self.view.upgrade(cx.app) { view.update(cx.app, |view, cx| { view.clear_bel(cx); @@ -969,36 +869,3 @@ impl Element for TerminalEl { Some(layout.cursor.as_ref()?.bounding_rect(origin)) } } - -mod test { - - #[test] - fn test_mouse_to_selection() { - let term_width = 100.; - let term_height = 200.; - let cell_width = 10.; - let line_height = 20.; - let mouse_pos_x = 100.; //Window relative - let mouse_pos_y = 100.; //Window relative - let origin_x = 10.; - let origin_y = 20.; - - let cur_size = crate::connected_el::TerminalSize::new( - line_height, - cell_width, - gpui::geometry::vector::vec2f(term_width, term_height), - ); - - let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); - let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in - let (point, _) = - crate::connected_el::TerminalEl::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); - assert_eq!( - point, - alacritty_terminal::index::Point::new( - alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), - alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), - ) - ); - } -} diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index dec67b68f72cd7a9378eb09064cddd9c0490e48a..7c524ed2f934d2a499d895f06a8bc73699fb26c5 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -1,16 +1,23 @@ +use std::cmp::min; +use std::iter::repeat; + +use alacritty_terminal::grid::Dimensions; /// Most of the code, and specifically the constants, in this are copied from Alacritty, /// with modifications for our circumstances -use alacritty_terminal::{index::Point, term::TermMode}; -use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; +use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side}; +use alacritty_terminal::term::TermMode; +use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; + +use crate::TerminalSize; -pub struct Modifiers { +struct Modifiers { ctrl: bool, shift: bool, alt: bool, } impl Modifiers { - pub fn from_moved(e: &MouseMovedEvent) -> Self { + fn from_moved(e: &MouseMovedEvent) -> Self { Modifiers { ctrl: e.ctrl, shift: e.shift, @@ -18,7 +25,7 @@ impl Modifiers { } } - pub fn from_button(e: &MouseButtonEvent) -> Self { + fn from_button(e: &MouseButtonEvent) -> Self { Modifiers { ctrl: e.ctrl, shift: e.shift, @@ -27,7 +34,7 @@ impl Modifiers { } //TODO: Determine if I should add modifiers into the ScrollWheelEvent type - pub fn from_scroll() -> Self { + fn from_scroll() -> Self { Modifiers { ctrl: false, shift: false, @@ -36,13 +43,13 @@ impl Modifiers { } } -pub enum MouseFormat { +enum MouseFormat { SGR, Normal(bool), } impl MouseFormat { - pub fn from_mode(mode: TermMode) -> Self { + fn from_mode(mode: TermMode) -> Self { if mode.contains(TermMode::SGR_MOUSE) { MouseFormat::SGR } else if mode.contains(TermMode::UTF8_MOUSE) { @@ -53,7 +60,7 @@ impl MouseFormat { } } -pub enum MouseButton { +enum MouseButton { LeftButton = 0, MiddleButton = 1, RightButton = 2, @@ -67,7 +74,7 @@ pub enum MouseButton { } impl MouseButton { - pub fn from_move(e: &MouseMovedEvent) -> Self { + fn from_move(e: &MouseMovedEvent) -> Self { match e.pressed_button { Some(b) => match b { gpui::MouseButton::Left => MouseButton::LeftMove, @@ -79,7 +86,7 @@ impl MouseButton { } } - pub fn from_button(e: &MouseButtonEvent) -> Self { + fn from_button(e: &MouseButtonEvent) -> Self { match e.button { gpui::MouseButton::Left => MouseButton::LeftButton, gpui::MouseButton::Right => MouseButton::MiddleButton, @@ -88,7 +95,7 @@ impl MouseButton { } } - pub fn from_scroll(e: &ScrollWheelEvent) -> Self { + fn from_scroll(e: &ScrollWheelEvent) -> Self { if e.delta.y() > 0. { MouseButton::ScrollUp } else { @@ -96,7 +103,7 @@ impl MouseButton { } } - pub fn is_other(&self) -> bool { + fn is_other(&self) -> bool { match self { MouseButton::Other => true, _ => false, @@ -109,24 +116,31 @@ pub fn scroll_report( scroll_lines: i32, e: &ScrollWheelEvent, mode: TermMode, -) -> Option>> { +) -> Option>> { if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { - if let Some(report) = mouse_report( + mouse_report( point, MouseButton::from_scroll(e), true, Modifiers::from_scroll(), MouseFormat::from_mode(mode), - ) { - let mut res = vec![]; - for _ in 0..scroll_lines.abs() { - res.push(report.clone()); - } - return Some(res); - } + ) + .map(|report| repeat(report).take(scroll_lines as usize)) + } else { + None } +} - None +pub fn alt_scroll(scroll_lines: i32) -> Vec { + let cmd = if scroll_lines > 0 { b'A' } else { b'B' }; + + let mut content = Vec::with_capacity(scroll_lines as usize * 3); + for _ in 0..scroll_lines { + content.push(0x1b); + content.push(b'O'); + content.push(cmd); + } + content } pub fn mouse_button_report( @@ -164,6 +178,31 @@ pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> } } +pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::index::Direction { + let x = pos.0.x() as usize; + let cell_x = x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize; + let half_cell_width = (cur_size.cell_width / 2.0) as usize; + let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; + let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; + //Width: Pixels or columns? + if cell_x > half_cell_width + // Edge case when mouse leaves the window. + || x as f32 >= end_of_grid + { + Side::Right + } else { + Side::Left + } +} + +pub fn mouse_point(pos: Vector2F, cur_size: TerminalSize, display_offset: usize) -> Point { + let col = pos.x() / cur_size.cell_width; + let col = min(GridCol(col as usize), cur_size.last_column()); + let line = pos.y() / cur_size.line_height; + let line = min(line as i32, cur_size.bottommost_line().0); + Point::new(GridLine(line - display_offset as i32), col) +} + ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode fn mouse_report( point: Point, @@ -246,3 +285,38 @@ fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String { msg } + +#[cfg(test)] +mod test { + use crate::mappings::mouse::mouse_point; + + #[test] + fn test_mouse_to_selection() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = crate::TerminalSize::new( + line_height, + cell_width, + gpui::geometry::vector::vec2f(term_width, term_height), + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let mouse_pos = mouse_pos - origin; + let point = mouse_point(mouse_pos, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 37bf85e78f9dd312734c7975ab9527a38582e88a..ff313478c5125172583365c981178950e1aa1114 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -24,16 +24,19 @@ use futures::{ FutureExt, }; -use mappings::mouse::mouse_moved_report; +use mappings::mouse::{ + alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report, +}; use modal::deploy_modal; use settings::{Settings, Shell, TerminalBlink}; -use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; +use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration}; use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext, + ClipboardItem, Entity, ModelContext, MouseButtonEvent, MouseMovedEvent, MutableAppContext, + ScrollWheelEvent, }; use crate::mappings::{ @@ -49,6 +52,11 @@ pub fn init(cx: &mut MutableAppContext) { connected_view::init(cx); } +///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. +pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; + const DEBUG_TERMINAL_WIDTH: f32 = 500.; const DEBUG_TERMINAL_HEIGHT: f32 = 30.; const DEBUG_CELL_WIDTH: f32 = 5.; @@ -348,6 +356,7 @@ impl TerminalBuilder { last_mode: TermMode::NONE, cur_size: initial_size, last_mouse: None, + last_offset: 0, }; Ok(TerminalBuilder { @@ -417,6 +426,7 @@ pub struct Terminal { title: String, cur_size: TerminalSize, last_mode: TermMode, + last_offset: usize, last_mouse: Option<(Point, Direction)>, } @@ -509,7 +519,7 @@ impl Terminal { } pub fn input(&mut self, input: String) { - self.scroll(Scroll::Bottom); + self.events.push(InternalEvent::Scroll(Scroll::Bottom)); self.events.push(InternalEvent::SetSelection(None)); self.write_to_pty(input); } @@ -563,11 +573,12 @@ impl Terminal { self.process_terminal_event(&e, &mut term, cx) } - // self.utilization = Self::estimate_utilization(term.take_last_processed_bytes()); self.last_mode = *term.mode(); let content = term.renderable_content(); + self.last_offset = content.display_offset; + let cursor_text = term.grid()[content.cursor.point].c; f(content, cursor_text) @@ -602,23 +613,45 @@ impl Terminal { } } - /// Handle a mouse move - pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { - if self.mouse_changed(point, side) { + pub fn mouse_mode(&self, shift: bool) -> bool { + self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift + } + + pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) { + let position = e.position.sub(origin); + + let point = mouse_point(position, self.cur_size, self.last_offset); + let side = mouse_side(position, self.cur_size); + + if self.mouse_changed(point, side) && self.mouse_mode(e.shift) { if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) { self.pty_tx.notify(bytes); } } } - pub fn mouse_drag(&mut self, point: Point, side: Direction) { - self.events - .push(InternalEvent::UpdateSelection((point, side))); + pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F) { + 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); + + self.events + .push(InternalEvent::UpdateSelection((point, side))); + } } - pub fn mouse_down(&mut self, point: Point, side: Direction) { - if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { - //TODE: MOUSE MODE + pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) { + let position = e.position.sub(origin); + + let point = mouse_point(position, self.cur_size, self.last_offset); + let side = mouse_side(position, self.cur_size); + + if self.mouse_mode(e.shift) { + if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) { + self.pty_tx.notify(bytes); + } } else { self.events .push(InternalEvent::SetSelection(Some(Selection::new( @@ -629,11 +662,15 @@ impl Terminal { } } - pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE - } else { - let selection_type = match clicks { + pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) { + let position = e.position.sub(origin); + //TODO: Alt-click cursor position + + 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); + + let selection_type = match e.click_count { 0 => return, //This is a release 1 => Some(SelectionType::Simple), 2 => Some(SelectionType::Semantic), @@ -648,13 +685,55 @@ impl Terminal { } } - ///Scroll the terminal - pub fn scroll(&mut self, scroll: Scroll) { - if self.last_mode.intersects(TermMode::MOUSE_MODE) { - //TODE: MOUSE MODE + pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) { + let position = e.position.sub(origin); + + if self.mouse_mode(e.shift) { + let point = mouse_point(position, self.cur_size, self.last_offset); + + if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) { + self.pty_tx.notify(bytes); + } + } else { + // Seems pretty standard to automatically copy on mouse_up for terminals, + // so let's do that here + self.copy(); } + } - self.events.push(InternalEvent::Scroll(scroll)); + ///Scroll the terminal + pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) { + if self.mouse_mode(false) { + //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); + + if let Some(scrolls) = scroll_report(point, scroll_lines as i32, scroll, self.last_mode) + { + for scroll in scrolls { + self.pty_tx.notify(scroll); + } + }; + } else if self + .last_mode + .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) + { + //TODO: See above TODO, also applies here. + let scroll_lines = ((scroll.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; + if scroll_lines != 0 { + let scroll = Scroll::Delta(scroll_lines); + self.events.push(InternalEvent::Scroll(scroll)); + } + } } } From efd3247ce4b79386e6bc06e7dcae93754d7fe875 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 12:10:12 -0700 Subject: [PATCH 5/8] Added modifiers to scroll wheel eevent --- crates/editor/src/element.rs | 1 + crates/gpui/src/elements/flex.rs | 1 + crates/gpui/src/elements/list.rs | 1 + crates/gpui/src/elements/uniform_list.rs | 1 + crates/gpui/src/platform/event.rs | 4 ++++ crates/gpui/src/platform/mac/event.rs | 6 ++++++ crates/terminal/src/terminal.rs | 3 ++- 7 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c935df7d07a47479b032cc16aad0d585c04a3c2a..1881d14842fe02cb37b89419e3ac6cf5f75f983e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1582,6 +1582,7 @@ impl Element for EditorElement { position, delta, precise, + .. }) => self.scroll(*position, *delta, *precise, layout, paint, cx), &Event::ModifiersChanged(event) => self.modifiers_changed(event, cx), diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 1d577344c6e387e100687ab229c047486dc7e472..798fb3e8c0106b3d5ec9dc7d2578418721b38c36 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -293,6 +293,7 @@ impl Element for Flex { position, delta, precise, + .. }) = event { if *remaining_space < 0. && bounds.contains_point(position) { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e0ca47b598e2cd20c82917bc3d56f28dcd54703d..d3c15c4e2b73bd167e450020258581173733e745 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -316,6 +316,7 @@ impl Element for List { position, delta, precise, + .. }) = event { if bounds.contains_point(*position) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index bf9a0a4e3024808fcc9039c5cfe6f3f563dc8f2a..103cb00d8cc1f46d77d85928e95aea666c589bed 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -315,6 +315,7 @@ impl Element for UniformList { position, delta, precise, + .. }) = event { if bounds.contains_point(*position) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 6ac75926be426e2a7512d35ea9c06cd5c7f64047..010fc9a7336c344413a9218c2a02abf428712a83 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -24,6 +24,10 @@ pub struct ScrollWheelEvent { pub position: Vector2F, pub delta: Vector2F, pub precise: bool, + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, } #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index a53e00958905f3d1caf3cc1a3c06086bf4d9022e..c6f838b431ac232463501be6f21e1ad8aea38e25 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -148,6 +148,8 @@ impl Event { }) } NSEventType::NSScrollWheel => window_height.map(|window_height| { + let modifiers = native_event.modifierFlags(); + Self::ScrollWheel(ScrollWheelEvent { position: vec2f( native_event.locationInWindow().x as f32, @@ -158,6 +160,10 @@ impl Event { native_event.scrollingDeltaY() as f32, ), precise: native_event.hasPreciseScrollingDeltas() == YES, + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), }) }), NSEventType::NSLeftMouseDragged diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ff313478c5125172583365c981178950e1aa1114..ed77b0070746c5c0756af3b25a453eda8ac833bd 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -703,7 +703,7 @@ impl Terminal { ///Scroll the terminal pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) { - if self.mouse_mode(false) { + if self.mouse_mode(scroll.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? @@ -720,6 +720,7 @@ impl Terminal { } else if self .last_mode .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) + && !scroll.shift { //TODO: See above TODO, also applies here. let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) From a279f83cd643063408d957f043b931318ea57355 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 13:03:17 -0700 Subject: [PATCH 6/8] Finished majority of mouse work. Need to add a setting and change up mouse handlers, and it'll be done. --- crates/terminal/src/terminal.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index ed77b0070746c5c0756af3b25a453eda8ac833bd..14e368b29d7ed2445c2bda916141a9afedec89fd 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -664,7 +664,6 @@ impl Terminal { pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) { let position = e.position.sub(origin); - //TODO: Alt-click cursor position if !self.mouse_mode(e.shift) { let point = mouse_point(position, self.cur_size, self.last_offset); From cfbda00cc4283f44b36ea78dacf2f3070a1d9968 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 13:54:25 -0700 Subject: [PATCH 7/8] Finished mouse compatability --- assets/settings/default.json | 62 ++++++++++++++++----------- crates/settings/src/settings.rs | 14 ++++++ crates/terminal/src/mappings/mouse.rs | 4 +- crates/terminal/src/terminal.rs | 11 ++++- crates/terminal/src/terminal_view.rs | 16 ++++++- 5 files changed, 77 insertions(+), 30 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 739b34d743fbac5596ac0c4b2acd7ddf89f951d1..8ea515e7a988027f0f60d072ac306cd53ef73ea6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,27 +102,37 @@ // // "working_directory": "current_project_directory", - //Set the cursor blinking behavior in the terminal. - //May take 4 values: - // 1. Never blink the cursor, ignoring the terminal mode - // "blinking": "off", - // 2. Default the cursor blink to off, but allow the terminal to - // set blinking - // "blinking": "terminal_controlled", - // 3. Always blink the cursor, ignoring the terminal mode - // "blinking": "on", + // Set the cursor blinking behavior in the terminal. + // May take 4 values: + // 1. Never blink the cursor, ignoring the terminal mode + // "blinking": "off", + // 2. Default the cursor blink to off, but allow the terminal to + // set blinking + // "blinking": "terminal_controlled", + // 3. Always blink the cursor, ignoring the terminal mode + // "blinking": "on", "blinking": "terminal_controlled", - //Any key-value pairs added to this list will be added to the terminal's - //enviroment. Use `:` to seperate multiple values. + // Set whether Alternate Scroll mode (code: ?1007) is active by default. + // Alternate Scroll mode converts mouse scroll events into up / down key + // presses when in the alternate screen (e.g. when running applications + // like vim or less). The terminal can still set and unset this mode. + // May take 2 values: + // 1. Default alternate scroll mode to on + // "alternate_scroll": "on", + // 2. Default alternate scroll mode to off + // "alternate_scroll": "off", + "alternate_scroll": "off", + // Any key-value pairs added to this list will be added to the terminal's + // enviroment. Use `:` to seperate multiple values. "env": { - //"KEY": "value1:value2" + // "KEY": "value1:value2" } - //Set the terminal's font size. If this option is not included, - //the terminal will default to matching the buffer's font size. - //"font_size": "15" - //Set the terminal's font family. If this option is not included, - //the terminal will default to matching the buffer's font family. - //"font_family": "Zed Mono" + // Set the terminal's font size. If this option is not included, + // the terminal will default to matching the buffer's font size. + // "font_size": "15" + // Set the terminal's font family. If this option is not included, + // the terminal will default to matching the buffer's font family. + // "font_family": "Zed Mono" }, // Different settings for specific languages. "languages": { @@ -155,15 +165,15 @@ "tab_size": 2 } }, - //LSP Specific settings. + // LSP Specific settings. "lsp": { - //Specify the LSP name as a key here. - //As of 8/10/22, supported LSPs are: - //pyright - //gopls - //rust-analyzer - //typescript-language-server - //vscode-json-languageserver + // Specify the LSP name as a key here. + // As of 8/10/22, supported LSPs are: + // pyright + // gopls + // rust-analyzer + // typescript-language-server + // vscode-json-languageserver // "rust_analyzer": { // //These initialization options are merged into Zed's defaults // "initialization_options": { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9defb6f41094d95ba3e8f31126664a1c5194bc40..2b97cb0b106682f7909dab904f7091796ccb9a6e 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -84,6 +84,7 @@ pub struct TerminalSettings { pub font_family: Option, pub env: Option>, pub blinking: Option, + pub alternate_scroll: Option, } #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] @@ -114,6 +115,19 @@ impl Default for Shell { } } +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AlternateScroll { + On, + Off, +} + +impl Default for AlternateScroll { + fn default() -> Self { + AlternateScroll::On + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectory { diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 7c524ed2f934d2a499d895f06a8bc73699fb26c5..9cd1768b4781d61efdb8d106f682063305cc560c 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -134,8 +134,8 @@ pub fn scroll_report( pub fn alt_scroll(scroll_lines: i32) -> Vec { let cmd = if scroll_lines > 0 { b'A' } else { b'B' }; - let mut content = Vec::with_capacity(scroll_lines as usize * 3); - for _ in 0..scroll_lines { + let mut content = Vec::with_capacity(scroll_lines.abs() as usize * 3); + for _ in 0..scroll_lines.abs() { content.push(0x1b); content.push(b'O'); content.push(cmd); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 14e368b29d7ed2445c2bda916141a9afedec89fd..a3aa0f06013b901b972c00238eeb9a9363d2551c 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -28,7 +28,7 @@ use mappings::mouse::{ alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report, }; use modal::deploy_modal; -use settings::{Settings, Shell, TerminalBlink}; +use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration}; use thiserror::Error; @@ -263,6 +263,7 @@ impl TerminalBuilder { env: Option>, initial_size: TerminalSize, blink_settings: Option, + alternate_scroll: &AlternateScroll, ) -> Result { let pty_config = { let alac_shell = shell.clone().and_then(|shell| match shell { @@ -306,6 +307,14 @@ 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. + term.unset_mode(alacritty_terminal::ansi::Mode::AlternateScroll) + } + let term = Arc::new(FairMutex::new(term)); //Setup the pty... diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index a997ebf6316f353ccbe3e34a0633be74f9d42acb..8041eafb9c43b52a901681c83be9ad3f2d7baf05 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -10,7 +10,7 @@ use workspace::{Item, Workspace}; use crate::TerminalSize; use project::{LocalWorktree, Project, ProjectPath}; -use settings::{Settings, WorkingDirectory}; +use settings::{AlternateScroll, Settings, WorkingDirectory}; use smallvec::SmallVec; use std::path::{Path, PathBuf}; @@ -94,12 +94,26 @@ impl TerminalView { let shell = settings.terminal_overrides.shell.clone(); let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap. + //TODO: move this pattern to settings + let scroll = settings + .terminal_overrides + .alternate_scroll + .as_ref() + .unwrap_or( + settings + .terminal_defaults + .alternate_scroll + .as_ref() + .unwrap_or_else(|| &AlternateScroll::On), + ); + let content = match TerminalBuilder::new( working_directory.clone(), shell, envs, size_info, settings.terminal_overrides.blinking.clone(), + scroll, ) { Ok(terminal) => { let terminal = cx.add_model(|cx| terminal.subscribe(cx)); From 1de68a724c22b144fb14d76ffb67bb32078822db Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 Aug 2022 15:11:49 -0700 Subject: [PATCH 8/8] Debugged mouse reporting for now. Remaining bugs are GPUI level bugs --- crates/gpui/src/presenter.rs | 21 ++++ crates/terminal/src/connected_el.rs | 164 +++++++++++++++----------- crates/terminal/src/mappings/mouse.rs | 28 +++-- crates/terminal/src/terminal.rs | 11 +- 4 files changed, 139 insertions(+), 85 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 0075581d365d54b3b6350f4214474d8bfc262efc..6836d157fe117ea22d1caf26e54922d2033e0347 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -237,6 +237,7 @@ impl Presenter { let mut mouse_down_out_handlers = Vec::new(); let mut mouse_moved_region = None; let mut mouse_down_region = None; + let mut mouse_up_region = None; let mut clicked_region = None; let mut dragged_region = None; @@ -283,6 +284,15 @@ impl Presenter { } } + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(position) { + invalidated_views.push(region.view_id); + mouse_up_region = + Some((region.clone(), MouseRegionEvent::Up(e.clone()))); + break; + } + } + if let Some(moved) = &mut self.last_mouse_moved_event { if moved.pressed_button == Some(button) { moved.pressed_button = None; @@ -350,6 +360,17 @@ impl Presenter { } } + if let Some((mouse_up_region, region_event)) = mouse_up_region { + handled = true; + if let Some(mouse_up_callback) = + mouse_up_region.handlers.get(®ion_event.handler_key()) + { + event_cx.with_current_view(mouse_up_region.view_id, |event_cx| { + mouse_up_callback(region_event, event_cx); + }) + } + } + if let Some((clicked_region, region_event)) = clicked_region { handled = true; if let Some(click_callback) = diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index e241cb5822f39579c840b9bf43df7b1d927db40e..bc80796aae5c4935472f1e5a645bd1d91dcd6d25 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -3,7 +3,10 @@ use alacritty_terminal::{ grid::Dimensions, index::Point, selection::SelectionRange, - term::cell::{Cell, Flags}, + term::{ + cell::{Cell, Flags}, + TermMode, + }, }; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ @@ -46,6 +49,7 @@ pub struct LayoutState { background_color: Color, selection_color: Color, size: TerminalSize, + mode: TermMode, } #[derive(Debug)] @@ -428,41 +432,92 @@ impl TerminalEl { origin: Vector2F, view_id: usize, visible_bounds: RectF, + mode: TermMode, cx: &mut PaintContext, ) { let connection = self.terminal; - cx.scene.push_mouse_region( - MouseRegion::new(view_id, None, visible_bounds) - .on_move(move |event, cx| { - if cx.is_parent_view_focused() { - if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - terminal.mouse_move(&event, origin); - cx.notify(); - }) - } + + let mut region = MouseRegion::new(view_id, None, visible_bounds); + + //Terminal Emulator controlled behavior: + region = region + //Start selections + .on_down( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ) + //Update drag selections + .on_drag(MouseButton::Left, move |_prev, event, cx| { + 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); + cx.notify(); + }) } - }) - .on_drag(MouseButton::Left, move |_prev, event, cx| { - 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); - cx.notify(); - }) - } + } + }) + //Copy on up behavior + .on_up( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.mouse_up(&e, origin); + }, + ), + ) + //Handle click based selections + .on_click( + MouseButton::Left, + TerminalEl::generic_button_handler( + connection, + origin, + move |terminal, origin, e, _cx| { + terminal.left_click(&e, origin); + }, + ), + ) + //Context menu + .on_click( + MouseButton::Right, + move |e @ MouseButtonEvent { position, .. }, cx| { + let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) + } else { + //If we can't get the model handle, probably can't deploy the context menu + true + }; + if !mouse_mode { + cx.dispatch_action(DeployContextMenu { position }); } - }) - .on_down( - MouseButton::Left, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) + }, + ) + //This handles both drag mode and mouse motion mode + //Mouse Move TODO + //This cannot be done conditionally for unknown reasons. Pending drag and drop rework. + //This also does not fire on right-mouse-down-move events wild. + .on_move(move |event, cx| { + dbg!(event); + if cx.is_parent_view_focused() { + if let Some(conn_handle) = connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + terminal.mouse_move(&event, origin); + cx.notify(); + }) + } + } + }); + + if mode.contains(TermMode::MOUSE_MODE) { + region = region .on_down( MouseButton::Right, TerminalEl::generic_button_handler( @@ -483,16 +538,6 @@ impl TerminalEl { }, ), ) - .on_up( - MouseButton::Left, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_up(&e, origin); - }, - ), - ) .on_up( MouseButton::Right, TerminalEl::generic_button_handler( @@ -513,31 +558,11 @@ impl TerminalEl { }, ), ) - .on_click( - MouseButton::Left, - TerminalEl::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.left_click(&e, origin); - }, - ), - ) - .on_click( - MouseButton::Right, - move |e @ MouseButtonEvent { position, .. }, cx| { - let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift)) - } else { - //If we can't get the model handle, probably can't deploy the context menu - true - }; - if !mouse_mode { - cx.dispatch_action(DeployContextMenu { position }); - } - }, - ), - ); + } + + //TODO: Mouse drag isn't correct + //TODO: Nor is mouse motion. Move events aren't happening?? + cx.scene.push_mouse_region(region); } ///Configures a text style from the current settings. @@ -601,7 +626,7 @@ impl Element for TerminalEl { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text) = self + let (cells, selection, cursor, display_offset, cursor_text, mode) = self .terminal .upgrade(cx) .unwrap() @@ -624,13 +649,13 @@ impl Element for TerminalEl { cell: ic.cell.clone(), }), ); - ( cells, content.selection, content.cursor, content.display_offset, cursor_text, + content.mode, ) }) }); @@ -709,6 +734,7 @@ impl Element for TerminalEl { size: dimensions, rects, highlights, + mode, }, ) } @@ -727,7 +753,7 @@ impl Element for TerminalEl { let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse - self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, cx); + self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx); cx.paint_layer(clip_bounds, |cx| { //Start with a background color diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 9cd1768b4781d61efdb8d106f682063305cc560c..236f954c74ca2e8ed49021aad18a132ef682006d 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -1,4 +1,4 @@ -use std::cmp::min; +use std::cmp::{max, min}; use std::iter::repeat; use alacritty_terminal::grid::Dimensions; @@ -60,6 +60,7 @@ impl MouseFormat { } } +#[derive(Debug)] enum MouseButton { LeftButton = 0, MiddleButton = 1, @@ -117,7 +118,7 @@ pub fn scroll_report( e: &ScrollWheelEvent, mode: TermMode, ) -> Option>> { - if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { + if mode.intersects(TermMode::MOUSE_MODE) { mouse_report( point, MouseButton::from_scroll(e), @@ -125,7 +126,7 @@ pub fn scroll_report( Modifiers::from_scroll(), MouseFormat::from_mode(mode), ) - .map(|report| repeat(report).take(scroll_lines as usize)) + .map(|report| repeat(report).take(max(scroll_lines, 1) as usize)) } else { None } @@ -165,14 +166,21 @@ pub fn mouse_button_report( pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option> { let button = MouseButton::from_move(e); + dbg!(&button); + if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { - mouse_report( - point, - button, - true, - Modifiers::from_moved(e), - MouseFormat::from_mode(mode), - ) + //Only drags are reported in drag mode, so block NoneMove. + if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, MouseButton::NoneMove) { + None + } else { + mouse_report( + point, + button, + true, + Modifiers::from_moved(e), + MouseFormat::from_mode(mode), + ) + } } else { None } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a3aa0f06013b901b972c00238eeb9a9363d2551c..ea3774a586b6ff951702545e8c5ada94b4554183 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -35,8 +35,8 @@ use thiserror::Error; use gpui::{ geometry::vector::{vec2f, Vector2F}, keymap::Keystroke, - ClipboardItem, Entity, ModelContext, MouseButtonEvent, MouseMovedEvent, MutableAppContext, - ScrollWheelEvent, + ClipboardItem, Entity, ModelContext, MouseButton, MouseButtonEvent, MouseMovedEvent, + MutableAppContext, ScrollWheelEvent, }; use crate::mappings::{ @@ -627,6 +627,7 @@ impl Terminal { } pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) { + dbg!("term mouse_move"); let position = e.position.sub(origin); let point = mouse_point(position, self.cur_size, self.last_offset); @@ -653,7 +654,6 @@ impl Terminal { pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) { let position = e.position.sub(origin); - let point = mouse_point(position, self.cur_size, self.last_offset); let side = mouse_side(position, self.cur_size); @@ -661,7 +661,7 @@ impl Terminal { if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) { self.pty_tx.notify(bytes); } - } else { + } else if e.button == MouseButton::Left { self.events .push(InternalEvent::SetSelection(Some(Selection::new( SelectionType::Simple, @@ -695,14 +695,13 @@ impl Terminal { pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) { let position = e.position.sub(origin); - if self.mouse_mode(e.shift) { let point = mouse_point(position, self.cur_size, self.last_offset); if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) { self.pty_tx.notify(bytes); } - } else { + } else if e.button == MouseButton::Left { // Seems pretty standard to automatically copy on mouse_up for terminals, // so let's do that here self.copy();