WIP hyperlink searching

Mikayla Maki created

Change summary

crates/terminal/src/terminal.rs | 53 +++++++++++++++++++++++++++-------
1 file changed, 42 insertions(+), 11 deletions(-)

Detailed changes

crates/terminal/src/terminal.rs 🔗

@@ -14,7 +14,7 @@ use alacritty_terminal::{
     selection::{Selection, SelectionRange, SelectionType},
     sync::FairMutex,
     term::{
-        cell::{Cell, Hyperlink},
+        cell::Cell,
         color::Rgb,
         search::{Match, RegexIter, RegexSearch},
         RenderableCursor, TermMode,
@@ -43,7 +43,7 @@ use std::{
     collections::{HashMap, VecDeque},
     fmt::Display,
     io,
-    ops::{Deref, Range, RangeInclusive, Sub},
+    ops::{Deref, RangeInclusive, Sub},
     os::unix::{prelude::AsRawFd, process::CommandExt},
     path::PathBuf,
     process::Command,
@@ -77,16 +77,14 @@ pub fn init(cx: &mut MutableAppContext) {
 ///Scroll multiplier that is set to 3 by default. This will be removed when I
 ///Implement scroll bars.
 const SCROLL_MULTIPLIER: f32 = 4.;
-// const MAX_SEARCH_LINES: usize = 100;
+const MAX_SEARCH_LINES: usize = 100;
 const DEBUG_TERMINAL_WIDTH: f32 = 500.;
 const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
 const DEBUG_CELL_WIDTH: f32 = 5.;
 const DEBUG_LINE_HEIGHT: f32 = 5.;
 
 /// Copied from alacritty's ui_config.rs
-const URL_REGEX: &str =
-    "(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\
-                         [^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+";
+static URL_REGEX: RegexSearch = RegexSearch::new("(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+").unwrap();
 
 ///Upward flowing events, for changing the title and such
 #[derive(Clone, Copy, Debug)]
@@ -500,7 +498,7 @@ pub struct Terminal {
     pub matches: Vec<RangeInclusive<Point>>,
     last_content: TerminalContent,
     last_synced: Instant,
-    last_hovered_hyperlink: Option<(Hyperlink, Range<Point>)>,
+    last_hovered_hyperlink: Option<(String, RangeInclusive<Point>)>,
     sync_task: Option<Task<()>>,
     selection_head: Option<Point>,
     breadcrumb_text: String,
@@ -651,14 +649,23 @@ impl Terminal {
             }
             InternalEvent::ScrollToPoint(point) => term.scroll_to_point(*point),
             InternalEvent::Hyperlink(position, open) => {
+                self.last_hovered_hyperlink = None;
+
                 let point = grid_point(
                     *position,
                     self.last_content.size,
                     term.grid().display_offset(),
                 );
-                let side = mouse_side(*position, self.last_content.size);
 
-                println!("Hyperlink hover | click ")
+                if let Some(url_match) = regex_match_at(term, point, &URL_REGEX) {
+                    let url = term.bounds_to_string(*url_match.start(), *url_match.end());
+
+                    if *open {
+                        open_uri(&url).log_err();
+                    } else {
+                        self.last_hovered_hyperlink = Some((url, url_match));
+                    }
+                }
             }
         }
     }
@@ -872,9 +879,9 @@ impl Terminal {
 
                 self.last_hovered_hyperlink = link.map(|link| {
                     (
-                        link,
+                        link.uri().to_owned(),
                         self.last_content.cells[min_index].point
-                            ..self.last_content.cells[max_index].point,
+                            ..=self.last_content.cells[max_index].point,
                     )
                 });
             } else {
@@ -1112,6 +1119,30 @@ impl Entity for Terminal {
     type Event = Event;
 }
 
+/// Based on alacritty/src/display/hint.rs > regex_match_at
+/// Retrieve the match, if the specified point is inside the content matching the regex.
+fn regex_match_at<T>(term: &Term<T>, point: Point, regex: &RegexSearch) -> Option<Match> {
+    visible_regex_match_iter(term, regex).find(|rm| rm.contains(&point))
+}
+
+/// Copied from alacritty/src/display/hint.rs:
+/// Iterate over all visible regex matches.
+pub fn visible_regex_match_iter<'a, T>(
+    term: &'a Term<T>,
+    regex: &'a RegexSearch,
+) -> impl Iterator<Item = Match> + 'a {
+    let viewport_start = Line(-(term.grid().display_offset() as i32));
+    let viewport_end = viewport_start + term.bottommost_line();
+    let mut start = term.line_search_left(Point::new(viewport_start, Column(0)));
+    let mut end = term.line_search_right(Point::new(viewport_end, Column(0)));
+    start.line = start.line.max(viewport_start - MAX_SEARCH_LINES);
+    end.line = end.line.min(viewport_end + MAX_SEARCH_LINES);
+
+    RegexIter::new(start, end, AlacDirection::Right, term, regex)
+        .skip_while(move |rm| rm.end().line < viewport_start)
+        .take_while(move |rm| rm.start().line <= viewport_end)
+}
+
 fn make_selection(range: &RangeInclusive<Point>) -> Selection {
     let mut selection = Selection::new(SelectionType::Simple, *range.start(), AlacDirection::Left);
     selection.update(*range.end(), AlacDirection::Right);