WIP hyperlink detection

Mikayla Maki created

Change summary

crates/terminal/src/terminal.rs         | 81 +++++++++++++++++++++++---
crates/terminal/src/terminal_element.rs | 25 +++-----
2 files changed, 80 insertions(+), 26 deletions(-)

Detailed changes

crates/terminal/src/terminal.rs 🔗

@@ -83,6 +83,11 @@ 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{-}\\^⟨⟩`]+";
+
 ///Upward flowing events, for changing the title and such
 #[derive(Clone, Copy, Debug)]
 pub enum Event {
@@ -105,6 +110,7 @@ enum InternalEvent {
     ScrollToPoint(Point),
     SetSelection(Option<(Selection, Point)>),
     UpdateSelection(Vector2F),
+    HyperlinkHover(Vector2F),
     Copy,
 }
 
@@ -643,6 +649,16 @@ impl Terminal {
                 }
             }
             InternalEvent::ScrollToPoint(point) => term.scroll_to_point(*point),
+            InternalEvent::HyperlinkHover(position) => {
+                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")
+            }
         }
     }
 
@@ -817,7 +833,6 @@ impl Terminal {
     pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
         self.last_hovered_hyperlink = None;
         let position = e.position.sub(origin);
-
         if self.mouse_mode(e.shift) {
             let point = grid_point(
                 position,
@@ -832,9 +847,39 @@ impl Terminal {
                 }
             }
         } else if e.cmd {
-            let hyperlink = cell_for_mouse(e.position, &self.last_content)
-                .cell
-                .hyperlink();
+            let content_index = content_index_for_mouse(position, &self.last_content);
+            let link = self.last_content.cells[content_index].hyperlink();
+
+            if link.is_some() {
+                let mut min_index = content_index;
+                loop {
+                    if self.last_content.cells[min_index - 1].hyperlink() == link {
+                        min_index = min_index - 1;
+                    } else {
+                        break;
+                    }
+                }
+
+                let mut max_index = content_index;
+                loop {
+                    if self.last_content.cells[max_index + 1].hyperlink() == link {
+                        max_index = max_index + 1;
+                    } else {
+                        break;
+                    }
+                }
+
+                self.last_hovered_hyperlink = link.map(|link| {
+                    (
+                        link,
+                        self.last_content.cells[min_index].point
+                            ..self.last_content.cells[max_index].point,
+                    )
+                });
+            } else {
+                self.events
+                    .push_back(InternalEvent::HyperlinkHover(position));
+            }
         }
     }
 
@@ -903,7 +948,12 @@ impl Terminal {
         let position = e.position.sub(origin);
         if !self.mouse_mode(e.shift) {
             if e.cmd {
-                if let Some(link) = cell_for_mouse(position, &self.last_content).hyperlink() {
+                if let Some(link) = self.last_content.cells
+                    [content_index_for_mouse(position, &self.last_content)]
+                .hyperlink()
+                {
+                    dbg!(&link);
+                    dbg!(&self.last_hovered_hyperlink);
                     open_uri(link.uri()).log_err();
                 }
             } else {
@@ -1073,7 +1123,7 @@ fn all_search_matches<'a, T>(
     RegexIter::new(start, end, AlacDirection::Right, term, regex)
 }
 
-fn cell_for_mouse<'a>(pos: Vector2F, content: &'a TerminalContent) -> &'a IndexedCell {
+fn content_index_for_mouse<'a>(pos: Vector2F, content: &'a TerminalContent) -> usize {
     let col = min(
         (pos.x() / content.size.cell_width()) as usize,
         content.size.columns() - 1,
@@ -1083,7 +1133,7 @@ fn cell_for_mouse<'a>(pos: Vector2F, content: &'a TerminalContent) -> &'a Indexe
         content.size.screen_lines() - 1,
     ) as usize;
 
-    &content.cells[(line * content.size.columns() + col)]
+    line * content.size.columns() + col
 }
 
 fn open_uri(uri: &str) -> Result<(), std::io::Error> {
@@ -1116,7 +1166,7 @@ mod tests {
     use gpui::geometry::vector::vec2f;
     use rand::{thread_rng, Rng};
 
-    use crate::cell_for_mouse;
+    use crate::content_index_for_mouse;
 
     use self::terminal_test_context::TerminalTestContext;
 
@@ -1153,7 +1203,10 @@ mod tests {
                         rng.gen_range(min_col..max_col),
                     );
 
-                    assert_eq!(cell_for_mouse(mouse_pos, &content).c, cells[j][i]);
+                    assert_eq!(
+                        content.cells[content_index_for_mouse(mouse_pos, &content)].c,
+                        cells[j][i]
+                    );
                 }
             }
         }
@@ -1172,7 +1225,13 @@ mod tests {
 
         let (content, cells) = TerminalTestContext::create_terminal_content(size, &mut rng);
 
-        assert_eq!(cell_for_mouse(vec2f(-10., -10.), &content).c, cells[0][0]);
-        assert_eq!(cell_for_mouse(vec2f(1000., 1000.), &content).c, cells[9][9]);
+        assert_eq!(
+            content.cells[content_index_for_mouse(vec2f(-10., -10.), &content)].c,
+            cells[0][0]
+        );
+        assert_eq!(
+            content.cells[content_index_for_mouse(vec2f(1000., 1000.), &content)].c,
+            cells[9][9]
+        );
     }
 }

crates/terminal/src/terminal_element.rs 🔗

@@ -427,6 +427,16 @@ impl TerminalElement {
                         position: e.position,
                     });
                 }
+            })
+            .on_move(move |event, cx| {
+                if cx.is_parent_view_focused() {
+                    if let Some(conn_handle) = connection.upgrade(cx.app) {
+                        conn_handle.update(cx.app, |terminal, cx| {
+                            terminal.mouse_move(&event, origin);
+                            cx.notify();
+                        })
+                    }
+                }
             });
 
         // Mouse mode handlers:
@@ -474,21 +484,6 @@ impl TerminalElement {
                     ),
                 )
         }
-        //Mouse move manages both dragging and motion events
-        if mode.intersects(TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION) {
-            region = region
-                //TODO: This does not fire on right-mouse-down-move events.
-                .on_move(move |event, cx| {
-                    if cx.is_parent_view_focused() {
-                        if let Some(conn_handle) = connection.upgrade(cx.app) {
-                            conn_handle.update(cx.app, |terminal, cx| {
-                                terminal.mouse_move(&event, origin);
-                                cx.notify();
-                            })
-                        }
-                    }
-                })
-        }
 
         cx.scene.push_mouse_region(region);
     }