diff --git a/src/terminal.rs b/src/terminal.rs index 0eabf19..a12471c 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -246,6 +246,7 @@ pub struct Terminal { pub url_regex_search: RegexSearch, pub regex_matches: Vec, pub active_regex_match: Option, + pub active_hyperlink_id: Option, bold_font_weight: Weight, buffer: Arc, is_focused: bool, @@ -335,6 +336,7 @@ impl Terminal { Ok(Self { active_regex_match: None, + active_hyperlink_id: None, url_regex_search: url_regex_search(), regex_matches: Vec::new(), bold_font_weight: Weight(bold_font_weight), @@ -887,6 +889,28 @@ impl Terminal { flags |= Flags::UNDERLINE; } } + if let Some(active_id) = &self.active_hyperlink_id { + let mut matches_active = indexed + .cell + .hyperlink() + .is_some_and(|link| link.id() == active_id); + if !matches_active + && indexed.cell.flags.intersects( + Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER, + ) + && indexed.point.column.0 > 0 + { + matches_active = grid[Point::new( + indexed.point.line, + Column(indexed.point.column.0 - 1), + )] + .hyperlink() + .is_some_and(|link| link.id() == active_id); + } + if matches_active { + flags |= Flags::UNDERLINE; + } + } let metadata = Metadata::new(bg, fg) .with_flags(flags) diff --git a/src/terminal_box.rs b/src/terminal_box.rs index b37e15f..cb154c4 100644 --- a/src/terminal_box.rs +++ b/src/terminal_box.rs @@ -336,11 +336,7 @@ where let location = terminal .viewport_to_point(TermPoint::new(row as usize, TermColumn(col as usize))); - if terminal - .regex_matches - .iter() - .any(|bounds| bounds.contains(&location)) - { + if get_hyperlink(&terminal, location).is_some() { return mouse::Interaction::Pointer; } } @@ -1002,7 +998,10 @@ where Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => { state.modifiers = modifiers; - if modifiers.contains(Modifiers::CTRL) || terminal.active_regex_match.is_some() { + if modifiers.contains(Modifiers::CTRL) + || terminal.active_regex_match.is_some() + || terminal.active_hyperlink_id.is_some() + { //Might need to update the url regex highlight, //so we need to calculate the mouse position let location = if let Some(p) = cursor_position.position() { @@ -1451,6 +1450,9 @@ fn get_hyperlink( terminal: &std::sync::MutexGuard<'_, Terminal>, location: TermPoint, ) -> Option { + if let Some(link) = osc8_hyperlink_at(terminal, location) { + return Some(link.uri().to_string()); + } if let Some(match_) = terminal .regex_matches .iter() @@ -1463,6 +1465,32 @@ fn get_hyperlink( } } +fn get_hyperlink_id( + terminal: &std::sync::MutexGuard<'_, Terminal>, + location: TermPoint, +) -> Option { + osc8_hyperlink_at(terminal, location).map(|link| link.id().to_string()) +} + +fn osc8_hyperlink_at( + terminal: &std::sync::MutexGuard<'_, Terminal>, + location: TermPoint, +) -> Option { + let term = terminal.term.lock(); + let grid = term.grid(); + let cell = &grid[location]; + if let Some(link) = cell.hyperlink() { + return Some(link); + } + if cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) + && location.column.0 > 0 + { + let left = TermPoint::new(location.line, TermColumn(location.column.0 - 1)); + return grid[left].hyperlink(); + } + None +} + fn update_active_regex_match( terminal: &mut std::sync::MutexGuard<'_, Terminal>, location: Option, @@ -1475,6 +1503,9 @@ fn update_active_regex_match( return; } + let allow_hyperlink = modifiers + .map(|mods| mods.contains(Modifiers::CTRL)) + .unwrap_or(false); //Require CTRL for keyboard and mouse interaction if let Some(modifiers) = modifiers { if !modifiers.contains(Modifiers::CTRL) { @@ -1482,16 +1513,37 @@ fn update_active_regex_match( terminal.active_regex_match = None; terminal.needs_update = true; } + if terminal.active_hyperlink_id.is_some() { + terminal.active_hyperlink_id = None; + terminal.needs_update = true; + } return; } + } else if terminal.active_hyperlink_id.is_some() { + terminal.active_hyperlink_id = None; + terminal.needs_update = true; } let Some(location) = location else { if terminal.active_regex_match.is_some() { terminal.active_regex_match = None; terminal.needs_update = true; } + if terminal.active_hyperlink_id.is_some() { + terminal.active_hyperlink_id = None; + terminal.needs_update = true; + } return; }; + if allow_hyperlink { + let next_hyperlink_id = get_hyperlink_id(terminal, location); + if terminal.active_hyperlink_id != next_hyperlink_id { + terminal.active_hyperlink_id = next_hyperlink_id; + terminal.needs_update = true; + } + } else if terminal.active_hyperlink_id.is_some() { + terminal.active_hyperlink_id = None; + terminal.needs_update = true; + } if let Some(match_) = terminal .regex_matches .iter()