T09: mouse: selection by cell, word and line, Shift extends; reports for X10, normal, button and any tracking in X10, UTF-8 and SGR bytes, mode 9 followed by the tap; the wheel reports, sends arrows or scrolls history; copy, bracketed paste, copy on select, OSC 52; Cmd-press opens OSC 8 targets and URLs; a release goes out only for a press the terminal saw; shot posts mouse gestures and never activates
CI / platforms (windows-latest) (push) Failing after 7s
CI / macos (push) Failing after 8m50s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-14 15:32:17 -04:00
1 parent fdb1caa26e
commit 9f81f1f704
19 files changed
+971 -27

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
use alacritty_terminal::{
index::{Boundary, Point},
term::{Term, cell::Flags},
};
use ely_gpui_component::terminal::{Target, links};
/// The link at `point`: the program's OSC 8 target, else a URL in its line, wrapped rows joined.
pub fn link_at<T>(term: &Term<T>, mut point: Point) -> Option<String> {
let grid = term.grid();
if grid[point].flags.contains(Flags::WIDE_CHAR_SPACER) {
point.column -= 1;
}
if let Some(link) = grid[point].hyperlink() {
return Some(link.uri().to_string());
}
let end = term.line_search_right(point);
let mut cell = term.line_search_left(point);
let (mut text, mut at) = (String::new(), 0);
loop {
if cell == point {
at = text.len();
}
let spacer = Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
if !grid[cell].flags.intersects(spacer) {
text.push(grid[cell].c);
}
if cell == end {
break;
}
cell = cell.add(term, Boundary::Grid, 1);
}
links(&text)
.into_iter()
.find_map(|(bytes, target)| match target {
Target::Url(url) if bytes.contains(&at) => Some(url.to_string()),
_ => None,
})
}
#[cfg(test)]
mod tests {
use alacritty_terminal::{
event::VoidListener,
index::{Column, Line},
term::{Config, test::TermSize},
vte::ansi::Processor,
};
use super::*;
fn link(bytes: &str, line: i32, column: usize) -> Option<String> {
let mut term = Term::new(Config::default(), &TermSize::new(12, 4), VoidListener);
let mut parser: Processor = Processor::new();
parser.advance(&mut term, bytes.as_bytes());
link_at(&term, Point::new(Line(line), Column(column)))
}
#[test]
fn an_osc_8_link_names_its_target() {
let text = "a \x1b]8;;https://ely.dev/x\x1b\\ely\x1b]8;;\x1b\\ b";
assert_eq!(link(text, 0, 3).as_deref(), Some("https://ely.dev/x"));
assert_eq!(link(text, 0, 0), None);
}
#[test]
fn a_url_ends_before_its_sentence_does() {
assert_eq!(link("(a.io/x).", 0, 2), None, "no scheme, no link");
let text = "(http://a.b)";
assert_eq!(link(text, 0, 4).as_deref(), Some("http://a.b"));
assert_eq!(link(text, 0, 11), None);
}
#[test]
fn a_url_runs_across_wrapped_rows_and_wide_cells() {
let text = "\u{4e2d} https://ely.dev/abc";
assert_eq!(link(text, 1, 3).as_deref(), Some("https://ely.dev/abc"));
assert_eq!(link(text, 0, 3).as_deref(), Some("https://ely.dev/abc"));
assert_eq!(link(text, 0, 1), None);
let wide = "file://a/\u{4e2d} x";
assert_eq!(
link(wide, 0, 10).as_deref(),
Some("file://a/\u{4e2d}"),
"a wide cell's spacer is its char"
);
}
}