Publish harness and TUI open-source
initial sync from the monorepo
This commit is contained in:
commit
c68e39f604
2734 changed files with 1437016 additions and 0 deletions
306
crates/codegen/xai-grok-pager-render/src/terminal/hyperlinks.rs
Normal file
306
crates/codegen/xai-grok-pager-render/src/terminal/hyperlinks.rs
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
//! Per-terminal hyperlink (OSC 8) capabilities.
|
||||
//!
|
||||
//! Classifies caller semantics so input-handling code can consume one struct
|
||||
//! instead of branching on brand.
|
||||
|
||||
use super::TerminalName;
|
||||
|
||||
/// Whether the terminal supports OSC 8 hyperlink sequences.
|
||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, strum::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum Osc8Support {
|
||||
/// Terminal natively supports OSC 8 sequences.
|
||||
Native,
|
||||
/// Terminal actively garbles unknown OSC sequences (Apple Terminal).
|
||||
HostileParser,
|
||||
/// Terminal explicitly does not support OSC 8.
|
||||
Unsupported,
|
||||
/// Support status is unknown.
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Which URL schemes the terminal supports in OSC 8 links.
|
||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, strum::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum SchemeFilter {
|
||||
/// Standard web schemes: http, https, mailto.
|
||||
#[default]
|
||||
Standard,
|
||||
/// Extended editor schemes: vscode://, cursor://, idea://, zed://.
|
||||
EditorExtended,
|
||||
}
|
||||
|
||||
impl SchemeFilter {
|
||||
/// Returns `true` if the given scheme is permitted by this filter.
|
||||
pub fn allows(&self, scheme: &str) -> bool {
|
||||
match self {
|
||||
Self::Standard => matches!(scheme, "http" | "https" | "mailto"),
|
||||
Self::EditorExtended => matches!(
|
||||
scheme,
|
||||
"http" | "https" | "mailto" | "file" | "vscode" | "cursor" | "idea" | "zed"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-terminal hyperlink capabilities.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct HyperlinkCapabilities {
|
||||
/// OSC 8 support level.
|
||||
pub osc8: Osc8Support,
|
||||
/// Whether the terminal supports the `id=` parameter for hover-grouping.
|
||||
pub id_param: bool,
|
||||
/// Which URL schemes the terminal handles.
|
||||
pub scheme_filter: SchemeFilter,
|
||||
/// Whether the terminal supports OSC 22 cursor-shape changes
|
||||
/// (e.g. switching to a hand/pointer cursor on link hover).
|
||||
pub osc22_cursor: bool,
|
||||
/// Whether the terminal handles link hover styling natively (so our
|
||||
/// app should skip its own Cmd/Ctrl+hover highlight logic).
|
||||
pub native_link_hover: bool,
|
||||
/// Terminal opens bare http(s)/mailto under mouse reporting (Warp).
|
||||
pub native_plain_url_open: bool,
|
||||
}
|
||||
|
||||
/// Classify hyperlink capabilities for a given `brand`.
|
||||
pub fn hyperlink_capabilities(brand: TerminalName) -> HyperlinkCapabilities {
|
||||
use Osc8Support::*;
|
||||
match brand {
|
||||
// Apple Terminal actively garbles unknown OSC sequences.
|
||||
TerminalName::AppleTerminal => HyperlinkCapabilities {
|
||||
osc8: HostileParser,
|
||||
id_param: false,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// Reference implementation. Excellent id= handling.
|
||||
TerminalName::Iterm2 => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: true,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
TerminalName::Ghostty => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: true,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// Since kitty v0.19.
|
||||
TerminalName::Kitty => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: true,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// Since Alacritty v0.11. Rio and foot also support OSC 8.
|
||||
TerminalName::Alacritty | TerminalName::Rio | TerminalName::Foot => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
TerminalName::WezTerm => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// VS Code integrated terminal since v1.72. VS Code-family embeds
|
||||
// inherit the same terminal renderer (xterm.js). Zed implements
|
||||
// OSC 8 with similar capabilities. All of these handle link hover
|
||||
// styling natively.
|
||||
TerminalName::VsCode
|
||||
| TerminalName::Cursor
|
||||
| TerminalName::Windsurf
|
||||
| TerminalName::Zed => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: true,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// Open issue warpdotdev/Warp#4194. UrlLocator opens bare URLs under
|
||||
// mouse reporting; keep native_link_hover false for file:// fallback.
|
||||
TerminalName::WarpTerminal => HyperlinkCapabilities {
|
||||
osc8: Unsupported,
|
||||
id_param: false,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: true,
|
||||
},
|
||||
// VTE-based terminals (GNOME Terminal, Terminator, etc.).
|
||||
// Conservative -- gated by version in the route resolver if
|
||||
// vte_version is too old.
|
||||
TerminalName::Vte | TerminalName::Terminator => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// Windows Terminal since v1.4 (OSC 8 support).
|
||||
TerminalName::WindowsTerminal => HyperlinkCapabilities {
|
||||
osc8: Native,
|
||||
id_param: true,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// JetBrains JediTerm: OSC 8 varies across IDE versions; no
|
||||
// runtime probe available (no TERM_FEATURES). Conservative.
|
||||
TerminalName::JetBrains => HyperlinkCapabilities {
|
||||
osc8: Unknown,
|
||||
id_param: false,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
// Electron app; behavior undocumented.
|
||||
TerminalName::GrokDesktop => HyperlinkCapabilities {
|
||||
osc8: Unknown,
|
||||
id_param: false,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
TerminalName::Otty | TerminalName::Unknown => HyperlinkCapabilities {
|
||||
osc8: Unknown,
|
||||
id_param: false,
|
||||
scheme_filter: SchemeFilter::Standard,
|
||||
osc22_cursor: false,
|
||||
native_link_hover: false,
|
||||
native_plain_url_open: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ── OSC 22 cursor-shape commands ──────────────────────────────────────
|
||||
//
|
||||
// These wrap raw OSC 22 sequences as crossterm `Command`s so call sites
|
||||
// can use `crossterm::execute!` / `queue!` instead of manual byte writes.
|
||||
|
||||
/// OSC 22: set the mouse pointer to the "pointer" (hand) shape.
|
||||
///
|
||||
/// Supported by iTerm2, Ghostty, and Kitty. Silently ignored by
|
||||
/// terminals that don't understand OSC 22.
|
||||
pub struct SetPointerCursor;
|
||||
|
||||
impl crossterm::Command for SetPointerCursor {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
f.write_str("\x1b]22;pointer\x1b\\")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// OSC 22: reset the mouse pointer to the default (arrow) shape.
|
||||
pub struct SetDefaultCursor;
|
||||
|
||||
impl crossterm::Command for SetDefaultCursor {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
f.write_str("\x1b]22;default\x1b\\")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn apple_terminal_hostile_parser() {
|
||||
let caps = hyperlink_capabilities(TerminalName::AppleTerminal);
|
||||
assert_eq!(caps.osc8, Osc8Support::HostileParser);
|
||||
assert!(!caps.id_param);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterm2_native_with_id() {
|
||||
let caps = hyperlink_capabilities(TerminalName::Iterm2);
|
||||
assert_eq!(caps.osc8, Osc8Support::Native);
|
||||
assert!(caps.id_param);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warp_unsupported() {
|
||||
let caps = hyperlink_capabilities(TerminalName::WarpTerminal);
|
||||
assert_eq!(caps.osc8, Osc8Support::Unsupported);
|
||||
assert!(!caps.id_param);
|
||||
assert!(caps.native_plain_url_open);
|
||||
assert!(!caps.native_link_hover);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_plain_url_open_only_warp() {
|
||||
assert!(hyperlink_capabilities(TerminalName::WarpTerminal).native_plain_url_open);
|
||||
for brand in [
|
||||
TerminalName::Iterm2,
|
||||
TerminalName::VsCode,
|
||||
TerminalName::AppleTerminal,
|
||||
TerminalName::Ghostty,
|
||||
TerminalName::Unknown,
|
||||
] {
|
||||
assert!(
|
||||
!hyperlink_capabilities(brand).native_plain_url_open,
|
||||
"{brand:?} must not set native_plain_url_open"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_terminal_unknown_support() {
|
||||
let caps = hyperlink_capabilities(TerminalName::Unknown);
|
||||
assert_eq!(caps.osc8, Osc8Support::Unknown);
|
||||
assert!(!caps.id_param);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scheme_filter_standard_allows_http() {
|
||||
assert!(SchemeFilter::Standard.allows("http"));
|
||||
assert!(SchemeFilter::Standard.allows("https"));
|
||||
assert!(SchemeFilter::Standard.allows("mailto"));
|
||||
assert!(!SchemeFilter::Standard.allows("file"));
|
||||
assert!(!SchemeFilter::Standard.allows("vscode"));
|
||||
assert!(!SchemeFilter::Standard.allows("javascript"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scheme_filter_extended_allows_editor_schemes() {
|
||||
assert!(SchemeFilter::EditorExtended.allows("http"));
|
||||
assert!(SchemeFilter::EditorExtended.allows("vscode"));
|
||||
assert!(SchemeFilter::EditorExtended.allows("cursor"));
|
||||
assert!(SchemeFilter::EditorExtended.allows("idea"));
|
||||
assert!(SchemeFilter::EditorExtended.allows("zed"));
|
||||
assert!(!SchemeFilter::EditorExtended.allows("javascript"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue