1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-26 23:04:49 +03:00

use termwiz::Hyperlink

This commit is contained in:
Wez Furlong 2018-08-04 00:26:24 -07:00
parent f37de9cbe5
commit 1ec83706e7
6 changed files with 31 additions and 119 deletions

View File

@ -124,10 +124,10 @@ impl<'a> term::TerminalHost for TabHost<'a> {
fn click_link(&mut self, link: &Rc<Hyperlink>) { fn click_link(&mut self, link: &Rc<Hyperlink>) {
// TODO: make this configurable // TODO: make this configurable
let mut cmd = Command::new("xdg-open"); let mut cmd = Command::new("xdg-open");
cmd.arg(&link.url); cmd.arg(&link.uri());
match cmd.spawn() { match cmd.spawn() {
Ok(_) => {} Ok(_) => {}
Err(err) => eprintln!("failed to spawn xdg-open {}: {:?}", link.url, err), Err(err) => eprintln!("failed to spawn xdg-open {}: {:?}", link.uri(), err),
} }
} }

View File

@ -12,60 +12,7 @@ use std::collections::HashMap;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug, Clone, Eq, PartialEq)] pub use termwiz::escape::osc::Hyperlink;
pub struct Hyperlink {
/// The target
pub url: String,
/// The identifier. This can be used by the render layer to determine
/// which cells to underline on hover. This is for the usecase where
/// an application has drawn windows in the window and the URL has
/// wrapped lines within such a window.
pub id: String,
/// If the link was produced by an implicit or matching rule,
/// this field will be set to true.
pub implicit: bool,
}
impl Hyperlink {
pub fn new(url: &str, params: &HashMap<&str, &str>) -> Self {
let id = params.get("id").unwrap_or(&"");
Self {
url: url.into(),
id: (**id).into(),
implicit: false,
}
}
pub fn with_id(url: &str, id: &str) -> Self {
Self {
url: url.into(),
id: id.into(),
implicit: false,
}
}
}
/// 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
}
/// In addition to handling explicit escape sequences to enable /// In addition to handling explicit escape sequences to enable
/// hyperlinks, we also support defining rules that match text /// hyperlinks, we also support defining rules that match text
@ -174,11 +121,10 @@ impl Rule {
.into_iter() .into_iter()
.map(|m| { .map(|m| {
let url = m.expand(); let url = m.expand();
let link = Rc::new(Hyperlink { let link = Rc::new(Hyperlink::new_with_params(
url, url,
id: "".to_owned(), hashmap!{"implicit".into() => "1".into()},
implicit: true, ));
});
RuleMatch { RuleMatch {
link, link,
range: m.range(), range: m.range(),
@ -203,11 +149,10 @@ mod test {
Rule::match_hyperlinks(" http://example.com", &rules), Rule::match_hyperlinks(" http://example.com", &rules),
vec![RuleMatch { vec![RuleMatch {
range: 2..20, range: 2..20,
link: Rc::new(Hyperlink { link: Rc::new(Hyperlink::new_with_params(
url: "http://example.com".to_owned(), "http://example.com",
id: "".to_owned(), hashmap!{"implicit".into()=>"1".into()},
implicit: true, )),
}),
}] }]
); );
@ -217,47 +162,19 @@ mod test {
// Longest match first // Longest match first
RuleMatch { RuleMatch {
range: 18..34, range: 18..34,
link: Rc::new(Hyperlink { link: Rc::new(Hyperlink::new_with_params(
url: "mailto:woot@example.com".to_owned(), "mailto:woot@example.com",
id: "".to_owned(), hashmap!{"implicit".into()=>"1".into()},
implicit: true, )),
}),
}, },
RuleMatch { RuleMatch {
range: 2..17, range: 2..17,
link: Rc::new(Hyperlink { link: Rc::new(Hyperlink::new_with_params(
url: "mailto:foo@example.com".to_owned(), "mailto:foo@example.com",
id: "".to_owned(), hashmap!{"implicit".into()=>"1".into()},
implicit: true, )),
}),
}, },
] ]
); );
} }
#[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"
}
);
}
} }

View File

