1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-20 11:17:15 +03:00

add concept of implicit hyperlink to termwiz

Moves that functionality from wezterm+term into termwiz
This commit is contained in:
Wez Furlong 2018-08-05 09:13:55 -07:00
parent 92f59f243e
commit e2461b2380
14 changed files with 152 additions and 113 deletions

View File

@ -25,6 +25,9 @@ directories = "~1.0"
[dependencies.term]
path = "term"
[dependencies.termwiz]
path = "termwiz"
[target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
freetype = "0.3"
servo-fontconfig = "~0.4"

View File

@ -5,7 +5,7 @@ use failure::{err_msg, Error};
use std;
use std::fs;
use std::io::prelude::*;
use term::hyperlink;
use termwiz::hyperlink;
use toml;
use term;

View File

@ -24,6 +24,7 @@ extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate term;
extern crate termwiz;
extern crate toml;
extern crate unicode_width;
#[macro_use]

View File

@ -17,7 +17,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::process::Child;
use std::process::Command;
use std::rc::Rc;
use term::hyperlink::Hyperlink;
use termwiz::hyperlink::Hyperlink;
use term::{self, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use xcb;

View File

@ -6,10 +6,6 @@ version = "0.1.0"
[dependencies]
bitflags = "~1.0"
failure = "~0.1"
maplit = "~1.0"
regex = "~0.2"
serde = "~1.0"
serde_derive = "~1.0"
unicode-segmentation = "~1.2"
unicode-width = "~0.1"

View File

@ -3,12 +3,6 @@
extern crate bitflags;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate maplit;
extern crate regex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate termwiz;
extern crate unicode_segmentation;
extern crate unicode_width;
@ -35,8 +29,7 @@ pub use screen::*;
pub mod selection;
use selection::{SelectionCoordinate, SelectionRange};
pub mod hyperlink;
use hyperlink::Hyperlink;
use termwiz::hyperlink::Hyperlink;
pub mod terminal;
pub use terminal::*;

View File

@ -1,4 +1,4 @@
use hyperlink::Rule;
use termwiz::hyperlink::Rule;
use std::ops::Range;
use std::str;
@ -266,7 +266,7 @@ impl Line {
// Clear any cells that have implicit hyperlinks
for mut cell in &mut self.cells {
let replace = match cell.attrs().hyperlink {
Some(ref link) if link.params().contains_key("implicit") => {
Some(ref link) if link.is_implicit() => {
Some(Cell::new_grapheme(
cell.str(),
cell.attrs().clone().set_hyperlink(None).clone(),

View File

@ -1,5 +1,6 @@
use super::*;
use termwiz::escape::parser::Parser;
use termwiz::hyperlink::Rule as HyperlinkRule;
/// Represents the host of the terminal.
/// Provides a means for sending data to the connected pty,
@ -63,7 +64,7 @@ impl Terminal {
physical_rows: usize,
physical_cols: usize,
scrollback_size: usize,
hyperlink_rules: Vec<hyperlink::Rule>,
hyperlink_rules: Vec<HyperlinkRule>,
) -> Terminal {
Terminal {
state: TerminalState::new(

View File

@ -5,6 +5,7 @@ use termwiz::escape::csi::{
Sgr,
};
use termwiz::escape::{Action, ControlCode, Esc, EscCode, OperatingSystemCommand, CSI};
use termwiz::hyperlink::Rule as HyperlinkRule;
use unicode_segmentation::UnicodeSegmentation;
struct TabStop {
@ -114,7 +115,7 @@ pub struct TerminalState {
tabs: TabStop,
hyperlink_rules: Vec<hyperlink::Rule>,
hyperlink_rules: Vec<HyperlinkRule>,
/// The terminal title string
title: String,
@ -146,7 +147,7 @@ impl TerminalState {
physical_rows: usize,
physical_cols: usize,
scrollback_size: usize,
hyperlink_rules: Vec<hyperlink::Rule>,
hyperlink_rules: Vec<HyperlinkRule>,
) -> TerminalState {
let screen = Screen::new(physical_rows, physical_cols, scrollback_size);
let alt_screen = Screen::new(physical_rows, physical_cols, 0);

View File

@ -6,6 +6,7 @@ authors = ["Wez Furlong"]
[dependencies]
terminfo = "~0.6"
palette = "~0.4"
regex = "~0.2"
serde = "~1.0"
serde_derive = "~1.0"
failure = "~0.1"

View File

@ -1,8 +1,8 @@
use base64;
use failure::{self, Error};
use num;
use std::collections::HashMap;
use std::fmt::{Display, Error as FmtError, Formatter};
pub use hyperlink::Hyperlink;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OperatingSystemCommand {
@ -92,87 +92,6 @@ impl Display for Selection {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hyperlink {
params: HashMap<String, String>,
uri: String,
}
impl Hyperlink {
pub fn uri(&self) -> &str {
&self.uri
}
pub fn params(&self) -> &HashMap<String, String> {
&self.params
}
pub fn new<S: Into<String>>(uri: S) -> Self {
Self {
uri: uri.into(),
params: HashMap::new(),
}
}
pub fn new_with_id<S: Into<String>, S2: Into<String>>(uri: S, id: S2) -> Self {
let mut params = HashMap::new();
params.insert("id".into(), id.into());
Self {
uri: uri.into(),
params,
}
}
pub fn new_with_params<S: Into<String>>(uri: S, params: HashMap<String, String>) -> Self {
Self {
uri: uri.into(),
params,
}
}
pub fn parse(osc: &[&[u8]]) -> Result<Option<Hyperlink>, failure::Error> {
ensure!(osc.len() == 3, "wrong param count");
if osc[1].len() == 0 && osc[2].len() == 0 {
// Clearing current hyperlink
Ok(None)
} else {
let param_str = String::from_utf8(osc[1].to_vec())?;
let uri = String::from_utf8(osc[2].to_vec())?;
let mut params = HashMap::new();
if param_str.len() > 0 {
for pair in param_str.split(':') {
let mut iter = pair.splitn(2, '=');
let key = iter.next().ok_or_else(|| failure::err_msg("bad params"))?;
let value = iter.next().ok_or_else(|| failure::err_msg("bad params"))?;
params.insert(key.to_owned(), value.to_owned());
}
}
Ok(Some(Hyperlink::new_with_params(uri, params)))
}
}
}
impl Display for Hyperlink {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
write!(f, "8;")?;
for (idx, (k, v)) in self.params.iter().enumerate() {
// TODO: protect against k, v containing : or =
if idx > 0 {
write!(f, ":")?;
}
write!(f, "{}={}", k, v)?;
}
// TODO: ensure that link.uri doesn't contain characters
// outside the range 32-126. Need to pull in a URI/URL
// crate to help with this.
write!(f, ";{}", self.uri)?;
Ok(())
}
}
impl OperatingSystemCommand {
pub fn parse(osc: &[&[u8]]) -> Self {
Self::internal_parse(osc).unwrap_or_else(|_| {

View File

@ -4,14 +4,113 @@
//! 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 failure::Error;
use failure::{err_msg, Error};
use regex::{Captures, Regex};
use serde::{self, Deserialize, Deserializer};
use std::collections::HashMap;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::ops::Range;
use std::rc::Rc;
pub use termwiz::escape::osc::Hyperlink;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hyperlink {
params: HashMap<String, String>,
uri: String,
/// If the link was produced by an implicit or matching rule,
/// this field will be set to true.
implicit: bool,
}
impl Hyperlink {
pub fn uri(&self) -> &str {
&self.uri
}
pub fn params(&self) -> &HashMap<String, String> {
&self.params
}
pub fn new<S: Into<String>>(uri: S) -> Self {
Self {
uri: uri.into(),
params: HashMap::new(),
implicit: false,
}
}
#[inline]
pub fn is_implicit(&self) -> bool {
self.implicit
}
pub fn new_implicit<S: Into<String>>(uri: S) -> Self {
Self {
uri: uri.into(),
params: HashMap::new(),
implicit: true,
}
}
pub fn new_with_id<S: Into<String>, S2: Into<String>>(uri: S, id: S2) -> Self {
let mut params = HashMap::new();
params.insert("id".into(), id.into());
Self {
uri: uri.into(),
params,
implicit:false,
}
}
pub fn new_with_params<S: Into<String>>(uri: S, params: HashMap<String, String>) -> Self {
Self {
uri: uri.into(),
params,
implicit:false,
}
}
pub fn parse(osc: &[&[u8]]) -> Result<Option<Hyperlink>, Error> {
ensure!(osc.len() == 3, "wrong param count");
if osc[1].len() == 0 && osc[2].len() == 0 {
// Clearing current hyperlink
Ok(None)
} else {
let param_str = String::from_utf8(osc[1].to_vec())?;
let uri = String::from_utf8(osc[2].to_vec())?;
let mut params = HashMap::new();
if param_str.len() > 0 {
for pair in param_str.split(':') {
let mut iter = pair.splitn(2, '=');
let key = iter.next().ok_or_else(|| err_msg("bad params"))?;
let value = iter.next().ok_or_else(|| err_msg("bad params"))?;
params.insert(key.to_owned(), value.to_owned());
}
}
Ok(Some(Hyperlink::new_with_params(uri, params)))
}
}
}
impl Display for Hyperlink {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
write!(f, "8;")?;
for (idx, (k, v)) in self.params.iter().enumerate() {
// TODO: protect against k, v containing : or =
if idx > 0 {
write!(f, ":")?;
}
write!(f, "{}={}", k, v)?;
}
// TODO: ensure that link.uri doesn't contain characters
// outside the range 32-126. Need to pull in a URI/URL
// crate to help with this.
write!(f, ";{}", self.uri)?;
Ok(())
}
}
/// In addition to handling explicit escape sequences to enable
/// hyperlinks, we also support defining rules that match text
@ -120,9 +219,8 @@ impl Rule {
.into_iter()
.map(|m| {
let url = m.expand();
let link = Rc::new(Hyperlink::new_with_params(
url,
hashmap!{"implicit".into() => "1".into()},
let link = Rc::new(Hyperlink::new_implicit(
url
));
RuleMatch {
link,
@ -148,9 +246,8 @@ mod test {
Rule::match_hyperlinks(" http://example.com", &rules),
vec![RuleMatch {
range: 2..20,
link: Rc::new(Hyperlink::new_with_params(
link: Rc::new(Hyperlink::new_implicit(
"http://example.com",
hashmap!{"implicit".into()=>"1".into()},
)),
}]
);
@ -161,16 +258,14 @@ mod test {
// Longest match first
RuleMatch {
range: 18..34,
link: Rc::new(Hyperlink::new_with_params(
link: Rc::new(Hyperlink::new_implicit(
"mailto:woot@example.com",
hashmap!{"implicit".into()=>"1".into()},
)),
},
RuleMatch {
range: 2..17,
link: Rc::new(Hyperlink::new_with_params(
link: Rc::new(Hyperlink::new_implicit(
"mailto:foo@example.com",
hashmap!{"implicit".into()=>"1".into()},
)),
},
]

View File

@ -57,8 +57,10 @@ extern crate num_derive;
extern crate derive_builder;
#[macro_use]
extern crate bitflags;
extern crate cassowary;
extern crate memmem;
extern crate regex;
extern crate smallvec;
extern crate unicode_segmentation;
extern crate unicode_width;
@ -67,6 +69,7 @@ pub mod caps;
pub mod cell;
pub mod color;
pub mod escape;
pub mod hyperlink;
pub mod input;
pub mod istty;
pub mod keymap;

View File

@ -49,6 +49,31 @@ impl Line {
self.bits &= !LineBits::DIRTY;
}
/// If we have any cells with an implicit hyperlink, remove the hyperlink
/// from the cell attributes but leave the remainder of the attributes alone.
fn invalidate_implicit_hyperlinks(&mut self) {
if (self.bits & LineBits::HAS_IMPLICIT_HYPERLINKS) == LineBits::NONE {
return;
}
for mut cell in &mut self.cells {
let replace = match cell.attrs().hyperlink {
Some(ref link) if link.is_implicit() => {
Some(Cell::new_grapheme(
cell.str(),
cell.attrs().clone().set_hyperlink(None).clone(),
))
}
_ => None,
};
if let Some(replace) = replace {
*cell = replace;
}
}
self.bits &= !LineBits::HAS_IMPLICIT_HYPERLINKS;
self.bits |= LineBits::SCANNED_IMPLICIT_HYPERLINKS|LineBits::DIRTY;
}
/// If we're about to modify a cell obscured by a double-width
/// character ahead of that cell, we need to nerf that sequence
/// of cells to avoid partial rendering concerns.
@ -58,6 +83,7 @@ impl Line {
/// to assign to an out of bounds index will not extend the cell array,
/// and it will not flag an error.
pub fn set_cell(&mut self, idx: usize, cell: Cell) {
self.invalidate_implicit_hyperlinks();
self.bits |= LineBits::DIRTY;
// Assumption: that the width of a grapheme is never > 2.
// This constrains the amount of look-back that we need to do here.