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

@ -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<AlacPoint>, Hsla)>,
cursor: Option<CursorLayout>,
background_color: Hsla,
dimensions: TerminalSize,
dimensions: TerminalBounds,
mode: TermMode,
display_offset: usize,
hyperlink_tooltip: Option<AnyElement>,
@ -86,7 +86,7 @@ impl LayoutCell {
pub fn paint(
&self,
origin: Point<Pixels>,
dimensions: &TerminalSize,
dimensions: &TerminalBounds,
_visible_bounds: Bounds<Pixels>,
window: &mut Window,
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 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>, Pixels)> {
if cursor_point.line() < size.total_lines() as i32 {
@ -412,27 +412,20 @@ impl TerminalElement {
fn generic_button_handler<E>(
connection: Entity<Terminal>,
origin: Point<Pixels>,
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) {
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<Pixels>,
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)
});
}
});

View file

@ -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,

View file

@ -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<Pixels>,
cx: &mut Context<Self>,
) {
fn scroll_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context<Self>) {
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<Self>) {
@ -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 {