Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/term/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ impl Frame {
}

/// The screen as it shows now, scrolled back or not; `lit` matches wear their washes.
pub fn frame<T: EventListener>(term: &Term<T>, ink: &Ink, lit: &[(Match, Hsla)]) -> Frame {
pub fn frame<T: EventListener>(
term: &Term<T>,
ink: &Ink,
lit: &[(Match, Hsla)],
link: Option<&Match>,
) -> Frame {
let content = term.renderable_content();
let offset = content.display_offset as i32;
let mut rows = vec![Row::default(); term.screen_lines()];
Expand Down Expand Up @@ -117,6 +122,13 @@ pub fn frame<T: EventListener>(term: &Term<T>, ink: &Ink, lit: &[(Match, Hsla)])
.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER);
// A placeholder's image draws over its cell; its glyph never.
let hidden = cell.flags.contains(Flags::HIDDEN) || cell.c == super::graphics::PLACEHOLDER;
if !hidden
&& look.underline == Underline::None
&& link.is_some_and(|link| covers(&|at| link.contains(&at)))
{
look.underline = Underline::Single;
look.underline_color = look.fg;
}
let marks = cell.zerowidth().filter(|marks| !marks.is_empty());
let alone = (!hidden && (marks.is_some() || boxes::drawn(cell.c))).then(|| {
std::iter::once(cell.c)
Expand Down Expand Up @@ -204,6 +216,9 @@ fn style(cell: &Cell, ink: &Ink, set: &Colors) -> Style {
}
}

#[cfg(test)]
mod hover_tests;

#[cfg(test)]
mod tests {
use alacritty_terminal::{
Expand All @@ -220,7 +235,7 @@ mod tests {
let mut parser: Processor = Processor::new();
parser.advance(&mut term, bytes);
let ink = Ink::new(themes::find("dracula").expect("dracula"), term.colors());
(frame(&term, &ink, &[]), ink)
(frame(&term, &ink, &[], None), ink)
}

#[test]
Expand Down Expand Up @@ -338,7 +353,7 @@ mod tests {
selection.update(Point::new(Line(0), Column(3)), Side::Left);
term.selection = Some(selection);
let ink = Ink::new(themes::find("dracula").expect("dracula"), term.colors());
let frame = frame(&term, &ink, &[]);
let frame = frame(&term, &ink, &[], None);
let grounds: Vec<_> = frame.rows[0]
.runs
.iter()
Expand Down Expand Up @@ -369,7 +384,7 @@ mod tests {
.map(|(bytes, style)| (*bytes, style.bg))
.collect()
};
let shown = frame(&term, &ink, &lit);
let shown = frame(&term, &ink, &lit, None);
assert_eq!(
&grounds(&shown)[..5],
[
Expand All @@ -386,7 +401,7 @@ mod tests {
.as_mut()
.expect("a selection")
.update(at(6), Side::Right);
let shown = frame(&term, &ink, &lit);
let shown = frame(&term, &ink, &lit, None);
assert_eq!(
grounds(&shown)[3],
(1, Some(ink.selection)),
Expand Down
86 changes: 86 additions & 0 deletions src/term/frame/hover_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use super::*;
use crate::{term::links::hit_at, themes};
use alacritty_terminal::{
event::VoidListener,
grid::Scroll,
index::{Column, Line},
term::{Config, test::TermSize},
vte::ansi::Processor,
};

fn term(text: &str) -> Term<VoidListener> {
let mut term = Term::new(Config::default(), &TermSize::new(12, 3), VoidListener);
<Processor>::new().advance(&mut term, text.as_bytes());
term
}

fn styles(frame: &Frame) -> Vec<Style> {
frame
.rows
.iter()
.flat_map(|row| {
row.runs
.iter()
.flat_map(|(bytes, style)| std::iter::repeat_n(*style, *bytes))
})
.collect()
}

fn shown(term: &Term<VoidListener>, hover: Option<Point>) -> Frame {
let ink = Ink::new(themes::find("dracula").expect("a theme"), term.colors());
let link = hover.and_then(|point| hit_at(term, point));
frame(term, &ink, &[], link.as_ref())
}

#[test]
fn hover_underlines_the_whole_link_and_leaving_restores_its_style() {
let term = term("(https://a.b/c) ");
let hover = Point::new(Line(0), Column(4));
let painted = styles(&shown(&term, Some(hover)));
assert_eq!(painted[0].underline, Underline::None);
assert!(
painted[1..14]
.iter()
.all(|style| style.underline == Underline::Single)
);
assert_eq!(painted[14].underline, Underline::None);
assert!(
styles(&shown(&term, None))
.iter()
.all(|style| style.underline == Underline::None)
);
assert!(!term.grid()[hover].flags.contains(Flags::UNDERLINE));
}

#[test]
fn hover_preserves_explicit_underlines_and_hidden_text() {
let term = term("\x1b[4:3m\x1b]8;;https://a.b\x07label\x1b]8;;\x07");
let hover = Point::new(Line(0), Column(2));
assert_eq!(shown(&term, Some(hover)).rows, shown(&term, None).rows);
let term = self::term("\x1b[8mhttps://a.b\x1b[0m");
assert!(
styles(&shown(&term, Some(hover)))
.iter()
.all(|style| style.underline == Underline::None)
);
}

#[test]
fn hover_follows_history_and_output_changes_without_a_mouse_move() {
let mut term = term("https://a.b\r\nnext\r\nlast\r\nbottom");
term.scroll_display(Scroll::Top);
let hover = Point::new(Line(-1), Column(2));
assert!(
styles(&shown(&term, Some(hover)))[..11]
.iter()
.all(|style| style.underline == Underline::Single)
);
let mut term = self::term("https://a.b");
let hover = Point::new(Line(0), Column(2));
<Processor>::new().advance(&mut term, b"\r\x1b[2Kplain");
assert!(
styles(&shown(&term, Some(hover)))
.iter()
.all(|style| style.underline == Underline::None)
);
}
Loading