diff options
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(); |