summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEthan P <eth-p+git@hidden.email>2023-04-17 19:07:58 -0700
committerEthan P. <eth-p+git@hidden.email>2024-02-09 22:09:39 -0800
commit6549e26f5d3923078b482d30bc4ed90b2f5710c9 (patch)
tree3efa82fc2d41e834061b76d10ad451aa25ef1004
parent165c495e7582e1251802cfc3c05b2fc28151cee8 (diff)
Re-emit hyperlinks when wrapping lines
-rw-r--r--src/vscreen.rs34
-rw-r--r--tests/integration_tests.rs19
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;<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 {
@@ -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<Item = char>) -> bool {
self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>());
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() {