From 6549e26f5d3923078b482d30bc4ed90b2f5710c9 Mon Sep 17 00:00:00 2001 From: Ethan P Date: Mon, 17 Apr 2023 19:07:58 -0700 Subject: [PATCH] Re-emit hyperlinks when wrapping lines --- src/vscreen.rs | 34 ++++++++++++++++++++++++++++++++-- tests/integration_tests.rs | 19 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/vscreen.rs b/src/vscreen.rs index ea1f02b4..7e2b8cd1 100644 --- a/src/vscreen.rs +++ b/src/vscreen.rs @@ -65,6 +65,13 @@ struct Attributes { /// ON: ^[9m /// OFF: ^[29m strike: String, + + /// The hyperlink sequence. + /// FORMAT: \x1B]8;;\e\\ + /// + /// `\e\\` may be replaced with BEL `\x07`. + /// Setting both and to an empty string represents no hyperlink. + hyperlink: String, } impl Attributes { @@ -80,6 +87,7 @@ impl Attributes { underline: "".to_owned(), italic: "".to_owned(), strike: "".to_owned(), + hyperlink: "".to_owned(), } } @@ -90,7 +98,16 @@ impl Attributes { match sequence { Text(_) => return false, Unknown(_) => { /* defer to update_with_unsupported */ } - OSC { .. } => return false, + OSC { + raw_sequence, + command, + .. + } => { + if command.starts_with("8;") { + return self.update_with_hyperlink(raw_sequence); + } + /* defer to update_with_unsupported */ + } CSI { final_byte, parameters, @@ -168,6 +185,18 @@ impl Attributes { false } + fn update_with_hyperlink(&mut self, sequence: &str) -> bool { + if sequence == "8;;" { + // Empty hyperlink ID and HREF -> end of hyperlink. + self.hyperlink.clear(); + } else { + self.hyperlink.clear(); + self.hyperlink.push_str(sequence); + } + + true + } + fn update_with_charset(&mut self, kind: char, set: impl Iterator) -> bool { self.charset = format!("\x1B{}{}", kind, set.take(1).collect::()); true @@ -191,7 +220,7 @@ impl Display for Attributes { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "{}{}{}{}{}{}{}{}{}", + "{}{}{}{}{}{}{}{}{}{}", self.foreground, self.background, self.underlined, @@ -201,6 +230,7 @@ impl Display for Attributes { self.underline, self.italic, self.strike, + self.hyperlink, ) } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index d90e724b..bb4c4668 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1952,6 +1952,25 @@ fn ansi_sgr_emitted_when_wrapped() { .stderr(""); } +// Ensure that a simple ANSI sequence passthrough is emitted properly on wrapped lines. +// This also helps ensure that escape sequences are counted as part of the visible characters when wrapping. +#[test] +fn ansi_hyperlink_emitted_when_wrapped() { + bat() + .arg("--paging=never") + .arg("--color=never") + .arg("--terminal-width=20") + .arg("--wrap=character") + .arg("--decorations=always") + .arg("--style=plain") + .write_stdin("\x1B]8;;http://example.com/\x1B\\Hyperlinks..........Wrap across lines.\n") + .assert() + .success() + .stdout("\x1B]8;;http://example.com/\x1B\\\x1B]8;;http://example.com/\x1B\\Hyperlinks..........\n\x1B]8;;http://example.com/\x1B\\Wrap across lines.\n") + // FIXME: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ should not be emitted twice. + .stderr(""); +} + // Ensure that multiple ANSI sequence SGR attributes are combined when emitted on wrapped lines. #[test] fn ansi_sgr_joins_attributes_when_wrapped() {