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:
parent
92f59f243e
commit
e2461b2380
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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(),
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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(|_| {
|
||||
|
@ -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()},
|
||||
)),
|
||||
},
|
||||
]
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user