diff --git a/term/Cargo.toml b/term/Cargo.toml index 03f2779ec..b79b87983 100644 --- a/term/Cargo.toml +++ b/term/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" [dependencies] bitflags = "1.0.1" failure = "0.1.1" +maplit = "1.0.1" palette = "0.2.1" serde = "1.0.27" serde_derive = "1.0.27" @@ -14,5 +15,4 @@ unicode-width = "0.1.4" vte = "0.3.2" [features] -# When enabled, print the results of parsing the input stream to stdout debug-escape-sequences = [] diff --git a/term/src/hyperlink.rs b/term/src/hyperlink.rs new file mode 100644 index 000000000..2eeba3c69 --- /dev/null +++ b/term/src/hyperlink.rs @@ -0,0 +1,61 @@ +//! Handling hyperlinks. +//! This gist describes an escape sequence for explicitly managing hyperlinks: +//! https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5fedaA +//! We use that as the foundation of our hyperlink support, and the game +//! plan is to then implicitly enable the hyperlink attribute for a cell +//! as we recognize linkable input text during print() processing. + +use std::collections::HashMap; + +/// The spec says that the escape sequence is of the form: +/// OSC 8 ; params ; URI BEL|ST +/// params is an optional list of key=value assignments, +/// separated by the : character. Example: id=xyz123:foo=bar:baz=quux. +/// This function parses such a string and returns the mapping +/// of key to value. Malformed input causes subsequent key/value pairs +/// to be skipped, returning the data successfully parsed out so far. +pub fn parse_link_params(params: &str) -> HashMap<&str, &str> { + let mut map = HashMap::new(); + for kv in params.split(":") { + let mut iter = kv.splitn(2, "="); + let key = iter.next(); + let value = iter.next(); + match (key, value) { + (Some(key), Some(value)) => map.insert(key, value), + _ => break, + }; + } + + map +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_link() { + assert_eq!(parse_link_params(""), hashmap!{}); + assert_eq!(parse_link_params("foo"), hashmap!{}); + assert_eq!( + parse_link_params("foo=bar=baz"), + hashmap!{"foo" => "bar=baz"} + ); + assert_eq!(parse_link_params("foo=bar"), hashmap!{"foo" => "bar"}); + + assert_eq!( + parse_link_params("id=1234:foo=bar"), + hashmap!{ + "id" => "1234", + "foo" => "bar" + } + ); + assert_eq!( + parse_link_params("id=1234:foo=bar:"), + hashmap!{ + "id" => "1234", + "foo" => "bar" + } + ); + } +} diff --git a/term/src/lib.rs b/term/src/lib.rs index cc7dba22f..c4e13d766 100644 --- a/term/src/lib.rs +++ b/term/src/lib.rs @@ -13,14 +13,19 @@ extern crate serde; #[macro_use] extern crate serde_derive; extern crate vte; +#[macro_use] +extern crate maplit; use failure::Error; use std::ops::{Deref, DerefMut, Range}; +use std::str; use std::time::{Duration, Instant}; #[macro_use] mod debug; +pub mod hyperlink; + /// Represents the index into screen.lines. Index 0 is the top of /// the scrollback (if any). The index of the top of the visible screen /// depends on the terminal dimensions and the scrollback size. @@ -1808,7 +1813,6 @@ impl vte::Perform for TerminalState { fn osc_dispatch(&mut self, osc: &[&[u8]]) { match osc { &[b"0", title] => { - use std::str; if let Ok(title) = str::from_utf8(title) { self.answerback.push( AnswerBack::TitleChanged(title.to_string()), @@ -1817,6 +1821,16 @@ impl vte::Perform for TerminalState { println!("OSC: failed to decode utf for {:?}", title); } } + &[b"8", params, url] => { + if let Ok(params) = str::from_utf8(params) { + println!("set URL params={}", params); + } + if let Ok(url) = str::from_utf8(url) { + println!("set URL attribute={}", url); + } else { + println!("OSC: failed to decode utf for {:?}", url); + } + } _ => { println!("OSC unhandled: {:?}", osc); }