zellij_server/panes/
link_handler.rs

1use std::collections::HashMap;
2
3use super::LinkAnchor;
4
5const TERMINATOR: &str = "\u{1b}\\";
6
7#[derive(Debug, Clone)]
8pub struct LinkHandler {
9    links: HashMap<u16, Link>,
10    link_index: u16,
11}
12#[derive(Debug, Clone)]
13pub struct Link {
14    pub id: Option<String>,
15    pub uri: String,
16}
17
18impl LinkHandler {
19    pub fn new() -> Self {
20        Self {
21            links: HashMap::new(),
22            link_index: 0,
23        }
24    }
25
26    pub fn dispatch_osc8(&mut self, params: &[&[u8]]) -> Option<LinkAnchor> {
27        let (link_params, uri) = (params[1], params[2]);
28        log::debug!(
29            "dispatching osc8, params: {:?}, uri: {:?}",
30            std::str::from_utf8(link_params),
31            std::str::from_utf8(uri)
32        );
33
34        if !uri.is_empty() {
35            // save the link, and the id if present to hashmap
36            String::from_utf8(uri.to_vec()).ok().map(|uri| {
37                let id = link_params
38                    .split(|&b| b == b':')
39                    .find(|kv| kv.starts_with(b"id="))
40                    .and_then(|kv| String::from_utf8(kv[3..].to_vec()).ok());
41                let anchor = LinkAnchor::Start(self.link_index);
42                self.links.insert(self.link_index, Link { id, uri });
43                self.link_index += 1;
44                anchor
45            })
46        } else {
47            // there is no link, so consider it a link end
48            Some(LinkAnchor::End)
49        }
50    }
51
52    pub fn new_link_from_url(&mut self, url: String) -> LinkAnchor {
53        let anchor = LinkAnchor::Start(self.link_index);
54        self.links.insert(
55            self.link_index,
56            Link {
57                id: Some(self.link_index.to_string()),
58                uri: url,
59            },
60        );
61        self.link_index += 1;
62        anchor
63    }
64
65    pub fn output_osc8(&self, link_anchor: Option<LinkAnchor>) -> Option<String> {
66        link_anchor.and_then(|link| match link {
67            LinkAnchor::Start(index) => {
68                let link = self.links.get(&index);
69
70                let output = link.map(|link| {
71                    let id = link
72                        .id
73                        .as_ref()
74                        .map_or("".to_string(), |id| format!("id={}", id));
75                    format!("\u{1b}]8;{};{}{}", id, link.uri, TERMINATOR)
76                });
77
78                if output.is_none() {
79                    log::warn!(
80                        "attempted to output osc8 link start, but id: {} was not found!",
81                        index
82                    );
83                }
84
85                output
86            },
87            LinkAnchor::End => Some(format!("\u{1b}]8;;{}", TERMINATOR)),
88        })
89    }
90
91    #[cfg(test)]
92    pub fn links(&self) -> HashMap<u16, Link> {
93        self.links.clone()
94    }
95}
96
97impl Default for LinkHandler {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn dispatch_osc8_link_start() {
109        let mut link_handler = LinkHandler::default();
110        let link_params = "id=test";
111        let uri = "http://test.com";
112        let params = vec!["8".as_bytes(), link_params.as_bytes(), uri.as_bytes()];
113
114        let anchor = link_handler.dispatch_osc8(&params);
115
116        match anchor {
117            Some(LinkAnchor::Start(link_id)) => {
118                let link = link_handler.links.get(&link_id).expect("link was not some");
119                assert_eq!(link.id, Some("test".to_string()));
120                assert_eq!(link.uri, uri);
121            },
122            _ => panic!("pending link handler was not start"),
123        }
124
125        let expected = format!("\u{1b}]8;id=test;http://test.com{}", TERMINATOR);
126        assert_eq!(link_handler.output_osc8(anchor).unwrap(), expected);
127    }
128
129    #[test]
130    fn dispatch_osc8_link_end() {
131        let mut link_handler = LinkHandler::default();
132        let params: Vec<&[_]> = vec![b"8", b"", b""];
133
134        let anchor = link_handler.dispatch_osc8(&params);
135
136        assert_eq!(anchor, Some(LinkAnchor::End));
137
138        let expected = format!("\u{1b}]8;;{}", TERMINATOR);
139        assert_eq!(link_handler.output_osc8(anchor).unwrap(), expected);
140    }
141
142    #[test]
143    fn return_none_on_missing_link_id() {
144        let link_handler = LinkHandler::default();
145        let anchor = LinkAnchor::Start(100);
146        assert_eq!(link_handler.output_osc8(Some(anchor)), None);
147    }
148}