Re-emit hyperlinks when wrapping lines

This commit is contained in:
Ethan P 2023-04-17 19:07:58 -07:00 committed by Ethan P.
parent 165c495e75
commit 6549e26f5d
No known key found for this signature in database
GPG Key ID: 1BA2A0CC7C22B854
2 changed files with 51 additions and 2 deletions

View File

@ -65,6 +65,13 @@ struct Attributes {
/// ON: ^[9m /// ON: ^[9m
/// OFF: ^[29m /// OFF: ^[29m
strike: String, strike: String,
/// The hyperlink sequence.
/// FORMAT: \x1B]8;<ID>;<HREF>\e\\
///
/// `\e\\` may be replaced with BEL `\x07`.
/// Setting both <ID> and <HREF> to an empty string represents no hyperlink.
hyperlink: String,
} }
impl Attributes { impl Attributes {
@ -80,6 +87,7 @@ impl Attributes {
underline: "".to_owned(), underline: "".to_owned(),
italic: "".to_owned(), italic: "".to_owned(),
strike: "".to_owned(), strike: "".to_owned(),
hyperlink: "".to_owned(),
} }
} }
@ -90,7 +98,16 @@ impl Attributes {
match sequence { match sequence {
Text(_) => return false, Text(_) => return false,
Unknown(_) => { /* defer to update_with_unsupported */ } 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 { CSI {
final_byte, final_byte,
parameters, parameters,
@ -168,6 +185,18 @@ impl Attributes {
false 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<Item = char>) -> bool { fn update_with_charset(&mut self, kind: char, set: impl Iterator<Item = char>) -> bool {
self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>()); self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>());
true true
@ -191,7 +220,7 @@ impl Display for Attributes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"{}{}{}{}{}{}{}{}{}", "{}{}{}{}{}{}{}{}{}{}",
self.foreground, self.foreground,
self.background, self.background,
self.underlined, self.underlined,
@ -201,6 +230,7 @@ impl Display for Attributes {
self.underline, self.underline,
self.italic, self.italic,
self.strike, self.strike,
self.hyperlink,
) )
} }
} }

View File

@ -1952,6 +1952,25 @@ fn ansi_sgr_emitted_when_wrapped() {
.stderr(""); .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. // Ensure that multiple ANSI sequence SGR attributes are combined when emitted on wrapped lines.
#[test] #[test]
fn ansi_sgr_joins_attributes_when_wrapped() { fn ansi_sgr_joins_attributes_when_wrapped() {