zellij_server/panes/
link_handler.rs1use 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 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 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(¶ms);
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(¶ms);
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}