@ -3,7 +3,6 @@
extern crate bitflags; extern crate bitflags;
#[macro_use] #[macro_use]
extern crate failure; extern crate failure;
#[cfg(test)]
#[macro_use] #[macro_use]
extern crate maplit; extern crate maplit;
extern crate palette; extern crate palette;

View File

@ -165,7 +165,7 @@ impl Line {
for mut cell in &mut self.cells { for mut cell in &mut self.cells {
let link = cell.attrs.hyperlink.take(); let link = cell.attrs.hyperlink.take();
cell.attrs.hyperlink = match link.as_ref() { cell.attrs.hyperlink = match link.as_ref() {
Some(link) if link.implicit => None, Some(link) if link.params().contains_key("implicit") => None,
Some(link) => Some(Rc::clone(link)), Some(link) => Some(Rc::clone(link)),
None => None, None => None,
}; };

View File

@ -1,5 +1,4 @@
use super::*; use super::*;
use std::collections::HashMap;
use std::fmt::Write; use std::fmt::Write;
use termwiz::escape::csi::{ use termwiz::escape::csi::{
Cursor, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay, EraseInLine, Mode, Cursor, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay, EraseInLine, Mode,
@ -1532,19 +1531,15 @@ impl<'a> Performer<'a> {
self.host.set_title(&title); self.host.set_title(&title);
} }
OperatingSystemCommand::SetIconName(_) => {} OperatingSystemCommand::SetIconName(_) => {}
OperatingSystemCommand::SetHyperlink(Some(link)) => { OperatingSystemCommand::SetHyperlink(link) => {
let params: HashMap<_, _> = link self.set_hyperlink(link);
.params()
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect();
self.set_hyperlink(Some(Hyperlink::new(link.uri(), &params)));
}
OperatingSystemCommand::SetHyperlink(None) => {
self.set_hyperlink(None);
} }
OperatingSystemCommand::Unspecified(unspec) => { OperatingSystemCommand::Unspecified(unspec) => {
eprintln!("Unhandled {:?}", unspec); eprint!("Unhandled OSC ");
for item in unspec {
eprint!(" {}", String::from_utf8_lossy(&item));
}
eprintln!("");
} }
OperatingSystemCommand::ClearSelection(_) => { OperatingSystemCommand::ClearSelection(_) => {

View File

@ -7,7 +7,7 @@ mod c1;
mod csi; mod csi;
mod selection; mod selection;
use termwiz::escape::csi::{Edit, EraseInDisplay, EraseInLine}; use termwiz::escape::csi::{Edit, EraseInDisplay, EraseInLine};
use termwiz::escape::CSI; use termwiz::escape::{OperatingSystemCommand, CSI};
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct TestHost { struct TestHost {
@ -100,7 +100,8 @@ impl TestTerm {
} }
fn hyperlink(&mut self, link: &Rc<Hyperlink>) { fn hyperlink(&mut self, link: &Rc<Hyperlink>) {
self.print(format!("\x1b]8;id={};{}\x1b\\", link.id, link.url)); let osc = OperatingSystemCommand::SetHyperlink(Some(link.as_ref().clone()));
self.print(format!("{}", osc));
} }
fn hyperlink_off(&mut self) { fn hyperlink_off(&mut self) {
@ -465,7 +466,7 @@ fn test_scrollup() {
#[test] #[test]
fn test_hyperlinks() { fn test_hyperlinks() {
let mut term = TestTerm::new(3, 5, 0); let mut term = TestTerm::new(3, 5, 0);
let link = Rc::new(Hyperlink::with_id("http://example.com", "")); let link = Rc::new(Hyperlink::new("http://example.com"));
term.hyperlink(&link); term.hyperlink(&link);
term.print("hello"); term.print("hello");
term.hyperlink_off(); term.hyperlink_off();
@ -499,7 +500,7 @@ fn test_hyperlinks() {
Compare::TEXT | Compare::ATTRS, Compare::TEXT | Compare::ATTRS,
); );
let otherlink = Rc::new(Hyperlink::with_id("http://example.com/other", "w00t")); let otherlink = Rc::new(Hyperlink::new_with_id("http://example.com/other", "w00t"));
// Switching link and turning it off // Switching link and turning it off
term.hyperlink(&otherlink); term.hyperlink(&otherlink);