aboutsummaryrefslogtreecommitdiff
path: root/src/input.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/input.rs')
-rw-r--r--src/input.rs108
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();