From 60a44359e4425fd105e63e763b81ad33dc1eefae Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 18 Feb 2025 14:10:10 -0700 Subject: [PATCH] Terminal mouse improvements (#25104) Closes #24911 Closes #17983 Closes #7073 Release Notes: - Terminal: Fix cmd-click on links/files when terminal is not focused - Terminal: Remove hover treatment after Zed hides/re-opens --------- Co-authored-by: Mikayla Maki --- crates/repl/src/outputs/plain.rs | 19 +- crates/terminal/src/mappings/mouse.rs | 43 +-- crates/terminal/src/terminal.rs | 267 +++++++++--------- crates/terminal_view/src/terminal_element.rs | 96 +++---- .../terminal_view/src/terminal_scrollbar.rs | 2 +- crates/terminal_view/src/terminal_view.rs | 32 +-- 6 files changed, 229 insertions(+), 230 deletions(-) diff --git a/crates/repl/src/outputs/plain.rs b/crates/repl/src/outputs/plain.rs index 2c467b57a7..f78b0f2d4c 100644 --- a/crates/repl/src/outputs/plain.rs +++ b/crates/repl/src/outputs/plain.rs @@ -22,7 +22,7 @@ use alacritty_terminal::{ term::Config, vte::ansi::Processor, }; -use gpui::{canvas, size, ClipboardItem, Entity, FontStyle, TextStyle, WhiteSpace}; +use gpui::{canvas, size, Bounds, ClipboardItem, Entity, FontStyle, TextStyle, WhiteSpace}; use language::Buffer; use settings::Settings as _; use terminal_view::terminal_element::TerminalElement; @@ -85,7 +85,7 @@ pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle { } /// Returns the default terminal size for the terminal output. -pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalSize { +pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalBounds { let text_style = text_style(window, cx); let text_system = window.text_system(); @@ -106,10 +106,13 @@ pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalSiz let width = columns as f32 * cell_width; let height = num_lines as f32 * window.line_height(); - terminal::TerminalSize { + terminal::TerminalBounds { cell_width, line_height, - size: size(width, height), + bounds: Bounds { + origin: gpui::Point::default(), + size: size(width, height), + }, } } @@ -277,10 +280,10 @@ impl Render for TerminalOutput { for rect in rects { rect.paint( bounds.origin, - &terminal::TerminalSize { + &terminal::TerminalBounds { cell_width, line_height: text_line_height, - size: bounds.size, + bounds, }, window, ); @@ -289,10 +292,10 @@ impl Render for TerminalOutput { for cell in cells { cell.paint( bounds.origin, - &terminal::TerminalSize { + &terminal::TerminalBounds { cell_width, line_height: text_line_height, - size: bounds.size, + bounds, }, bounds, window, diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 27965b3c26..bd91009a56 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -6,9 +6,9 @@ use alacritty_terminal::grid::Dimensions; /// with modifications for our circumstances use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side}; use alacritty_terminal::term::TermMode; -use gpui::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent}; +use gpui::{px, Modifiers, MouseButton, Pixels, Point, ScrollWheelEvent}; -use crate::TerminalSize; +use crate::TerminalBounds; enum MouseFormat { Sgr, @@ -42,14 +42,12 @@ enum AlacMouseButton { } impl AlacMouseButton { - fn from_move(e: &MouseMoveEvent) -> Self { - match e.pressed_button { - Some(b) => match b { - gpui::MouseButton::Left => AlacMouseButton::LeftMove, - gpui::MouseButton::Middle => AlacMouseButton::MiddleMove, - gpui::MouseButton::Right => AlacMouseButton::RightMove, - gpui::MouseButton::Navigate(_) => AlacMouseButton::Other, - }, + fn from_move_button(e: Option) -> Self { + match e { + Some(gpui::MouseButton::Left) => AlacMouseButton::LeftMove, + Some(gpui::MouseButton::Middle) => AlacMouseButton::MiddleMove, + Some(gpui::MouseButton::Right) => AlacMouseButton::RightMove, + Some(gpui::MouseButton::Navigate(_)) => AlacMouseButton::Other, None => AlacMouseButton::NoneMove, } } @@ -134,34 +132,37 @@ pub fn mouse_button_report( } } -pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) -> Option> { - let button = AlacMouseButton::from_move(e); +pub fn mouse_moved_report( + point: AlacPoint, + button: Option, + modifiers: Modifiers, + mode: TermMode, +) -> Option> { + let button = AlacMouseButton::from_move_button(button); if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { //Only drags are reported in drag mode, so block NoneMove. if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) { None } else { - mouse_report( - point, - button, - true, - e.modifiers, - MouseFormat::from_mode(mode), - ) + mouse_report(point, button, true, modifiers, MouseFormat::from_mode(mode)) } } else { None } } -pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { +pub fn grid_point( + pos: Point, + cur_size: TerminalBounds, + display_offset: usize, +) -> AlacPoint { grid_point_and_side(pos, cur_size, display_offset).0 } pub fn grid_point_and_side( pos: Point, - cur_size: TerminalSize, + cur_size: TerminalBounds, display_offset: usize, ) -> (AlacPoint, Side) { let mut col = GridCol((pos.x / cur_size.cell_width) as usize); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d172142f9f..ec95fa1aca 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -61,6 +61,7 @@ use gpui::{ actions, black, px, AnyWindowHandle, App, AppContext as _, Bounds, ClipboardItem, Context, EventEmitter, Hsla, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, + Window, }; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; @@ -131,7 +132,7 @@ pub enum MaybeNavigationTarget { #[derive(Clone)] enum InternalEvent { - Resize(TerminalSize), + Resize(TerminalBounds), Clear, // FocusNextMatch, Scroll(AlacScroll), @@ -161,35 +162,35 @@ pub fn init(cx: &mut App) { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct TerminalSize { +pub struct TerminalBounds { pub cell_width: Pixels, pub line_height: Pixels, - pub size: Size, + pub bounds: Bounds, } -impl TerminalSize { - pub fn new(line_height: Pixels, cell_width: Pixels, size: Size) -> Self { - TerminalSize { +impl TerminalBounds { + pub fn new(line_height: Pixels, cell_width: Pixels, bounds: Bounds) -> Self { + TerminalBounds { cell_width, line_height, - size, + bounds, } } pub fn num_lines(&self) -> usize { - (self.size.height / self.line_height).floor() as usize + (self.bounds.size.height / self.line_height).floor() as usize } pub fn num_columns(&self) -> usize { - (self.size.width / self.cell_width).floor() as usize + (self.bounds.size.width / self.cell_width).floor() as usize } pub fn height(&self) -> Pixels { - self.size.height + self.bounds.size.height } pub fn width(&self) -> Pixels { - self.size.width + self.bounds.size.width } pub fn cell_width(&self) -> Pixels { @@ -201,21 +202,24 @@ impl TerminalSize { } } -impl Default for TerminalSize { +impl Default for TerminalBounds { fn default() -> Self { - TerminalSize::new( + TerminalBounds::new( DEBUG_LINE_HEIGHT, DEBUG_CELL_WIDTH, - Size { - width: DEBUG_TERMINAL_WIDTH, - height: DEBUG_TERMINAL_HEIGHT, + Bounds { + origin: Point::default(), + size: Size { + width: DEBUG_TERMINAL_WIDTH, + height: DEBUG_TERMINAL_HEIGHT, + }, }, ) } } -impl From for WindowSize { - fn from(val: TerminalSize) -> Self { +impl From for WindowSize { + fn from(val: TerminalBounds) -> Self { WindowSize { num_lines: val.num_lines() as u16, num_cols: val.num_columns() as u16, @@ -225,7 +229,7 @@ impl From for WindowSize { } } -impl Dimensions for TerminalSize { +impl Dimensions for TerminalBounds { /// Note: this is supposed to be for the back buffer's length, /// but we exclusively use it to resize the terminal, which does not /// use this method. We still have to implement it for the trait though, @@ -406,7 +410,7 @@ impl TerminalBuilder { //Set up the terminal... let mut term = Term::new( config.clone(), - &TerminalSize::default(), + &TerminalBounds::default(), ZedListener(events_tx.clone()), ); @@ -420,7 +424,7 @@ impl TerminalBuilder { //Setup the pty... let pty = match tty::new( &pty_options, - TerminalSize::default().into(), + TerminalBounds::default().into(), window.window_id().as_u64(), ) { Ok(pty) => pty, @@ -463,11 +467,9 @@ impl TerminalBuilder { pty_info, breadcrumb_text: String::new(), scroll_px: px(0.), - last_mouse_position: None, next_link_id: 0, selection_phase: SelectionPhase::Ended, - secondary_pressed: false, - hovered_word: false, + // hovered_word: false, url_regex: RegexSearch::new(URL_REGEX).unwrap(), word_regex: RegexSearch::new(WORD_REGEX).unwrap(), vi_mode_enabled: false, @@ -569,7 +571,7 @@ pub struct TerminalContent { pub selection: Option, pub cursor: RenderableCursor, pub cursor_char: char, - pub size: TerminalSize, + pub terminal_bounds: TerminalBounds, pub last_hovered_word: Option, } @@ -593,7 +595,7 @@ impl Default for TerminalContent { point: AlacPoint::new(Line(0), Column(0)), }, cursor_char: Default::default(), - size: Default::default(), + terminal_bounds: Default::default(), last_hovered_word: None, } } @@ -613,8 +615,6 @@ pub struct Terminal { events: VecDeque, /// This is only used for mouse mode cell change detection last_mouse: Option<(AlacPoint, AlacDirection)>, - /// This is only used for terminal hovered word checking - last_mouse_position: Option>, pub matches: Vec>, pub last_content: TerminalContent, pub selection_head: Option, @@ -625,8 +625,6 @@ pub struct Terminal { scroll_px: Pixels, next_link_id: usize, selection_phase: SelectionPhase, - secondary_pressed: bool, - hovered_word: bool, url_regex: RegexSearch, word_regex: RegexSearch, task: Option, @@ -697,7 +695,7 @@ impl Terminal { } AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()), AlacTermEvent::TextAreaSizeRequest(format) => { - self.write_to_pty(format(self.last_content.size.into())) + self.write_to_pty(format(self.last_content.terminal_bounds.into())) } AlacTermEvent::CursorBlinkingChange => { let terminal = self.term.lock(); @@ -746,18 +744,20 @@ impl Terminal { &mut self, event: &InternalEvent, term: &mut Term, + window: &mut Window, cx: &mut Context, ) { match event { - InternalEvent::Resize(mut new_size) => { - new_size.size.height = cmp::max(new_size.line_height, new_size.height()); - new_size.size.width = cmp::max(new_size.cell_width, new_size.width()); + InternalEvent::Resize(mut new_bounds) => { + new_bounds.bounds.size.height = + cmp::max(new_bounds.line_height, new_bounds.height()); + new_bounds.bounds.size.width = cmp::max(new_bounds.cell_width, new_bounds.width()); - self.last_content.size = new_size; + self.last_content.terminal_bounds = new_bounds; - self.pty_tx.0.send(Msg::Resize(new_size.into())).ok(); + self.pty_tx.0.send(Msg::Resize(new_bounds.into())).ok(); - term.resize(new_size); + term.resize(new_bounds); } InternalEvent::Clear => { // Clear back buffer @@ -793,7 +793,7 @@ impl Terminal { } InternalEvent::Scroll(scroll) => { term.scroll_display(*scroll); - self.refresh_hovered_word(); + self.refresh_hovered_word(window); if self.vi_mode_enabled { match *scroll { @@ -849,7 +849,7 @@ impl Terminal { if let Some(mut selection) = term.selection.take() { let (point, side) = grid_point_and_side( *position, - self.last_content.size, + self.last_content.terminal_bounds, term.grid().display_offset(), ); @@ -873,7 +873,7 @@ impl Terminal { } InternalEvent::ScrollToAlacPoint(point) => { term.scroll_to_point(*point); - self.refresh_hovered_word(); + self.refresh_hovered_word(window); } InternalEvent::ToggleViMode => { self.vi_mode_enabled = !self.vi_mode_enabled; @@ -887,7 +887,7 @@ impl Terminal { let point = grid_point( *position, - self.last_content.size, + self.last_content.terminal_bounds, term.grid().display_offset(), ) .grid_clamp(term, Boundary::Grid); @@ -977,13 +977,9 @@ impl Terminal { cx, ); } - self.hovered_word = true; } None => { - if self.hovered_word { - cx.emit(Event::NewNavigationTarget(None)); - } - self.hovered_word = false; + cx.emit(Event::NewNavigationTarget(None)); } } } @@ -1015,6 +1011,7 @@ impl Terminal { id: self.next_link_id(), }); cx.emit(Event::NewNavigationTarget(Some(navigation_target))); + cx.notify() } fn next_link_id(&mut self) -> usize { @@ -1134,9 +1131,9 @@ impl Terminal { } ///Resize the terminal and the PTY. - pub fn set_size(&mut self, new_size: TerminalSize) { - if self.last_content.size != new_size { - self.events.push_back(InternalEvent::Resize(new_size)) + pub fn set_size(&mut self, new_bounds: TerminalBounds) { + if self.last_content.terminal_bounds != new_bounds { + self.events.push_back(InternalEvent::Resize(new_bounds)) } } @@ -1200,8 +1197,8 @@ impl Terminal { if let Some(motion) = motion { let cursor = self.last_content.cursor.point; let cursor_pos = Point { - x: cursor.column.0 as f32 * self.last_content.size.cell_width, - y: cursor.line.0 as f32 * self.last_content.size.line_height, + x: cursor.column.0 as f32 * self.last_content.terminal_bounds.cell_width, + y: cursor.line.0 as f32 * self.last_content.terminal_bounds.line_height, }; self.events .push_back(InternalEvent::UpdateSelection(cursor_pos)); @@ -1215,11 +1212,11 @@ impl Terminal { "b" if keystroke.modifiers.control => Some(AlacScroll::PageUp), "f" if keystroke.modifiers.control => Some(AlacScroll::PageDown), "d" if keystroke.modifiers.control => { - let amount = self.last_content.size.line_height().to_f64() as i32 / 2; + let amount = self.last_content.terminal_bounds.line_height().to_f64() as i32 / 2; Some(AlacScroll::Delta(-amount)) } "u" if keystroke.modifiers.control => { - let amount = self.last_content.size.line_height().to_f64() as i32 / 2; + let amount = self.last_content.terminal_bounds.line_height().to_f64() as i32 / 2; Some(AlacScroll::Delta(amount)) } _ => None, @@ -1277,13 +1274,22 @@ impl Terminal { } } - pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { - let changed = self.secondary_pressed != modifiers.secondary(); - if !self.secondary_pressed && modifiers.secondary() { - self.refresh_hovered_word(); + pub fn try_modifiers_change( + &mut self, + modifiers: &Modifiers, + window: &Window, + cx: &mut Context, + ) { + if self + .last_content + .terminal_bounds + .bounds + .contains(&window.mouse_position()) + && modifiers.secondary() + { + self.refresh_hovered_word(window); } - self.secondary_pressed = modifiers.secondary(); - changed + cx.notify(); } ///Paste text into the terminal @@ -1297,12 +1303,12 @@ impl Terminal { self.input(paste_text); } - pub fn sync(&mut self, cx: &mut Context) { + pub fn sync(&mut self, window: &mut Window, cx: &mut Context) { let term = self.term.clone(); let mut terminal = term.lock_unfair(); //Note that the ordering of events matters for event processing while let Some(e) = self.events.pop_front() { - self.process_terminal_event(&e, &mut terminal, cx) + self.process_terminal_event(&e, &mut terminal, window, cx) } self.last_content = Self::make_content(&terminal, &self.last_content); @@ -1331,7 +1337,7 @@ impl Terminal { selection: content.selection, cursor: content.cursor, cursor_char: term.grid()[content.cursor.point].c, - size: last_content.size, + terminal_bounds: last_content.terminal_bounds, last_hovered_word: last_content.last_hovered_word.clone(), } } @@ -1368,7 +1374,6 @@ impl Terminal { } pub fn focus_out(&mut self) { - self.last_mouse_position = None; if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) { self.write_to_pty("\x1b[O".to_string()); } @@ -1395,44 +1400,48 @@ impl Terminal { self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift } - pub fn mouse_move(&mut self, e: &MouseMoveEvent, origin: Point) { - let position = e.position - origin; - self.last_mouse_position = Some(position); + pub fn mouse_move(&mut self, e: &MouseMoveEvent, cx: &mut Context) { + let position = e.position - self.last_content.terminal_bounds.bounds.origin; if self.mouse_mode(e.modifiers.shift) { let (point, side) = grid_point_and_side( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); if self.mouse_changed(point, side) { - if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) { + if let Some(bytes) = + mouse_moved_report(point, e.pressed_button, e.modifiers, self.last_content.mode) + { self.pty_tx.notify(bytes); } } - } else if self.secondary_pressed { - self.word_from_position(Some(position)); + } else if e.modifiers.secondary() { + self.word_from_position(e.position); } + cx.notify(); } - fn word_from_position(&mut self, position: Option>) { + fn word_from_position(&mut self, position: Point) { if self.selection_phase == SelectionPhase::Selecting { self.last_content.last_hovered_word = None; - } else if let Some(position) = position { - self.events - .push_back(InternalEvent::FindHyperlink(position, false)); + } else if self.last_content.terminal_bounds.bounds.contains(&position) { + self.events.push_back(InternalEvent::FindHyperlink( + position - self.last_content.terminal_bounds.bounds.origin, + false, + )); + } else { + self.last_content.last_hovered_word = None; } } pub fn mouse_drag( &mut self, e: &MouseMoveEvent, - origin: Point, region: Bounds, + cx: &mut Context, ) { - let position = e.position - origin; - self.last_mouse_position = Some(position); - + let position = e.position - self.last_content.terminal_bounds.bounds.origin; if !self.mouse_mode(e.modifiers.shift) { self.selection_phase = SelectionPhase::Selecting; // Alacritty has the same ordering, of first updating the selection @@ -1447,18 +1456,21 @@ impl Terminal { None => return, }; - let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32; + let scroll_lines = + (scroll_delta / self.last_content.terminal_bounds.line_height) as i32; self.events .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); } + + cx.notify(); } } fn drag_line_delta(&self, e: &MouseMoveEvent, region: Bounds) -> Option { //TODO: Why do these need to be doubled? Probably the same problem that the IME has - let top = region.origin.y + (self.last_content.size.line_height * 2.); - let bottom = region.bottom_left().y - (self.last_content.size.line_height * 2.); + let top = region.origin.y + (self.last_content.terminal_bounds.line_height * 2.); + let bottom = region.bottom_left().y - (self.last_content.terminal_bounds.line_height * 2.); let scroll_delta = if e.position.y < top { (top - e.position.y).pow(1.1) } else if e.position.y > bottom { @@ -1469,16 +1481,11 @@ impl Terminal { Some(scroll_delta) } - pub fn mouse_down( - &mut self, - e: &MouseDownEvent, - origin: Point, - _cx: &mut Context, - ) { - let position = e.position - origin; + pub fn mouse_down(&mut self, e: &MouseDownEvent, _cx: &mut Context) { + let position = e.position - self.last_content.terminal_bounds.bounds.origin; let point = grid_point( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1491,10 +1498,9 @@ impl Terminal { } else { match e.button { MouseButton::Left => { - let position = e.position - origin; let (point, side) = grid_point_and_side( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1526,14 +1532,14 @@ impl Terminal { } } - pub fn mouse_up(&mut self, e: &MouseUpEvent, origin: Point, cx: &Context) { + pub fn mouse_up(&mut self, e: &MouseUpEvent, cx: &Context) { let setting = TerminalSettings::get_global(cx); - let position = e.position - origin; + let position = e.position - self.last_content.terminal_bounds.bounds.origin; if self.mouse_mode(e.modifiers.shift) { let point = grid_point( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1549,10 +1555,11 @@ impl Terminal { //Hyperlinks if self.selection_phase == SelectionPhase::Ended { - let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size); + let mouse_cell_index = + content_index_for_mouse(position, &self.last_content.terminal_bounds); if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { cx.open_url(link.uri()); - } else if self.secondary_pressed { + } else if e.modifiers.secondary() { self.events .push_back(InternalEvent::FindHyperlink(position, true)); } @@ -1564,14 +1571,14 @@ impl Terminal { } ///Scroll the terminal - pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point) { + pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent) { let mouse_mode = self.mouse_mode(e.shift); if let Some(scroll_lines) = self.determine_scroll_lines(e, mouse_mode) { if mouse_mode { let point = grid_point( - e.position - origin, - self.last_content.size, + e.position - self.last_content.terminal_bounds.bounds.origin, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1596,13 +1603,13 @@ impl Terminal { } } - fn refresh_hovered_word(&mut self) { - self.word_from_position(self.last_mouse_position); + fn refresh_hovered_word(&mut self, window: &Window) { + self.word_from_position(window.mouse_position()); } fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option { let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER }; - let line_height = self.last_content.size.line_height; + let line_height = self.last_content.terminal_bounds.line_height; match e.touch_phase { /* Reset scroll state on started */ TouchPhase::Started => { @@ -1619,7 +1626,7 @@ impl Terminal { // Whenever we hit the edges, reset our stored scroll to 0 // so we can respond to changes in direction quickly - self.scroll_px %= self.last_content.size.height(); + self.scroll_px %= self.last_content.terminal_bounds.height(); Some(new_offset - old_offset) } @@ -1714,10 +1721,6 @@ impl Terminal { } } - pub fn can_navigate_to_selected_word(&self) -> bool { - self.secondary_pressed && self.hovered_word - } - pub fn task(&self) -> Option<&TaskState> { self.task.as_ref() } @@ -1899,12 +1902,12 @@ fn all_search_matches<'a, T>( RegexIter::new(start, end, AlacDirection::Right, term, regex) } -fn content_index_for_mouse(pos: Point, size: &TerminalSize) -> usize { - let col = (pos.x / size.cell_width()).round() as usize; - let clamped_col = min(col, size.columns() - 1); - let row = (pos.y / size.line_height()).round() as usize; - let clamped_row = min(row, size.screen_lines() - 1); - clamped_row * size.columns() + clamped_col +fn content_index_for_mouse(pos: Point, terminal_bounds: &TerminalBounds) -> usize { + let col = (pos.x / terminal_bounds.cell_width()).round() as usize; + let clamped_col = min(col, terminal_bounds.columns() - 1); + let row = (pos.y / terminal_bounds.line_height()).round() as usize; + let clamped_row = min(row, terminal_bounds.screen_lines() - 1); + clamped_row * terminal_bounds.columns() + clamped_col } /// Converts an 8 bit ANSI color to its GPUI equivalent. @@ -1997,11 +2000,11 @@ mod tests { index::{Column, Line, Point as AlacPoint}, term::cell::Cell, }; - use gpui::{point, size, Pixels}; + use gpui::{bounds, point, size, Pixels, Point}; use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng}; use crate::{ - content_index_for_mouse, rgb_for_index, IndexedCell, TerminalContent, TerminalSize, + content_index_for_mouse, rgb_for_index, IndexedCell, TerminalBounds, TerminalContent, }; #[test] @@ -2023,12 +2026,15 @@ mod tests { let viewport_cells = rng.gen_range(15..20); let cell_size = rng.gen_range(5 * PRECISION..20 * PRECISION) as f32 / PRECISION as f32; - let size = crate::TerminalSize { + let size = crate::TerminalBounds { cell_width: Pixels::from(cell_size), line_height: Pixels::from(cell_size), - size: size( - Pixels::from(cell_size * (viewport_cells as f32)), - Pixels::from(cell_size * (viewport_cells as f32)), + bounds: bounds( + Point::default(), + size( + Pixels::from(cell_size * (viewport_cells as f32)), + Pixels::from(cell_size * (viewport_cells as f32)), + ), ), }; @@ -2048,7 +2054,8 @@ mod tests { Pixels::from(row as f32 * cell_size + row_offset), ); - let content_index = content_index_for_mouse(mouse_pos, &content.size); + let content_index = + content_index_for_mouse(mouse_pos, &content.terminal_bounds); let mouse_cell = content.cells[content_index].c; let real_cell = cells[row][col]; @@ -2062,10 +2069,13 @@ mod tests { fn test_mouse_to_cell_clamp() { let mut rng = thread_rng(); - let size = crate::TerminalSize { + let size = crate::TerminalBounds { cell_width: Pixels::from(10.), line_height: Pixels::from(10.), - size: size(Pixels::from(100.), Pixels::from(100.)), + bounds: bounds( + Point::default(), + size(Pixels::from(100.), Pixels::from(100.)), + ), }; let cells = get_cells(size, &mut rng); @@ -2074,7 +2084,7 @@ mod tests { assert_eq!( content.cells[content_index_for_mouse( point(Pixels::from(-10.), Pixels::from(-10.)), - &content.size, + &content.terminal_bounds, )] .c, cells[0][0] @@ -2082,14 +2092,14 @@ mod tests { assert_eq!( content.cells[content_index_for_mouse( point(Pixels::from(1000.), Pixels::from(1000.)), - &content.size, + &content.terminal_bounds, )] .c, cells[9][9] ); } - fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec> { + fn get_cells(size: TerminalBounds, rng: &mut ThreadRng) -> Vec> { let mut cells = Vec::new(); for _ in 0..((size.height() / size.line_height()) as usize) { @@ -2104,7 +2114,10 @@ mod tests { cells } - fn convert_cells_to_content(size: TerminalSize, cells: &[Vec]) -> TerminalContent { + fn convert_cells_to_content( + terminal_bounds: TerminalBounds, + cells: &[Vec], + ) -> TerminalContent { let mut ic = Vec::new(); for (index, row) in cells.iter().enumerate() { @@ -2121,7 +2134,7 @@ mod tests { TerminalContent { cells: ic, - size, + terminal_bounds, ..Default::default() } } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 9a7a0e79d3..26b1e7ac03 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -21,7 +21,7 @@ use terminal::{ }, }, terminal_settings::TerminalSettings, - HoveredWord, IndexedCell, Terminal, TerminalContent, TerminalSize, + HoveredWord, IndexedCell, Terminal, TerminalBounds, TerminalContent, }; use theme::{ActiveTheme, Theme, ThemeSettings}; use ui::{ParentElement, Tooltip}; @@ -40,7 +40,7 @@ pub struct LayoutState { relative_highlighted_ranges: Vec<(RangeInclusive, Hsla)>, cursor: Option, background_color: Hsla, - dimensions: TerminalSize, + dimensions: TerminalBounds, mode: TermMode, display_offset: usize, hyperlink_tooltip: Option, @@ -86,7 +86,7 @@ impl LayoutCell { pub fn paint( &self, origin: Point, - dimensions: &TerminalSize, + dimensions: &TerminalBounds, _visible_bounds: Bounds, window: &mut Window, cx: &mut App, @@ -130,7 +130,7 @@ impl LayoutRect { } } - pub fn paint(&self, origin: Point, dimensions: &TerminalSize, window: &mut Window) { + pub fn paint(&self, origin: Point, dimensions: &TerminalBounds, window: &mut Window) { let position = { let alac_point = self.point; point( @@ -313,7 +313,7 @@ impl TerminalElement { /// the same position for sequential indexes. Use em_width instead fn shape_cursor( cursor_point: DisplayCursor, - size: TerminalSize, + size: TerminalBounds, text_fragment: &ShapedLine, ) -> Option<(Point, Pixels)> { if cursor_point.line() < size.total_lines() as i32 { @@ -412,27 +412,20 @@ impl TerminalElement { fn generic_button_handler( connection: Entity, - origin: Point, focus_handle: FocusHandle, - f: impl Fn(&mut Terminal, Point, &E, &mut Context), + f: impl Fn(&mut Terminal, &E, &mut Context), ) -> impl Fn(&E, &mut Window, &mut App) { move |event, window, cx| { window.focus(&focus_handle); connection.update(cx, |terminal, cx| { - f(terminal, origin, event, cx); + f(terminal, event, cx); cx.notify(); }) } } - fn register_mouse_listeners( - &mut self, - origin: Point, - mode: TermMode, - hitbox: &Hitbox, - window: &mut Window, - ) { + fn register_mouse_listeners(&mut self, mode: TermMode, hitbox: &Hitbox, window: &mut Window) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); @@ -442,29 +435,26 @@ impl TerminalElement { move |e, window, cx| { window.focus(&focus); terminal.update(cx, |terminal, cx| { - terminal.mouse_down(e, origin, cx); + terminal.mouse_down(e, cx); cx.notify(); }) } }); window.on_mouse_event({ - let focus = self.focus.clone(); let terminal = self.terminal.clone(); let hitbox = hitbox.clone(); + let focus = focus.clone(); move |e: &MouseMoveEvent, phase, window, cx| { - if phase != DispatchPhase::Bubble || !focus.is_focused(window) { + if phase != DispatchPhase::Bubble { return; } - if e.pressed_button.is_some() && !cx.has_active_drag() { + if e.pressed_button.is_some() && !cx.has_active_drag() && focus.is_focused(window) { let hovered = hitbox.is_hovered(window); terminal.update(cx, |terminal, cx| { - if terminal.selection_started() { - terminal.mouse_drag(e, origin, hitbox.bounds); - cx.notify(); - } else if hovered { - terminal.mouse_drag(e, origin, hitbox.bounds); + if terminal.selection_started() || hovered { + terminal.mouse_drag(e, hitbox.bounds, cx); cx.notify(); } }) @@ -472,8 +462,7 @@ impl TerminalElement { if hitbox.is_hovered(window) { terminal.update(cx, |terminal, cx| { - terminal.mouse_move(e, origin); - cx.notify(); + terminal.mouse_move(e, cx); }) } } @@ -483,10 +472,9 @@ impl TerminalElement { MouseButton::Left, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_up(e, cx); }, ), ); @@ -494,10 +482,9 @@ impl TerminalElement { MouseButton::Middle, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_down(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_down(e, cx); }, ), ); @@ -506,7 +493,7 @@ impl TerminalElement { move |e, _, cx| { terminal_view .update(cx, |terminal_view, cx| { - terminal_view.scroll_wheel(e, origin, cx); + terminal_view.scroll_wheel(e, cx); cx.notify(); }) .ok(); @@ -520,10 +507,9 @@ impl TerminalElement { MouseButton::Right, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_down(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_down(e, cx); }, ), ); @@ -531,23 +517,17 @@ impl TerminalElement { MouseButton::Right, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_up(e, cx); }, ), ); self.interactivity.on_mouse_up( MouseButton::Middle, - TerminalElement::generic_button_handler( - terminal, - origin, - focus, - move |terminal, origin, e, cx| { - terminal.mouse_up(e, origin, cx); - }, - ), + TerminalElement::generic_button_handler(terminal, focus, move |terminal, e, cx| { + terminal.mouse_up(e, cx); + }), ); } } @@ -705,7 +685,10 @@ impl Element for TerminalElement { size.width = cell_width * 2.0; } - TerminalSize::new(line_height, cell_width, size) + let mut origin = bounds.origin; + origin.x += gutter; + + TerminalBounds::new(line_height, cell_width, Bounds { origin, size }) }; let search_matches = self.terminal.read(cx).matches.clone(); @@ -714,9 +697,11 @@ impl Element for TerminalElement { let last_hovered_word = self.terminal.update(cx, |terminal, cx| { terminal.set_size(dimensions); - terminal.sync(cx); + terminal.sync(window, cx); + if self.can_navigate_to_selected_word - && terminal.can_navigate_to_selected_word() + && window.modifiers().secondary() + && bounds.contains(&window.mouse_position()) { terminal.last_content.last_hovered_word.clone() } else { @@ -898,7 +883,7 @@ impl Element for TerminalElement { workspace: self.workspace.clone(), }; - self.register_mouse_listeners(origin, layout.mode, &layout.hitbox, window); + self.register_mouse_listeners(layout.mode, &layout.hitbox, window); if self.can_navigate_to_selected_word && layout.last_hovered_word.is_some() { window.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox); } else { @@ -924,12 +909,9 @@ impl Element for TerminalElement { return; } - let handled = this - .update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); - - if handled { - window.refresh(); - } + this.update(cx, |term, cx| { + term.try_modifiers_change(&event.modifiers, window, cx) + }); } }); diff --git a/crates/terminal_view/src/terminal_scrollbar.rs b/crates/terminal_view/src/terminal_scrollbar.rs index e86ebdf558..01b9100714 100644 --- a/crates/terminal_view/src/terminal_scrollbar.rs +++ b/crates/terminal_view/src/terminal_scrollbar.rs @@ -19,7 +19,7 @@ struct ScrollHandleState { impl ScrollHandleState { fn new(terminal: &Terminal) -> Self { Self { - line_height: terminal.last_content().size.line_height, + line_height: terminal.last_content().terminal_bounds.line_height, total_lines: terminal.total_lines(), viewport_lines: terminal.viewport_lines(), display_offset: terminal.last_content().display_offset, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 7b3c92c7aa..28a926c611 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -23,7 +23,7 @@ use terminal::{ terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal, - TerminalSize, ToggleViMode, + TerminalBounds, ToggleViMode, }; use terminal_element::{is_blank, TerminalElement}; use terminal_panel::TerminalPanel; @@ -101,7 +101,7 @@ pub struct BlockProperties { pub struct BlockContext<'a, 'b> { pub window: &'a mut Window, pub context: &'b mut App, - pub dimensions: TerminalSize, + pub dimensions: TerminalBounds, } ///A terminal view, maintains the PTY's file handles and communicates with the terminal @@ -342,7 +342,7 @@ impl TerminalView { return Pixels::ZERO; }; - let line_height = terminal.last_content().size.line_height; + let line_height = terminal.last_content().terminal_bounds.line_height; let mut terminal_lines = terminal.total_lines(); let viewport_lines = terminal.viewport_lines(); if terminal.total_lines() == terminal.viewport_lines() { @@ -366,16 +366,11 @@ impl TerminalView { max_scroll_top_in_lines as f32 * line_height } - fn scroll_wheel( - &mut self, - event: &ScrollWheelEvent, - origin: gpui::Point, - cx: &mut Context, - ) { + fn scroll_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { - let line_height = terminal_content.size.line_height; + let line_height = terminal_content.terminal_bounds.line_height; let y_delta = event.delta.pixel_delta(line_height).y; if y_delta < Pixels::ZERO || self.scroll_top > Pixels::ZERO { self.scroll_top = cmp::max( @@ -387,8 +382,7 @@ impl TerminalView { } } - self.terminal - .update(cx, |term, _| term.scroll_wheel(event, origin)); + self.terminal.update(cx, |term, _| term.scroll_wheel(event)); } fn scroll_line_up(&mut self, _: &ScrollLineUp, _: &mut Window, cx: &mut Context) { @@ -397,7 +391,7 @@ impl TerminalView { && terminal_content.display_offset == 0 && self.scroll_top > Pixels::ZERO { - let line_height = terminal_content.size.line_height; + let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::max(self.scroll_top - line_height, Pixels::ZERO); return; } @@ -411,7 +405,7 @@ impl TerminalView { if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { let max_scroll_top = self.max_scroll_top(cx); if self.scroll_top < max_scroll_top { - let line_height = terminal_content.size.line_height; + let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::min(self.scroll_top + line_height, max_scroll_top); } return; @@ -425,7 +419,12 @@ impl TerminalView { if self.scroll_top == Pixels::ZERO { self.terminal.update(cx, |term, _| term.scroll_page_up()); } else { - let line_height = self.terminal.read(cx).last_content.size.line_height(); + let line_height = self + .terminal + .read(cx) + .last_content + .terminal_bounds + .line_height(); let visible_block_lines = (self.scroll_top / line_height) as usize; let viewport_lines = self.terminal.read(cx).viewport_lines(); let visible_content_lines = viewport_lines - visible_block_lines; @@ -866,7 +865,8 @@ fn subscribe_for_terminal_events( } } None => false, - } + }; + cx.notify() } Event::Open(maybe_navigation_target) => match maybe_navigation_target {