Repository: alacritty/alacritty (language: rust)

Issue:
hints.enabled action="Select" mouse.enabled=true results in selection of the whole line (instead of just the hint)
Using the following configuration snippet:
```
#Select hint text
[[hints.enabled]]
action          = "Select"
hyperlinks      = true
mouse.enabled   = true
#mouse.mods      = "Alt"
```
The Select action select the whole line containing the hint, not just the hint text. 
For example:
If I do something like `ls -l --hyperlink=auto` only the files get underlined as I hover over them, but when clicked the whole line is selected.

This doesn't change even if I uncoment the `mouse.mods` line.

Is this the intended behavior?

The man 5 alacritty states:
```
Select
                   Select the hint's text.
```
So I would expect only the hint's text to be selected.

If it's not a bug / the current behavior is already established, would you consider something like "SelectHintOnly" action?


### System

OS: Linux (Fedora 38)
Version: alacritty 0.13.1
Linux/BSD: X11, KDE5 / KWin



Discussion / hints:
Seems like this issue is specifically due to the mouse being used to select the hint. It works just fine with the keyboard. It also works just fine for regex hints.
Still would be nice if it could be fixed. I'm trying to optimize some workflows and being able to select/copy stuff like filenames with a single click (even when they contain spaces etc.) would be really nice. Maybe a `CopyText` action that would copy the displayed hint text instead of the hyperlink could be even nicer.  Also the docs could probably use a little bit of clarification: now the description for `Copy` action states: `Copy the hint's text to the clipboard.` which in case of a hyperlink is a little misleading/ambiguous. 
Also, as I understand, currently the `mouse.mods`  option serves two distinct purposes and any of the `mouse.mods` options need to be satisfied for the hints to be highlighted (underlined) on mouse-over. So maybe a special `NoOp` action could be introduced for people that want the mouse-over highlighting always-on but require a modifier to invoke any of the actions / commands (yes I know I could introduce a no-op command as a workaround).
> So maybe a special NoOp action could be introduced for people that want the mouse-over highlighting always-on but require a modifier to invoke any of the actions / commands (yes I know I could introduce a no-op command as a workaround).

This is not something I'm interested in. The highlight shows users when they can interact with a link, so underlining without the ability to click it would just be misleading and awkward.
> This is not something I'm interested in. The highlight shows users when they can interact with a link, so underlining without the ability to click it would just be misleading and awkward.

I would agree in case of regex hints (where I know/control how I set them up), but in case of hyperlinks some distinction could be really useful. If I use a new/unfamiliar program that uses hyperlinks, there should  be some way to discover they are there (different styling / mouse cursor / whatever). Think of it as a web page where all the links are styled just as normal text, no cursor change on hover and you need a special keyboard combo to click-though them.

On the other hand, leaving the default no-modifier hyperlink activation on could be a security nightmare (a random click into the terminal could get you owned).

It's not a big deal, I have the "Select" action set up right now for no-mods, and if no action is required one could do something like a `/bin/true` command instead to get just the highlighting. I was just considering all the uses cases.

Hints/hyperlinks are a very nice feature, but one should take care to set them up properly.
> If I use a new/unfamiliar program that uses hyperlinks, there should be some way to discover they are there 

It is up to the application to make that clear to you. If they want to underline their hyperlinks, they're easily capable of doing so. If they don't, they don't want them to be highlighted.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f536186128a..63f2fe9644f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mouse cursor not changing on Wayland when cursor theme uses legacy cursor icon names
 - Config keys are available under proper names
 - Build failure when compiling with x11 feature on NetBSD
+- Hint `Select` action selecting the entire line for URL escapes
 
 ### Changed
 
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs
index fee2680f720..d5e24b4abe6 100644
--- a/alacritty/src/cli.rs
+++ b/alacritty/src/cli.rs
@@ -5,7 +5,7 @@ use std::rc::Rc;
 
 use alacritty_config::SerdeReplace;
 use clap::{ArgAction, Args, Parser, Subcommand, ValueHint};
-use log::{self, error, LevelFilter};
+use log::{error, LevelFilter};
 use serde::{Deserialize, Serialize};
 use toml::Value;
 
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
index 21059734f55..a4b6c2c5e9f 100644
--- a/alacritty/src/config/ui_config.rs
+++ b/alacritty/src/config/ui_config.rs
@@ -10,7 +10,7 @@ use alacritty_terminal::term::Config as TermConfig;
 use alacritty_terminal::tty::{Options as PtyOptions, Shell};
 use log::{error, warn};
 use serde::de::{Error as SerdeError, MapAccess, Visitor};
-use serde::{self, Deserialize, Deserializer};
+use serde::{Deserialize, Deserializer};
 use unicode_width::UnicodeWidthChar;
 use winit::keyboard::{Key, ModifiersState};
 
diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs
index bd09a881061..f118dbe090f 100644
--- a/alacritty/src/display/hint.rs
+++ b/alacritty/src/display/hint.rs
@@ -204,7 +204,8 @@ pub struct HintMatch {
 impl HintMatch {
     #[inline]
     pub fn should_highlight(&self, point: Point, pointed_hyperlink: Option<&Hyperlink>) -> bool {
-        self.bounds.contains(&point) && self.hyperlink.as_ref() == pointed_hyperlink
+        self.hyperlink.as_ref() == pointed_hyperlink
+            && (self.hyperlink.is_some() || self.bounds.contains(&point))
     }
 
     #[inline]
@@ -400,49 +401,30 @@ pub fn highlighted_at<T>(
 }
 
 /// Retrieve the hyperlink with its range, if there is one at the specified point.
+///
+/// This will only return contiguous cells, even if another hyperlink with the same ID exists.
 fn hyperlink_at<T>(term: &Term<T>, point: Point) -> Option<(Hyperlink, Match)> {
     let hyperlink = term.grid()[point].hyperlink()?;
 
-    let viewport_start = Line(-(term.grid().display_offset() as i32));
-    let viewport_end = viewport_start + term.bottommost_line();
-
-    let mut match_start = Point::new(point.line, Column(0));
-    let mut match_end = Point::new(point.line, Column(term.columns() - 1));
     let grid = term.grid();
 
-    // Find adjacent lines that have the same `hyperlink`. The end purpose to highlight hyperlinks
-    // that span across multiple lines or not directly attached to each other.
-
-    // Find the closest to the viewport start adjacent line.
-    while match_start.line > viewport_start {
-        let next_line = match_start.line - 1i32;
-        // Iterate over all the cells in the grid's line and check if any of those cells contains
-        // the hyperlink we've found at original `point`.
-        let line_contains_hyperlink = grid[next_line]
-            .into_iter()
-            .any(|cell| cell.hyperlink().map_or(false, |h| h == hyperlink));
-
-        // There's no hyperlink on the next line, break.
-        if !line_contains_hyperlink {
+    let mut match_end = point;
+    for cell in grid.iter_from(point) {
+        if cell.hyperlink().map_or(false, |link| link == hyperlink) {
+            match_end = cell.point;
+        } else {
             break;
         }
-
-        match_start.line = next_line;
     }
 
-    // Ditto for the end.
-    while match_end.line < viewport_end {
-        let next_line = match_end.line + 1i32;
-
-        let line_contains_hyperlink = grid[next_line]
-            .into_iter()
-            .any(|cell| cell.hyperlink().map_or(false, |h| h == hyperlink));
-
-        if !line_contains_hyperlink {
+    let mut match_start = point;
+    let mut iter = grid.iter_from(point);
+    while let Some(cell) = iter.prev() {
+        if cell.hyperlink().map_or(false, |link| link == hyperlink) {
+            match_start = cell.point;
+        } else {
             break;
         }
-
-        match_end.line = next_line;
     }
 
     Some((hyperlink, match_start..=match_end))
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index dc37794d9c0..4dafa80fac4 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -20,7 +20,7 @@ use winit::dpi::PhysicalSize;
 use winit::keyboard::ModifiersState;
 use winit::window::CursorIcon;
 
-use crossfont::{self, Rasterize, Rasterizer, Size as FontSize};
+use crossfont::{Rasterize, Rasterizer, Size as FontSize};
 use unicode_width::UnicodeWidthChar;
 
 use alacritty_terminal::event::{EventListener, OnResize, WindowSize};
diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs
index 42f1536ef3b..a3833a5bab1 100644
--- a/alacritty/src/logging.rs
+++ b/alacritty/src/logging.rs
@@ -12,7 +12,7 @@ use std::sync::{Arc, Mutex, OnceLock};
 use std::time::Instant;
 use std::{env, process};
 
-use log::{self, Level, LevelFilter};
+use log::{Level, LevelFilter};
 use winit::event_loop::EventLoopProxy;
 
 use crate::cli::Options;
diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs
index 929c642d829..62dd7440239 100644
--- a/alacritty_terminal/src/event_loop.rs
+++ b/alacritty_terminal/src/event_loop.rs
@@ -5,7 +5,6 @@ use std::collections::VecDeque;
 use std::fmt::{self, Display, Formatter};
 use std::fs::File;
 use std::io::{self, ErrorKind, Read, Write};
-use std::marker::Send;
 use std::num::NonZeroUsize;
 use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
 use std::sync::Arc;
diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs
index d5709d14e6b..0a2be43b71a 100644
--- a/alacritty_terminal/src/grid/storage.rs
+++ b/alacritty_terminal/src/grid/storage.rs
@@ -1,4 +1,4 @@
-use std::cmp::{max, PartialEq};
+use std::cmp::max;
 use std::mem;
 use std::mem::MaybeUninit;
 use std::ops::{Index, IndexMut};
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 2ed6d7c994c..f17f746252b 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -2389,10 +2389,8 @@ pub mod test {
 
     #[cfg(feature = "serde")]
     use serde::{Deserialize, Serialize};
-    use unicode_width::UnicodeWidthChar;
 
     use crate::event::VoidListener;
-    use crate::index::Column;
 
     #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
     pub struct TermSize {
diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs
index 8ce04191266..455abbd2f84 100644
--- a/alacritty_terminal/src/tty/unix.rs
+++ b/alacritty_terminal/src/tty/unix.rs
@@ -11,7 +11,7 @@ use std::process::{Child, Command, Stdio};
 use std::sync::Arc;
 use std::{env, ptr};
 
-use libc::{self, c_int, TIOCSCTTY};
+use libc::{c_int, TIOCSCTTY};
 use log::error;
 use polling::{Event, PollMode, Poller};
 use rustix_openpty::openpty;


Test patch: