diff options
author | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-03-19 19:14:17 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-19 19:14:17 +0000 |
commit | a672f7d553ac17d5aaef8dc667dee31d5815677d (patch) | |
tree | 0289e3deb60e3fdce6025fded1ec9c3299bc893d /src/input.rs | |
parent | eb7a1ea803ba54d3b8cd6af49255eb8fbe0d7544 (diff) | |
download | r-alacritty-a672f7d553ac17d5aaef8dc667dee31d5815677d.tar.gz r-alacritty-a672f7d553ac17d5aaef8dc667dee31d5815677d.tar.bz2 r-alacritty-a672f7d553ac17d5aaef8dc667dee31d5815677d.zip |
Add URL hover highlighting
This changes the cursor whenever it moves to a cell which contains
part of a URL.
When a URL is hovered over, all characters that are recognized as part
of the URL will be underlined and the mouse cursor shape will be
changed. After the cursor leaves the URL, the previous hover state is
restored.
This also changes the behavior when clicking an illegal character right
in front of a URL. Previously this would still launch the URL, but strip
the illegal character. Now these clicks are ignored to make sure there's
no mismatch between underline and legal URL click positions
Diffstat (limited to 'src/input.rs')
-rw-r--r-- | src/input.rs | 108 |
1 files changed, 97 insertions, 11 deletions
diff --git a/src/input.rs b/src/input.rs index 32e4dcbd..8e72c8b7 100644 --- a/src/input.rs +++ b/src/input.rs @@ -21,20 +21,26 @@ use std::borrow::Cow; use std::mem; use std::time::Instant; +use std::iter::once; use copypasta::{Clipboard, Load, Buffer as ClipboardBuffer}; -use glutin::{ElementState, MouseButton, TouchPhase, MouseScrollDelta, ModifiersState, KeyboardInput}; +use glutin::{ + ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta, + TouchPhase, +}; use crate::config::{self, Key}; use crate::grid::Scroll; -use crate::event::{ClickState, Mouse}; +use crate::event::{ClickState, Mouse, UrlHoverSaveState}; use crate::index::{Line, Column, Side, Point}; use crate::term::{Term, SizeInfo, Search}; use crate::term::mode::TermMode; +use crate::term::cell::Flags; use crate::util::fmt::Red; use crate::util::start_daemon; use crate::message_bar::{self, Message}; use crate::ansi::{Handler, ClearMode}; +use crate::url::Url; pub const FONT_SIZE_STEP: f32 = 0.5; @@ -385,19 +391,23 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode; - // Don't launch URLs if mouse has moved - if prev_line != self.ctx.mouse().line + let mouse_moved = prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column - || prev_side != cell_side - { + || prev_side != cell_side; + + // Don't launch URLs if mouse has moved + if mouse_moved { self.ctx.mouse_mut().block_url_launcher = true; } - // Ignore motions over the message bar - if self.message_at_point(Some(point)).is_some() { + // Only report motions when cell changed and mouse is not over the message bar + if self.message_at_point(Some(point)).is_some() || !mouse_moved { return; } + // Underline URLs and change cursor on hover + self.update_url_highlight(point, modifiers); + if self.ctx.mouse().left_button_state == ElementState::Pressed && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode)) { @@ -409,8 +419,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { cell_side, ); } else if self.ctx.terminal().mode().intersects(motion_mode) - // Only report motion when changing cells - && (prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column) && size_info.contains_point(x, y, false) { if self.ctx.mouse().left_button_state == ElementState::Pressed { @@ -425,6 +433,84 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } + /// Underline URLs and change the mouse cursor when URL hover state changes. + fn update_url_highlight(&mut self, point: Point, modifiers: ModifiersState) { + let mouse_mode = + TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; + + // Only show URLs as launchable when all required modifiers are pressed + let url = if self.mouse_config.url.modifiers.relaxed_eq(modifiers) + && (!self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN) || modifiers.shift) + { + self.ctx.terminal().url_search(point.into()) + } else { + None + }; + + if let Some(Url { text, origin }) = url { + let mouse_cursor = if self.ctx.terminal().mode().intersects(mouse_mode) { + MouseCursor::Default + } else { + MouseCursor::Text + }; + + let cols = self.ctx.size_info().cols().0; + let last_line = self.ctx.size_info().lines().0 - 1; + + // Calculate the URL's start position + let col = (cols + point.col.0 - origin % cols) % cols; + let line = last_line - point.line.0 + (origin + cols - point.col.0 - 1) / cols; + let start = Point::new(line, Column(col)); + + // Update URLs only on change, so they don't all get marked as underlined + if self.ctx.mouse().url_hover_save.as_ref().map(|hs| hs.start) == Some(start) { + return; + } + + // Since the URL changed without reset, we need to clear the previous underline + if let Some(hover_save) = self.ctx.mouse_mut().url_hover_save.take() { + self.reset_underline(&hover_save); + } + + // Underline all cells and store their current underline state + let mut underlined = Vec::with_capacity(text.len()); + let iter = once(start).chain(start.iter(Column(cols - 1), last_line)); + for point in iter.take(text.len()) { + let cell = &mut self.ctx.terminal_mut().grid_mut()[point.line][point.col]; + underlined.push(cell.flags.contains(Flags::UNDERLINE)); + cell.flags.insert(Flags::UNDERLINE); + } + + // Save the higlight state for restoring it again + self.ctx.mouse_mut().url_hover_save = Some(UrlHoverSaveState { + mouse_cursor, + underlined, + start, + }); + + self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand); + self.ctx.terminal_mut().dirty = true; + } else if let Some(hover_save) = self.ctx.mouse_mut().url_hover_save.take() { + self.ctx.terminal_mut().set_mouse_cursor(hover_save.mouse_cursor); + self.ctx.terminal_mut().dirty = true; + self.reset_underline(&hover_save); + } + } + + /// Reset the underline state after unhovering a URL. + fn reset_underline(&mut self, hover_save: &UrlHoverSaveState) { + let last_col = self.ctx.size_info().cols() - 1; + let last_line = self.ctx.size_info().lines().0 - 1; + + let mut iter = once(hover_save.start).chain(hover_save.start.iter(last_col, last_line)); + for underlined in &hover_save.underlined { + if let (Some(point), false) = (iter.next(), underlined) { + let cell = &mut self.ctx.terminal_mut().grid_mut()[point.line][point.col]; + cell.flags.remove(Flags::UNDERLINE); + } + } + } + fn get_mouse_side(&self) -> Side { let size_info = self.ctx.size_info(); let x = self.ctx.mouse().x; @@ -600,7 +686,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { return None; } - let text = self.ctx.terminal().url_search(point.into())?; + let text = self.ctx.terminal().url_search(point.into())?.text; let launcher = self.mouse_config.url.launcher.as_ref()?; let mut args = launcher.args().to_vec(); |