From 37ca7a66589851872831e3edd79eda9d6d301817 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 Aug 2022 17:37:24 -0700 Subject: [PATCH] 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": {