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 <mikayla.c.maki@gmail.com>
This commit is contained in:
Conrad Irwin 2025-02-18 14:10:10 -07:00 committed by GitHub
parent b1872e3afd
commit 60a44359e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 229 additions and 230 deletions

View file

@ -22,7 +22,7 @@ use alacritty_terminal::{
term::Config, term::Config,
vte::ansi::Processor, 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 language::Buffer;
use settings::Settings as _; use settings::Settings as _;
use terminal_view::terminal_element::TerminalElement; 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. /// 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_style = text_style(window, cx);
let text_system = window.text_system(); 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 width = columns as f32 * cell_width;
let height = num_lines as f32 * window.line_height(); let height = num_lines as f32 * window.line_height();
terminal::TerminalSize { terminal::TerminalBounds {
cell_width, cell_width,
line_height, 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 { for rect in rects {
rect.paint( rect.paint(
bounds.origin, bounds.origin,
&terminal::TerminalSize { &terminal::TerminalBounds {
cell_width, cell_width,
line_height: text_line_height, line_height: text_line_height,
size: bounds.size, bounds,
}, },
window, window,
); );
@ -289,10 +292,10 @@ impl Render for TerminalOutput {
for cell in cells { for cell in cells {
cell.paint( cell.paint(
bounds.origin, bounds.origin,
&terminal::TerminalSize { &terminal::TerminalBounds {
cell_width, cell_width,
line_height: text_line_height, line_height: text_line_height,
size: bounds.size, bounds,
}, },
bounds, bounds,
window, window,

View file

@ -6,9 +6,9 @@ use alacritty_terminal::grid::Dimensions;
/// with modifications for our circumstances /// with modifications for our circumstances
use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side}; use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side};
use alacritty_terminal::term::TermMode; 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 { enum MouseFormat {
Sgr, Sgr,
@ -42,14 +42,12 @@ enum AlacMouseButton {
} }
impl AlacMouseButton { impl AlacMouseButton {
fn from_move(e: &MouseMoveEvent) -> Self { fn from_move_button(e: Option<MouseButton>) -> Self {
match e.pressed_button { match e {
Some(b) => match b { Some(gpui::MouseButton::Left) => AlacMouseButton::LeftMove,
gpui::MouseButton::Left => AlacMouseButton::LeftMove, Some(gpui::MouseButton::Middle) => AlacMouseButton::MiddleMove,
gpui::MouseButton::Middle => AlacMouseButton::MiddleMove, Some(gpui::MouseButton::Right) => AlacMouseButton::RightMove,
gpui::MouseButton::Right => AlacMouseButton::RightMove, Some(gpui::MouseButton::Navigate(_)) => AlacMouseButton::Other,
gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
},
None => AlacMouseButton::NoneMove, None => AlacMouseButton::NoneMove,
} }
} }
@ -134,34 +132,37 @@ pub fn mouse_button_report(
} }
} }
pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) -> Option<Vec<u8>> { pub fn mouse_moved_report(
let button = AlacMouseButton::from_move(e); point: AlacPoint,
button: Option<MouseButton>,
modifiers: Modifiers,
mode: TermMode,
) -> Option<Vec<u8>> {
let button = AlacMouseButton::from_move_button(button);
if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
//Only drags are reported in drag mode, so block NoneMove. //Only drags are reported in drag mode, so block NoneMove.
if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) { if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) {
None None
} else { } else {
mouse_report( mouse_report(point, button, true, modifiers, MouseFormat::from_mode(mode))
point,
button,
true,
e.modifiers,
MouseFormat::from_mode(mode),
)
} }
} else { } else {
None None
} }
} }
pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { pub fn grid_point(
pos: Point<Pixels>,
cur_size: TerminalBounds,
display_offset: usize,
) -> AlacPoint {
grid_point_and_side(pos, cur_size, display_offset).0 grid_point_and_side(pos, cur_size, display_offset).0
} }
pub fn grid_point_and_side( pub fn grid_point_and_side(
pos: Point<Pixels>, pos: Point<Pixels>,
cur_size: TerminalSize, cur_size: TerminalBounds,
display_offset: usize, display_offset: usize,
) -> (AlacPoint, Side) { ) -> (AlacPoint, Side) {
let mut col = GridCol((pos.x / cur_size.cell_width) as usize); let mut col = GridCol((pos.x / cur_size.cell_width) as usize);

View file

@ -61,6 +61,7 @@ use gpui::{
actions, black, px, AnyWindowHandle, App, AppContext as _, Bounds, ClipboardItem, Context, actions, black, px, AnyWindowHandle, App, AppContext as _, Bounds, ClipboardItem, Context,
EventEmitter, Hsla, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, EventEmitter, Hsla, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase,
Window,
}; };
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
@ -131,7 +132,7 @@ pub enum MaybeNavigationTarget {
#[derive(Clone)] #[derive(Clone)]
enum InternalEvent { enum InternalEvent {
Resize(TerminalSize), Resize(TerminalBounds),
Clear, Clear,
// FocusNextMatch, // FocusNextMatch,
Scroll(AlacScroll), Scroll(AlacScroll),
@ -161,35 +162,35 @@ pub fn init(cx: &mut App) {
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TerminalSize { pub struct TerminalBounds {
pub cell_width: Pixels, pub cell_width: Pixels,
pub line_height: Pixels, pub line_height: Pixels,
pub size: Size<Pixels>, pub bounds: Bounds<Pixels>,
} }
impl TerminalSize { impl TerminalBounds {
pub fn new(line_height: Pixels, cell_width: Pixels, size: Size<Pixels>) -> Self { pub fn new(line_height: Pixels, cell_width: Pixels, bounds: Bounds<Pixels>) -> Self {
TerminalSize { TerminalBounds {
cell_width, cell_width,
line_height, line_height,
size, bounds,
} }
} }
pub fn num_lines(&self) -> usize { 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 { 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 { pub fn height(&self) -> Pixels {
self.size.height self.bounds.size.height
} }
pub fn width(&self) -> Pixels { pub fn width(&self) -> Pixels {
self.size.width self.bounds.size.width
} }
pub fn cell_width(&self) -> Pixels { pub fn cell_width(&self) -> Pixels {
@ -201,21 +202,24 @@ impl TerminalSize {
} }
} }
impl Default for TerminalSize { impl Default for TerminalBounds {
fn default() -> Self { fn default() -> Self {
TerminalSize::new( TerminalBounds::new(
DEBUG_LINE_HEIGHT, DEBUG_LINE_HEIGHT,
DEBUG_CELL_WIDTH, DEBUG_CELL_WIDTH,
Size { Bounds {
width: DEBUG_TERMINAL_WIDTH, origin: Point::default(),
height: DEBUG_TERMINAL_HEIGHT, size: Size {
width: DEBUG_TERMINAL_WIDTH,
height: DEBUG_TERMINAL_HEIGHT,
},
}, },
) )
} }
} }
impl From<TerminalSize> for WindowSize { impl From<TerminalBounds> for WindowSize {
fn from(val: TerminalSize) -> Self { fn from(val: TerminalBounds) -> Self {
WindowSize { WindowSize {
num_lines: val.num_lines() as u16, num_lines: val.num_lines() as u16,
num_cols: val.num_columns() as u16, num_cols: val.num_columns() as u16,
@ -225,7 +229,7 @@ impl From<TerminalSize> for WindowSize {
} }
} }
impl Dimensions for TerminalSize { impl Dimensions for TerminalBounds {
/// Note: this is supposed to be for the back buffer's length, /// Note: this is supposed to be for the back buffer's length,
/// but we exclusively use it to resize the terminal, which does not /// 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, /// use this method. We still have to implement it for the trait though,
@ -406,7 +410,7 @@ impl TerminalBuilder {
//Set up the terminal... //Set up the terminal...
let mut term = Term::new( let mut term = Term::new(
config.clone(), config.clone(),
&TerminalSize::default(), &TerminalBounds::default(),
ZedListener(events_tx.clone()), ZedListener(events_tx.clone()),
); );
@ -420,7 +424,7 @@ impl TerminalBuilder {
//Setup the pty... //Setup the pty...
let pty = match tty::new( let pty = match tty::new(
&pty_options, &pty_options,
TerminalSize::default().into(), TerminalBounds::default().into(),
window.window_id().as_u64(), window.window_id().as_u64(),
) { ) {
Ok(pty) => pty, Ok(pty) => pty,
@ -463,11 +467,9 @@ impl TerminalBuilder {
pty_info, pty_info,
breadcrumb_text: String::new(), breadcrumb_text: String::new(),
scroll_px: px(0.), scroll_px: px(0.),
last_mouse_position: None,
next_link_id: 0, next_link_id: 0,
selection_phase: SelectionPhase::Ended, selection_phase: SelectionPhase::Ended,
secondary_pressed: false, // hovered_word: false,
hovered_word: false,
url_regex: RegexSearch::new(URL_REGEX).unwrap(), url_regex: RegexSearch::new(URL_REGEX).unwrap(),
word_regex: RegexSearch::new(WORD_REGEX).unwrap(), word_regex: RegexSearch::new(WORD_REGEX).unwrap(),
vi_mode_enabled: false, vi_mode_enabled: false,
@ -569,7 +571,7 @@ pub struct TerminalContent {
pub selection: Option<SelectionRange>, pub selection: Option<SelectionRange>,
pub cursor: RenderableCursor, pub cursor: RenderableCursor,
pub cursor_char: char, pub cursor_char: char,
pub size: TerminalSize, pub terminal_bounds: TerminalBounds,
pub last_hovered_word: Option<HoveredWord>, pub last_hovered_word: Option<HoveredWord>,
} }
@ -593,7 +595,7 @@ impl Default for TerminalContent {
point: AlacPoint::new(Line(0), Column(0)), point: AlacPoint::new(Line(0), Column(0)),
}, },
cursor_char: Default::default(), cursor_char: Default::default(),
size: Default::default(), terminal_bounds: Default::default(),
last_hovered_word: None, last_hovered_word: None,
} }
} }
@ -613,8 +615,6 @@ pub struct Terminal {
events: VecDeque<InternalEvent>, events: VecDeque<InternalEvent>,
/// This is only used for mouse mode cell change detection /// This is only used for mouse mode cell change detection
last_mouse: Option<(AlacPoint, AlacDirection)>, last_mouse: Option<(AlacPoint, AlacDirection)>,
/// This is only used for terminal hovered word checking
last_mouse_position: Option<Point<Pixels>>,
pub matches: Vec<RangeInclusive<AlacPoint>>, pub matches: Vec<RangeInclusive<AlacPoint>>,
pub last_content: TerminalContent, pub last_content: TerminalContent,
pub selection_head: Option<AlacPoint>, pub selection_head: Option<AlacPoint>,
@ -625,8 +625,6 @@ pub struct Terminal {
scroll_px: Pixels, scroll_px: Pixels,
next_link_id: usize, next_link_id: usize,
selection_phase: SelectionPhase, selection_phase: SelectionPhase,
secondary_pressed: bool,
hovered_word: bool,
url_regex: RegexSearch, url_regex: RegexSearch,
word_regex: RegexSearch, word_regex: RegexSearch,
task: Option<TaskState>, task: Option<TaskState>,
@ -697,7 +695,7 @@ impl Terminal {
} }
AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()), AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()),
AlacTermEvent::TextAreaSizeRequest(format) => { 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 => { AlacTermEvent::CursorBlinkingChange => {
let terminal = self.term.lock(); let terminal = self.term.lock();
@ -746,18 +744,20 @@ impl Terminal {
&mut self, &mut self,
event: &InternalEvent, event: &InternalEvent,
term: &mut Term<ZedListener>, term: &mut Term<ZedListener>,
window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
InternalEvent::Resize(mut new_size) => { InternalEvent::Resize(mut new_bounds) => {
new_size.size.height = cmp::max(new_size.line_height, new_size.height()); new_bounds.bounds.size.height =
new_size.size.width = cmp::max(new_size.cell_width, new_size.width()); 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 => { InternalEvent::Clear => {
// Clear back buffer // Clear back buffer
@ -793,7 +793,7 @@ impl Terminal {
} }
InternalEvent::Scroll(scroll) => { InternalEvent::Scroll(scroll) => {
term.scroll_display(*scroll); term.scroll_display(*scroll);
self.refresh_hovered_word(); self.refresh_hovered_word(window);
if self.vi_mode_enabled { if self.vi_mode_enabled {
match *scroll { match *scroll {
@ -849,7 +849,7 @@ impl Terminal {
if let Some(mut selection) = term.selection.take() { if let Some(mut selection) = term.selection.take() {
let (point, side) = grid_point_and_side( let (point, side) = grid_point_and_side(
*position, *position,
self.last_content.size, self.last_content.terminal_bounds,
term.grid().display_offset(), term.grid().display_offset(),
); );
@ -873,7 +873,7 @@ impl Terminal {
} }
InternalEvent::ScrollToAlacPoint(point) => { InternalEvent::ScrollToAlacPoint(point) => {
term.scroll_to_point(*point); term.scroll_to_point(*point);
self.refresh_hovered_word(); self.refresh_hovered_word(window);
} }
InternalEvent::ToggleViMode => { InternalEvent::ToggleViMode => {
self.vi_mode_enabled = !self.vi_mode_enabled; self.vi_mode_enabled = !self.vi_mode_enabled;
@ -887,7 +887,7 @@ impl Terminal {
let point = grid_point( let point = grid_point(
*position, *position,
self.last_content.size, self.last_content.terminal_bounds,
term.grid().display_offset(), term.grid().display_offset(),
) )
.grid_clamp(term, Boundary::Grid); .grid_clamp(term, Boundary::Grid);
@ -977,13 +977,9 @@ impl Terminal {
cx, cx,
); );
} }
self.hovered_word = true;
} }
None => { None => {
if self.hovered_word { cx.emit(Event::NewNavigationTarget(None));
cx.emit(Event::NewNavigationTarget(None));
}
self.hovered_word = false;
} }
} }
} }
@ -1015,6 +1011,7 @@ impl Terminal {
id: self.next_link_id(), id: self.next_link_id(),
}); });
cx.emit(Event::NewNavigationTarget(Some(navigation_target))); cx.emit(Event::NewNavigationTarget(Some(navigation_target)));
cx.notify()
} }
fn next_link_id(&mut self) -> usize { fn next_link_id(&mut self) -> usize {
@ -1134,9 +1131,9 @@ impl Terminal {
} }
///Resize the terminal and the PTY. ///Resize the terminal and the PTY.
pub fn set_size(&mut self, new_size: TerminalSize) { pub fn set_size(&mut self, new_bounds: TerminalBounds) {
if self.last_content.size != new_size { if self.last_content.terminal_bounds != new_bounds {
self.events.push_back(InternalEvent::Resize(new_size)) self.events.push_back(InternalEvent::Resize(new_bounds))
} }
} }
@ -1200,8 +1197,8 @@ impl Terminal {
if let Some(motion) = motion { if let Some(motion) = motion {
let cursor = self.last_content.cursor.point; let cursor = self.last_content.cursor.point;
let cursor_pos = Point { let cursor_pos = Point {
x: cursor.column.0 as f32 * self.last_content.size.cell_width, x: cursor.column.0 as f32 * self.last_content.terminal_bounds.cell_width,
y: cursor.line.0 as f32 * self.last_content.size.line_height, y: cursor.line.0 as f32 * self.last_content.terminal_bounds.line_height,
}; };
self.events self.events
.push_back(InternalEvent::UpdateSelection(cursor_pos)); .push_back(InternalEvent::UpdateSelection(cursor_pos));
@ -1215,11 +1212,11 @@ impl Terminal {
"b" if keystroke.modifiers.control => Some(AlacScroll::PageUp), "b" if keystroke.modifiers.control => Some(AlacScroll::PageUp),
"f" if keystroke.modifiers.control => Some(AlacScroll::PageDown), "f" if keystroke.modifiers.control => Some(AlacScroll::PageDown),
"d" if keystroke.modifiers.control => { "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)) Some(AlacScroll::Delta(-amount))
} }
"u" if keystroke.modifiers.control => { "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)) Some(AlacScroll::Delta(amount))
} }
_ => None, _ => None,
@ -1277,13 +1274,22 @@ impl Terminal {
} }
} }
pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { pub fn try_modifiers_change(
let changed = self.secondary_pressed != modifiers.secondary(); &mut self,
if !self.secondary_pressed && modifiers.secondary() { modifiers: &Modifiers,
self.refresh_hovered_word(); window: &Window,
cx: &mut Context<Self>,
) {
if self
.last_content
.terminal_bounds
.bounds
.contains(&window.mouse_position())
&& modifiers.secondary()
{
self.refresh_hovered_word(window);
} }
self.secondary_pressed = modifiers.secondary(); cx.notify();
changed
} }
///Paste text into the terminal ///Paste text into the terminal
@ -1297,12 +1303,12 @@ impl Terminal {
self.input(paste_text); self.input(paste_text);
} }
pub fn sync(&mut self, cx: &mut Context<Self>) { pub fn sync(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let term = self.term.clone(); let term = self.term.clone();
let mut terminal = term.lock_unfair(); let mut terminal = term.lock_unfair();
//Note that the ordering of events matters for event processing //Note that the ordering of events matters for event processing
while let Some(e) = self.events.pop_front() { 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); self.last_content = Self::make_content(&terminal, &self.last_content);
@ -1331,7 +1337,7 @@ impl Terminal {
selection: content.selection, selection: content.selection,
cursor: content.cursor, cursor: content.cursor,
cursor_char: term.grid()[content.cursor.point].c, 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(), last_hovered_word: last_content.last_hovered_word.clone(),
} }
} }
@ -1368,7 +1374,6 @@ impl Terminal {
} }
pub fn focus_out(&mut self) { pub fn focus_out(&mut self) {
self.last_mouse_position = None;
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) { if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[O".to_string()); self.write_to_pty("\x1b[O".to_string());
} }
@ -1395,44 +1400,48 @@ impl Terminal {
self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift
} }
pub fn mouse_move(&mut self, e: &MouseMoveEvent, origin: Point<Pixels>) { pub fn mouse_move(&mut self, e: &MouseMoveEvent, cx: &mut Context<Self>) {
let position = e.position - origin; let position = e.position - self.last_content.terminal_bounds.bounds.origin;
self.last_mouse_position = Some(position);
if self.mouse_mode(e.modifiers.shift) { if self.mouse_mode(e.modifiers.shift) {
let (point, side) = grid_point_and_side( let (point, side) = grid_point_and_side(
position, position,
self.last_content.size, self.last_content.terminal_bounds,
self.last_content.display_offset, self.last_content.display_offset,
); );
if self.mouse_changed(point, side) { 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); self.pty_tx.notify(bytes);
} }
} }
} else if self.secondary_pressed { } else if e.modifiers.secondary() {
self.word_from_position(Some(position)); self.word_from_position(e.position);
} }
cx.notify();
} }
fn word_from_position(&mut self, position: Option<Point<Pixels>>) { fn word_from_position(&mut self, position: Point<Pixels>) {
if self.selection_phase == SelectionPhase::Selecting { if self.selection_phase == SelectionPhase::Selecting {
self.last_content.last_hovered_word = None; self.last_content.last_hovered_word = None;
} else if let Some(position) = position { } else if self.last_content.terminal_bounds.bounds.contains(&position) {
self.events self.events.push_back(InternalEvent::FindHyperlink(
.push_back(InternalEvent::FindHyperlink(position, false)); position - self.last_content.terminal_bounds.bounds.origin,
false,
));
} else {
self.last_content.last_hovered_word = None;
} }
} }
pub fn mouse_drag( pub fn mouse_drag(
&mut self, &mut self,
e: &MouseMoveEvent, e: &MouseMoveEvent,
origin: Point<Pixels>,
region: Bounds<Pixels>, region: Bounds<Pixels>,
cx: &mut Context<Self>,
) { ) {
let position = e.position - origin; let position = e.position - self.last_content.terminal_bounds.bounds.origin;
self.last_mouse_position = Some(position);
if !self.mouse_mode(e.modifiers.shift) { if !self.mouse_mode(e.modifiers.shift) {
self.selection_phase = SelectionPhase::Selecting; self.selection_phase = SelectionPhase::Selecting;
// Alacritty has the same ordering, of first updating the selection // Alacritty has the same ordering, of first updating the selection
@ -1447,18 +1456,21 @@ impl Terminal {
None => return, 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 self.events
.push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
} }
cx.notify();
} }
} }
fn drag_line_delta(&self, e: &MouseMoveEvent, region: Bounds<Pixels>) -> Option<Pixels> { fn drag_line_delta(&self, e: &MouseMoveEvent, region: Bounds<Pixels>) -> Option<Pixels> {
//TODO: Why do these need to be doubled? Probably the same problem that the IME has //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 top = region.origin.y + (self.last_content.terminal_bounds.line_height * 2.);
let bottom = region.bottom_left().y - (self.last_content.size.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 { let scroll_delta = if e.position.y < top {
(top - e.position.y).pow(1.1) (top - e.position.y).pow(1.1)
} else if e.position.y > bottom { } else if e.position.y > bottom {
@ -1469,16 +1481,11 @@ impl Terminal {
Some(scroll_delta) Some(scroll_delta)
} }
pub fn mouse_down( pub fn mouse_down(&mut self, e: &MouseDownEvent, _cx: &mut Context<Self>) {
&mut self, let position = e.position - self.last_content.terminal_bounds.bounds.origin;
e: &MouseDownEvent,
origin: Point<Pixels>,
_cx: &mut Context<Self>,
) {
let position = e.position - origin;
let point = grid_point( let point = grid_point(
position, position,
self.last_content.size, self.last_content.terminal_bounds,
self.last_content.display_offset, self.last_content.display_offset,
); );
@ -1491,10 +1498,9 @@ impl Terminal {
} else { } else {
match e.button { match e.button {
MouseButton::Left => { MouseButton::Left => {
let position = e.position - origin;
let (point, side) = grid_point_and_side( let (point, side) = grid_point_and_side(
position, position,
self.last_content.size, self.last_content.terminal_bounds,
self.last_content.display_offset, self.last_content.display_offset,
); );
@ -1526,14 +1532,14 @@ impl Terminal {
} }
} }
pub fn mouse_up(&mut self, e: &MouseUpEvent, origin: Point<Pixels>, cx: &Context<Self>) { pub fn mouse_up(&mut self, e: &MouseUpEvent, cx: &Context<Self>) {
let setting = TerminalSettings::get_global(cx); 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) { if self.mouse_mode(e.modifiers.shift) {
let point = grid_point( let point = grid_point(
position, position,
self.last_content.size, self.last_content.terminal_bounds,
self.last_content.display_offset, self.last_content.display_offset,
); );
@ -1549,10 +1555,11 @@ impl Terminal {
//Hyperlinks //Hyperlinks
if self.selection_phase == SelectionPhase::Ended { 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() { if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
cx.open_url(link.uri()); cx.open_url(link.uri());
} else if self.secondary_pressed { } else if e.modifiers.secondary() {
self.events self.events
.push_back(InternalEvent::FindHyperlink(position, true)); .push_back(InternalEvent::FindHyperlink(position, true));
} }
@ -1564,14 +1571,14 @@ impl Terminal {
} }
///Scroll the terminal ///Scroll the terminal
pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point<Pixels>) { pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent) {
let mouse_mode = self.mouse_mode(e.shift); let mouse_mode = self.mouse_mode(e.shift);
if let Some(scroll_lines) = self.determine_scroll_lines(e, mouse_mode) { if let Some(scroll_lines) = self.determine_scroll_lines(e, mouse_mode) {
if mouse_mode { if mouse_mode {
let point = grid_point( let point = grid_point(
e.position - origin, e.position - self.last_content.terminal_bounds.bounds.origin,
self.last_content.size, self.last_content.terminal_bounds,
self.last_content.display_offset, self.last_content.display_offset,
); );
@ -1596,13 +1603,13 @@ impl Terminal {
} }
} }
fn refresh_hovered_word(&mut self) { fn refresh_hovered_word(&mut self, window: &Window) {
self.word_from_position(self.last_mouse_position); self.word_from_position(window.mouse_position());
} }
fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option<i32> { fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option<i32> {
let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER }; 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 { match e.touch_phase {
/* Reset scroll state on started */ /* Reset scroll state on started */
TouchPhase::Started => { TouchPhase::Started => {
@ -1619,7 +1626,7 @@ impl Terminal {
// Whenever we hit the edges, reset our stored scroll to 0 // Whenever we hit the edges, reset our stored scroll to 0
// so we can respond to changes in direction quickly // 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) 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> { pub fn task(&self) -> Option<&TaskState> {
self.task.as_ref() self.task.as_ref()
} }
@ -1899,12 +1902,12 @@ fn all_search_matches<'a, T>(
RegexIter::new(start, end, AlacDirection::Right, term, regex) RegexIter::new(start, end, AlacDirection::Right, term, regex)
} }
fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize { fn content_index_for_mouse(pos: Point<Pixels>, terminal_bounds: &TerminalBounds) -> usize {
let col = (pos.x / size.cell_width()).round() as usize; let col = (pos.x / terminal_bounds.cell_width()).round() as usize;
let clamped_col = min(col, size.columns() - 1); let clamped_col = min(col, terminal_bounds.columns() - 1);
let row = (pos.y / size.line_height()).round() as usize; let row = (pos.y / terminal_bounds.line_height()).round() as usize;
let clamped_row = min(row, size.screen_lines() - 1); let clamped_row = min(row, terminal_bounds.screen_lines() - 1);
clamped_row * size.columns() + clamped_col clamped_row * terminal_bounds.columns() + clamped_col
} }
/// Converts an 8 bit ANSI color to its GPUI equivalent. /// Converts an 8 bit ANSI color to its GPUI equivalent.
@ -1997,11 +2000,11 @@ mod tests {
index::{Column, Line, Point as AlacPoint}, index::{Column, Line, Point as AlacPoint},
term::cell::Cell, 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 rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng};
use crate::{ use crate::{
content_index_for_mouse, rgb_for_index, IndexedCell, TerminalContent, TerminalSize, content_index_for_mouse, rgb_for_index, IndexedCell, TerminalBounds, TerminalContent,
}; };
#[test] #[test]
@ -2023,12 +2026,15 @@ mod tests {
let viewport_cells = rng.gen_range(15..20); let viewport_cells = rng.gen_range(15..20);
let cell_size = rng.gen_range(5 * PRECISION..20 * PRECISION) as f32 / PRECISION as f32; 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), cell_width: Pixels::from(cell_size),
line_height: Pixels::from(cell_size), line_height: Pixels::from(cell_size),
size: size( bounds: bounds(
Pixels::from(cell_size * (viewport_cells as f32)), Point::default(),
Pixels::from(cell_size * (viewport_cells as f32)), 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), 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 mouse_cell = content.cells[content_index].c;
let real_cell = cells[row][col]; let real_cell = cells[row][col];
@ -2062,10 +2069,13 @@ mod tests {
fn test_mouse_to_cell_clamp() { fn test_mouse_to_cell_clamp() {
let mut rng = thread_rng(); let mut rng = thread_rng();
let size = crate::TerminalSize { let size = crate::TerminalBounds {
cell_width: Pixels::from(10.), cell_width: Pixels::from(10.),
line_height: 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); let cells = get_cells(size, &mut rng);
@ -2074,7 +2084,7 @@ mod tests {
assert_eq!( assert_eq!(
content.cells[content_index_for_mouse( content.cells[content_index_for_mouse(
point(Pixels::from(-10.), Pixels::from(-10.)), point(Pixels::from(-10.), Pixels::from(-10.)),
&content.size, &content.terminal_bounds,
)] )]
.c, .c,
cells[0][0] cells[0][0]
@ -2082,14 +2092,14 @@ mod tests {
assert_eq!( assert_eq!(
content.cells[content_index_for_mouse( content.cells[content_index_for_mouse(
point(Pixels::from(1000.), Pixels::from(1000.)), point(Pixels::from(1000.), Pixels::from(1000.)),
&content.size, &content.terminal_bounds,
)] )]
.c, .c,
cells[9][9] cells[9][9]
); );
} }
fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec<Vec<char>> { fn get_cells(size: TerminalBounds, rng: &mut ThreadRng) -> Vec<Vec<char>> {
let mut cells = Vec::new(); let mut cells = Vec::new();
for _ in 0..((size.height() / size.line_height()) as usize) { for _ in 0..((size.height() / size.line_height()) as usize) {
@ -2104,7 +2114,10 @@ mod tests {
cells cells
} }
fn convert_cells_to_content(size: TerminalSize, cells: &[Vec<char>]) -> TerminalContent { fn convert_cells_to_content(
terminal_bounds: TerminalBounds,
cells: &[Vec<char>],
) -> TerminalContent {
let mut ic = Vec::new(); let mut ic = Vec::new();
for (index, row) in cells.iter().enumerate() { for (index, row) in cells.iter().enumerate() {
@ -2121,7 +2134,7 @@ mod tests {
TerminalContent { TerminalContent {
cells: ic, cells: ic,
size, terminal_bounds,
..Default::default() ..Default::default()
} }
} }

View file

@ -21,7 +21,7 @@ use terminal::{
}, },
}, },
terminal_settings::TerminalSettings, terminal_settings::TerminalSettings,
HoveredWord, IndexedCell, Terminal, TerminalContent, TerminalSize, HoveredWord, IndexedCell, Terminal, TerminalBounds, TerminalContent,
}; };
use theme::{ActiveTheme, Theme, ThemeSettings}; use theme::{ActiveTheme, Theme, ThemeSettings};
use ui::{ParentElement, Tooltip}; use ui::{ParentElement, Tooltip};
@ -40,7 +40,7 @@ pub struct LayoutState {
relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>, relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
cursor: Option<CursorLayout>, cursor: Option<CursorLayout>,
background_color: Hsla, background_color: Hsla,
dimensions: TerminalSize, dimensions: TerminalBounds,
mode: TermMode, mode: TermMode,
display_offset: usize, display_offset: usize,
hyperlink_tooltip: Option<AnyElement>, hyperlink_tooltip: Option<AnyElement>,
@ -86,7 +86,7 @@ impl LayoutCell {
pub fn paint( pub fn paint(
&self, &self,
origin: Point<Pixels>, origin: Point<Pixels>,
dimensions: &TerminalSize, dimensions: &TerminalBounds,
_visible_bounds: Bounds<Pixels>, _visible_bounds: Bounds<Pixels>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@ -130,7 +130,7 @@ impl LayoutRect {
} }
} }
pub fn paint(&self, origin: Point<Pixels>, dimensions: &TerminalSize, window: &mut Window) { pub fn paint(&self, origin: Point<Pixels>, dimensions: &TerminalBounds, window: &mut Window) {
let position = { let position = {
let alac_point = self.point; let alac_point = self.point;
point( point(
@ -313,7 +313,7 @@ impl TerminalElement {
/// the same position for sequential indexes. Use em_width instead /// the same position for sequential indexes. Use em_width instead
fn shape_cursor( fn shape_cursor(
cursor_point: DisplayCursor, cursor_point: DisplayCursor,
size: TerminalSize, size: TerminalBounds,
text_fragment: &ShapedLine, text_fragment: &ShapedLine,
) -> Option<(Point<Pixels>, Pixels)> { ) -> Option<(Point<Pixels>, Pixels)> {
if cursor_point.line() < size.total_lines() as i32 { if cursor_point.line() < size.total_lines() as i32 {
@ -412,27 +412,20 @@ impl TerminalElement {
fn generic_button_handler<E>( fn generic_button_handler<E>(
connection: Entity<Terminal>, connection: Entity<Terminal>,
origin: Point<Pixels>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
f: impl Fn(&mut Terminal, Point<Pixels>, &E, &mut Context<Terminal>), f: impl Fn(&mut Terminal, &E, &mut Context<Terminal>),
) -> impl Fn(&E, &mut Window, &mut App) { ) -> impl Fn(&E, &mut Window, &mut App) {
move |event, window, cx| { move |event, window, cx| {
window.focus(&focus_handle); window.focus(&focus_handle);
connection.update(cx, |terminal, cx| { connection.update(cx, |terminal, cx| {
f(terminal, origin, event, cx); f(terminal, event, cx);
cx.notify(); cx.notify();
}) })
} }
} }
fn register_mouse_listeners( fn register_mouse_listeners(&mut self, mode: TermMode, hitbox: &Hitbox, window: &mut Window) {
&mut self,
origin: Point<Pixels>,
mode: TermMode,
hitbox: &Hitbox,
window: &mut Window,
) {
let focus = self.focus.clone(); let focus = self.focus.clone();
let terminal = self.terminal.clone(); let terminal = self.terminal.clone();
@ -442,29 +435,26 @@ impl TerminalElement {
move |e, window, cx| { move |e, window, cx| {
window.focus(&focus); window.focus(&focus);
terminal.update(cx, |terminal, cx| { terminal.update(cx, |terminal, cx| {
terminal.mouse_down(e, origin, cx); terminal.mouse_down(e, cx);
cx.notify(); cx.notify();
}) })
} }
}); });
window.on_mouse_event({ window.on_mouse_event({
let focus = self.focus.clone();
let terminal = self.terminal.clone(); let terminal = self.terminal.clone();
let hitbox = hitbox.clone(); let hitbox = hitbox.clone();
let focus = focus.clone();
move |e: &MouseMoveEvent, phase, window, cx| { move |e: &MouseMoveEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble || !focus.is_focused(window) { if phase != DispatchPhase::Bubble {
return; 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); let hovered = hitbox.is_hovered(window);
terminal.update(cx, |terminal, cx| { terminal.update(cx, |terminal, cx| {
if terminal.selection_started() { if terminal.selection_started() || hovered {
terminal.mouse_drag(e, origin, hitbox.bounds); terminal.mouse_drag(e, hitbox.bounds, cx);
cx.notify();
} else if hovered {
terminal.mouse_drag(e, origin, hitbox.bounds);
cx.notify(); cx.notify();
} }
}) })
@ -472,8 +462,7 @@ impl TerminalElement {
if hitbox.is_hovered(window) { if hitbox.is_hovered(window) {
terminal.update(cx, |terminal, cx| { terminal.update(cx, |terminal, cx| {
terminal.mouse_move(e, origin); terminal.mouse_move(e, cx);
cx.notify();
}) })
} }
} }
@ -483,10 +472,9 @@ impl TerminalElement {
MouseButton::Left, MouseButton::Left,
TerminalElement::generic_button_handler( TerminalElement::generic_button_handler(
terminal.clone(), terminal.clone(),
origin,
focus.clone(), focus.clone(),
move |terminal, origin, e, cx| { move |terminal, e, cx| {
terminal.mouse_up(e, origin, cx); terminal.mouse_up(e, cx);
}, },
), ),
); );
@ -494,10 +482,9 @@ impl TerminalElement {
MouseButton::Middle, MouseButton::Middle,
TerminalElement::generic_button_handler( TerminalElement::generic_button_handler(
terminal.clone(), terminal.clone(),
origin,
focus.clone(), focus.clone(),
move |terminal, origin, e, cx| { move |terminal, e, cx| {
terminal.mouse_down(e, origin, cx); terminal.mouse_down(e, cx);
}, },
), ),
); );
@ -506,7 +493,7 @@ impl TerminalElement {
move |e, _, cx| { move |e, _, cx| {
terminal_view terminal_view
.update(cx, |terminal_view, cx| { .update(cx, |terminal_view, cx| {
terminal_view.scroll_wheel(e, origin, cx); terminal_view.scroll_wheel(e, cx);
cx.notify(); cx.notify();
}) })
.ok(); .ok();
@ -520,10 +507,9 @@ impl TerminalElement {
MouseButton::Right, MouseButton::Right,
TerminalElement::generic_button_handler( TerminalElement::generic_button_handler(
terminal.clone(), terminal.clone(),
origin,
focus.clone(), focus.clone(),
move |terminal, origin, e, cx| { move |terminal, e, cx| {
terminal.mouse_down(e, origin, cx); terminal.mouse_down(e, cx);
}, },
), ),
); );
@ -531,23 +517,17 @@ impl TerminalElement {
MouseButton::Right, MouseButton::Right,
TerminalElement::generic_button_handler( TerminalElement::generic_button_handler(
terminal.clone(), terminal.clone(),
origin,
focus.clone(), focus.clone(),
move |terminal, origin, e, cx| { move |terminal, e, cx| {
terminal.mouse_up(e, origin, cx); terminal.mouse_up(e, cx);
}, },
), ),
); );
self.interactivity.on_mouse_up( self.interactivity.on_mouse_up(
MouseButton::Middle, MouseButton::Middle,
TerminalElement::generic_button_handler( TerminalElement::generic_button_handler(terminal, focus, move |terminal, e, cx| {
terminal, terminal.mouse_up(e, cx);
origin, }),
focus,
move |terminal, origin, e, cx| {
terminal.mouse_up(e, origin, cx);
},
),
); );
} }
} }
@ -705,7 +685,10 @@ impl Element for TerminalElement {
size.width = cell_width * 2.0; 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(); 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| { let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
terminal.set_size(dimensions); terminal.set_size(dimensions);
terminal.sync(cx); terminal.sync(window, cx);
if self.can_navigate_to_selected_word 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() terminal.last_content.last_hovered_word.clone()
} else { } else {
@ -898,7 +883,7 @@ impl Element for TerminalElement {
workspace: self.workspace.clone(), 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() { if self.can_navigate_to_selected_word && layout.last_hovered_word.is_some() {
window.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox); window.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox);
} else { } else {
@ -924,12 +909,9 @@ impl Element for TerminalElement {
return; return;
} }
let handled = this this.update(cx, |term, cx| {
.update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); term.try_modifiers_change(&event.modifiers, window, cx)
});
if handled {
window.refresh();
}
} }
}); });

View file

@ -19,7 +19,7 @@ struct ScrollHandleState {
impl ScrollHandleState { impl ScrollHandleState {
fn new(terminal: &Terminal) -> Self { fn new(terminal: &Terminal) -> Self {
Self { Self {
line_height: terminal.last_content().size.line_height, line_height: terminal.last_content().terminal_bounds.line_height,
total_lines: terminal.total_lines(), total_lines: terminal.total_lines(),
viewport_lines: terminal.viewport_lines(), viewport_lines: terminal.viewport_lines(),
display_offset: terminal.last_content().display_offset, display_offset: terminal.last_content().display_offset,

View file

@ -23,7 +23,7 @@ use terminal::{
terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory},
Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown, Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown,
ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal,
TerminalSize, ToggleViMode, TerminalBounds, ToggleViMode,
}; };
use terminal_element::{is_blank, TerminalElement}; use terminal_element::{is_blank, TerminalElement};
use terminal_panel::TerminalPanel; use terminal_panel::TerminalPanel;
@ -101,7 +101,7 @@ pub struct BlockProperties {
pub struct BlockContext<'a, 'b> { pub struct BlockContext<'a, 'b> {
pub window: &'a mut Window, pub window: &'a mut Window,
pub context: &'b mut App, 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 ///A terminal view, maintains the PTY's file handles and communicates with the terminal
@ -342,7 +342,7 @@ impl TerminalView {
return Pixels::ZERO; 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 mut terminal_lines = terminal.total_lines();
let viewport_lines = terminal.viewport_lines(); let viewport_lines = terminal.viewport_lines();
if terminal.total_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 max_scroll_top_in_lines as f32 * line_height
} }
fn scroll_wheel( fn scroll_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context<Self>) {
&mut self,
event: &ScrollWheelEvent,
origin: gpui::Point<Pixels>,
cx: &mut Context<Self>,
) {
let terminal_content = self.terminal.read(cx).last_content(); let terminal_content = self.terminal.read(cx).last_content();
if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { 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; let y_delta = event.delta.pixel_delta(line_height).y;
if y_delta < Pixels::ZERO || self.scroll_top > Pixels::ZERO { if y_delta < Pixels::ZERO || self.scroll_top > Pixels::ZERO {
self.scroll_top = cmp::max( self.scroll_top = cmp::max(
@ -387,8 +382,7 @@ impl TerminalView {
} }
} }
self.terminal self.terminal.update(cx, |term, _| term.scroll_wheel(event));
.update(cx, |term, _| term.scroll_wheel(event, origin));
} }
fn scroll_line_up(&mut self, _: &ScrollLineUp, _: &mut Window, cx: &mut Context<Self>) { fn scroll_line_up(&mut self, _: &ScrollLineUp, _: &mut Window, cx: &mut Context<Self>) {
@ -397,7 +391,7 @@ impl TerminalView {
&& terminal_content.display_offset == 0 && terminal_content.display_offset == 0
&& self.scroll_top > Pixels::ZERO && 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); self.scroll_top = cmp::max(self.scroll_top - line_height, Pixels::ZERO);
return; return;
} }
@ -411,7 +405,7 @@ impl TerminalView {
if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 {
let max_scroll_top = self.max_scroll_top(cx); let max_scroll_top = self.max_scroll_top(cx);
if self.scroll_top < max_scroll_top { 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); self.scroll_top = cmp::min(self.scroll_top + line_height, max_scroll_top);
} }
return; return;
@ -425,7 +419,12 @@ impl TerminalView {
if self.scroll_top == Pixels::ZERO { if self.scroll_top == Pixels::ZERO {
self.terminal.update(cx, |term, _| term.scroll_page_up()); self.terminal.update(cx, |term, _| term.scroll_page_up());
} else { } 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 visible_block_lines = (self.scroll_top / line_height) as usize;
let viewport_lines = self.terminal.read(cx).viewport_lines(); let viewport_lines = self.terminal.read(cx).viewport_lines();
let visible_content_lines = viewport_lines - visible_block_lines; let visible_content_lines = viewport_lines - visible_block_lines;
@ -866,7 +865,8 @@ fn subscribe_for_terminal_events(
} }
} }
None => false, None => false,
} };
cx.notify()
} }
Event::Open(maybe_navigation_target) => match maybe_navigation_target { Event::Open(maybe_navigation_target) => match maybe_navigation_target {