aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Linford <tlinford@users.noreply.github.com>2021-11-22 18:54:49 +0100
committerGitHub <noreply@github.com>2021-11-22 18:54:49 +0100
commita96b9d8769a70251b897501b825cdfd1ae2b079d (patch)
tree721ca89742e199a7d554353471f311cff828bd11
parentdocs(changelog): large scrollback performance fix (diff)
downloadsl0ppij-a96b9d8769a70251b897501b825cdfd1ae2b079d.tar.gz
sl0ppij-a96b9d8769a70251b897501b825cdfd1ae2b079d.tar.xz
sl0ppij-a96b9d8769a70251b897501b825cdfd1ae2b079d.zip
feat(compatibility): add support for osc 8 escape code (#822)
Resolve #645 * add support for osc 8 escape code * refactor, add links to CharacterStyles * cleanup * refactor, add minimal tests
-rw-r--r--zellij-server/src/panes/grid.rs14
-rw-r--r--zellij-server/src/panes/link_handler.rs111
-rw-r--r--zellij-server/src/panes/mod.rs1
-rw-r--r--zellij-server/src/panes/terminal_character.rs17
-rw-r--r--zellij-server/src/panes/terminal_pane.rs3
5 files changed, 144 insertions, 2 deletions
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index c094701f..e3752fef 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -18,13 +18,13 @@ use zellij_tile::data::{Palette, PaletteColor};
use zellij_utils::{consts::VERSION, shared::version_number};
use crate::panes::alacritty_functions::{parse_number, xparse_color};
+use crate::panes::link_handler::LinkHandler;
+use crate::panes::selection::Selection;
use crate::panes::terminal_character::{
AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset,
TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
};
-use super::selection::Selection;
-
fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
let mut index_of_last_non_canonical_row = None;
for (i, row) in rows.iter().enumerate() {
@@ -394,6 +394,7 @@ pub struct Grid {
pub selection: Selection,
pub title: Option<String>,
pub is_scrolled: bool,
+ pub link_handler: LinkHandler,
scrollback_buffer_lines: usize,
}
@@ -440,6 +441,7 @@ impl Grid {
title: None,
changed_colors: None,
is_scrolled: false,
+ link_handler: Default::default(),
scrollback_buffer_lines: 0,
}
}
@@ -1549,6 +1551,14 @@ impl Perform for Grid {
}
}
+ // define hyperlink
+ b"8" => {
+ if params.len() < 3 {
+ return;
+ }
+ self.cursor.pending_styles.link_anchor = self.link_handler.dispatch_osc8(params);
+ }
+
// Get/set Foreground, Background, Cursor colors.
b"10" | b"11" | b"12" => {
if params.len() >= 2 {
diff --git a/zellij-server/src/panes/link_handler.rs b/zellij-server/src/panes/link_handler.rs
new file mode 100644
index 00000000..4294dbed
--- /dev/null
+++ b/zellij-server/src/panes/link_handler.rs
@@ -0,0 +1,111 @@
+use std::collections::HashMap;
+
+use super::LinkAnchor;
+
+const TERMINATOR: &str = "\u{1b}\\";
+
+#[derive(Debug, Clone)]
+pub struct LinkHandler {
+ links: HashMap<u16, Link>,
+ link_index: u16,
+}
+#[derive(Debug, Clone)]
+struct Link {
+ id: Option<String>,
+ uri: String,
+}
+
+impl LinkHandler {
+ pub fn new() -> Self {
+ Self {
+ links: HashMap::new(),
+ link_index: 0,
+ }
+ }
+
+ pub fn dispatch_osc8(&mut self, params: &[&[u8]]) -> Option<LinkAnchor> {
+ let (link_params, uri) = (params[1], params[2]);
+ log::debug!(
+ "dispatching osc8, params: {:?}, uri: {:?}",
+ std::str::from_utf8(link_params),
+ std::str::from_utf8(uri)
+ );
+
+ if !uri.is_empty() {
+ // save the link, and the id if present to hashmap
+ String::from_utf8(uri.to_vec()).ok().map(|uri| {
+ let id = link_params
+ .split(|&b| b == b':')
+ .find(|kv| kv.starts_with(b"id="))
+ .and_then(|kv| String::from_utf8(kv[3..].to_vec()).ok());
+ let anchor = LinkAnchor::Start(self.link_index);
+ self.links.insert(self.link_index, Link { id, uri });
+ self.link_index += 1;
+ anchor
+ })
+ } else {
+ // there is no link, so consider it a link end
+ Some(LinkAnchor::End)
+ }
+ }
+
+ pub fn output_osc8(&self, link_anchor: Option<LinkAnchor>) -> String {
+ link_anchor.map_or("".to_string(), |link| match link {
+ LinkAnchor::Start(index) => {
+ let link = self.links.get(&index).unwrap();
+ let id = link
+ .id
+ .as_ref()
+ .map_or("".to_string(), |id| format!("id={}", id));
+ format!("\u{1b}]8;{};{}{}", id, link.uri, TERMINATOR)
+ }
+ LinkAnchor::End => format!("\u{1b}]8;;{}", TERMINATOR),
+ })
+ }
+}
+
+impl Default for LinkHandler {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn dispatch_osc8_link_start() {
+ let mut link_handler = LinkHandler::default();
+ let link_params = "id=test";
+ let uri = "http://test.com";
+ let params = vec!["8".as_bytes(), link_params.as_bytes(), uri.as_bytes()];
+
+ let anchor = link_handler.dispatch_osc8(&params);
+
+ match anchor {
+ Some(LinkAnchor::Start(link_id)) => {
+ let link = link_handler.links.get(&link_id).expect("link was not some");
+ assert_eq!(link.id, Some("test".to_string()));
+ assert_eq!(link.uri, uri);
+ }
+ _ => panic!("pending link handler was not start"),
+ }
+
+ let expected = format!("\u{1b}]8;id=test;http://test.com{}", TERMINATOR);
+ assert_eq!(link_handler.output_osc8(anchor), expected);
+ }
+
+ #[test]
+ fn dispatch_osc8_link_end() {
+ let mut link_handler = LinkHandler::default();
+ let params = vec!["8".as_bytes(), &[], &[]];
+
+ let anchor = link_handler.dispatch_osc8(&params);
+
+ assert_eq!(anchor, Some(LinkAnchor::End));
+
+ let expected = format!("\u{1b}]8;;{}", TERMINATOR);
+ assert_eq!(link_handler.output_osc8(anchor), expected);
+ }
+}
diff --git a/zellij-server/src/panes/mod.rs b/zellij-server/src/panes/mod.rs
index c918f98f..7ee5ab62 100644
--- a/zellij-server/src/panes/mod.rs
+++ b/zellij-server/src/panes/mod.rs
@@ -1,5 +1,6 @@
mod alacritty_functions;
mod grid;
+mod link_handler;
mod plugin_pane;
mod selection;
mod terminal_character;
diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs
index f619b891..c9de3760 100644
--- a/zellij-server/src/panes/terminal_character.rs
+++ b/zellij-server/src/panes/terminal_character.rs
@@ -23,6 +23,7 @@ pub const RESET_STYLES: CharacterStyles = CharacterStyles {
bold: Some(AnsiCode::Reset),
dim: Some(AnsiCode::Reset),
italic: Some(AnsiCode::Reset),
+ link_anchor: Some(LinkAnchor::End),
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -110,6 +111,7 @@ pub struct CharacterStyles {
pub bold: Option<AnsiCode>,
pub dim: Option<AnsiCode>,
pub italic: Option<AnsiCode>,
+ pub link_anchor: Option<LinkAnchor>,
}
impl Default for CharacterStyles {
@@ -126,6 +128,7 @@ impl Default for CharacterStyles {
bold: None,
dim: None,
italic: None,
+ link_anchor: None,
}
}
}
@@ -178,6 +181,10 @@ impl CharacterStyles {
self.strike = strike_code;
self
}
+ pub fn link_anchor(mut self, link_anchor: Option<LinkAnchor>) -> Self {
+ self.link_anchor = link_anchor;
+ self
+ }
pub fn clear(&mut self) {
self.foreground = None;
self.background = None;
@@ -190,6 +197,7 @@ impl CharacterStyles {
self.bold = None;
self.dim = None;
self.italic = None;
+ self.link_anchor = None;
}
pub fn update_and_return_diff(
&mut self,
@@ -241,6 +249,9 @@ impl CharacterStyles {
if self.italic != new_styles.italic {
diff.italic = new_styles.italic;
}
+ if self.link_anchor != new_styles.link_anchor {
+ diff.link_anchor = new_styles.link_anchor;
+ }
// apply new styles
*self = *new_styles;
@@ -556,6 +567,12 @@ impl Display for CharacterStyles {
}
}
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum LinkAnchor {
+ Start(u16),
+ End,
+}
+
#[derive(Clone, Copy, Debug)]
pub enum CharsetIndex {
G0,
diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs
index 66a8a3dd..f2974cee 100644
--- a/zellij-server/src/panes/terminal_pane.rs
+++ b/zellij-server/src/panes/terminal_pane.rs
@@ -236,7 +236,10 @@ impl Pane for TerminalPane {
.update_and_return_diff(&t_character.styles, self.grid.changed_colors)
{
vte_output.push_str(&new_styles.to_string());
+ vte_output
+ .push_str(&self.grid.link_handler.output_osc8(new_styles.link_anchor))
}
+
vte_output.push(t_character.character);
}
character_styles.clear();