Skip to main content

urushi/style/
hyperlink.rs

1//! A logical terminal hyperlink value.
2
3use std::sync::Arc;
4
5/// A terminal hyperlink attached to one run of text.
6///
7/// The URI and parameters remain logical data. The selected terminal output
8/// boundary escapes and frames them when it encodes OSC 8. Parameter names and
9/// values remain intentionally open-ended; terminals currently define `id`,
10/// while future terminals may add more keys.
11///
12/// ```
13/// use urushi::{Hyperlink, TextStyle};
14///
15/// let plain = TextStyle::new().hyperlink("https://example.com");
16/// let identified = TextStyle::new().hyperlink(
17///     Hyperlink::new("https://example.com").parameter("id", "documentation"),
18/// );
19///
20/// assert_eq!(plain.get_hyperlink().unwrap().uri(), "https://example.com");
21/// assert_eq!(identified.get_hyperlink().unwrap().parameters().len(), 1);
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Hyperlink {
25    uri: Arc<str>,
26    parameters: Arc<[(String, String)]>,
27}
28
29impl Hyperlink {
30    /// Creates a hyperlink to `uri` without parameters.
31    ///
32    pub fn new(uri: impl AsRef<str>) -> Self {
33        Self {
34            uri: uri.as_ref().into(),
35            parameters: Arc::from([]),
36        }
37    }
38
39    /// Adds one OSC 8 parameter.
40    ///
41    pub fn parameter(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
42        let mut parameters = self.parameters.to_vec();
43        parameters.push((name.as_ref().to_owned(), value.as_ref().to_owned()));
44        self.parameters = parameters.into();
45        self
46    }
47
48    /// Returns the logical URI supplied by the caller.
49    pub fn uri(&self) -> &str {
50        &self.uri
51    }
52
53    /// Returns the logical parameters in insertion order.
54    pub fn parameters(&self) -> &[(String, String)] {
55        &self.parameters
56    }
57}
58
59impl From<&str> for Hyperlink {
60    fn from(uri: &str) -> Self {
61        Self::new(uri)
62    }
63}
64
65impl From<String> for Hyperlink {
66    fn from(uri: String) -> Self {
67        Self::new(uri)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn construction_preserves_logical_hyperlink_data() {
77        let hyperlink = Hyperlink::new("https://example.com/a\u{1b}\\b\u{7}\u{9c}")
78            .parameter("i:d=;\u{9d}", "v:a=l;ue\n\u{9b}");
79
80        assert_eq!(hyperlink.uri(), "https://example.com/a\u{1b}\\b\u{7}\u{9c}");
81        assert_eq!(
82            hyperlink.parameters(),
83            &[("i:d=;\u{9d}".to_owned(), "v:a=l;ue\n\u{9b}".to_owned())]
84        );
85    }
86}