diff options
author | Kirill Chibisov <contact@kchibisov.com> | 2022-07-10 20:11:28 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-10 20:11:28 +0300 |
commit | 694a52bcffeffdc9e163818c3b2ac5c39e26f1ef (patch) | |
tree | a774babc1869b4c700d7df1478dbbfe5b2c3bcda /alacritty_terminal/src | |
parent | 8451b75689b44f11ec1707af7e26d915772c3972 (diff) | |
download | alacritty-694a52bcffeffdc9e163818c3b2ac5c39e26f1ef.tar.gz alacritty-694a52bcffeffdc9e163818c3b2ac5c39e26f1ef.zip |
Add support for hyperlink escape sequence
This commit adds support for hyperlink escape sequence
`OSC 8 ; params ; URI ST`. The configuration option responsible for
those is `hints.enabled.hyperlinks`.
Fixes #922.
Diffstat (limited to 'alacritty_terminal/src')
-rw-r--r-- | alacritty_terminal/src/ansi.rs | 25 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/row.rs | 10 | ||||
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 84 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 8 |
4 files changed, 121 insertions, 6 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 393f6a8c..e30ed0f5 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -12,6 +12,7 @@ use vte::{Params, ParamsIter}; use alacritty_config_derive::ConfigDeserialize; use crate::index::{Column, Line}; +use crate::term::cell::Hyperlink; use crate::term::color::Rgb; /// Maximum time before a synchronized update is aborted. @@ -457,6 +458,9 @@ pub trait Handler { /// Report text area size in characters. fn text_area_size_chars(&mut self) {} + + /// Set hyperlink. + fn set_hyperlink(&mut self, _: Option<Hyperlink>) {} } /// Terminal cursor configuration. @@ -1007,6 +1011,27 @@ where } }, + // Hyperlink. + b"8" if params.len() > 2 => { + let link_params = params[1]; + let uri = str::from_utf8(params[2]).unwrap_or_default(); + + // The OSC 8 escape sequence must be stopped when getting an empty `uri`. + if uri.is_empty() { + self.handler.set_hyperlink(None); + return; + } + + // Link parameters are in format of `key1=value1:key2=value2`. Currently only key + // `id` is defined. + let id = link_params + .split(|&b| b == b':') + .find_map(|kv| kv.strip_prefix(b"id=")) + .and_then(|kv| str::from_utf8(kv).ok()); + + self.handler.set_hyperlink(Some(Hyperlink::new(id, uri))); + }, + // Get/set Foreground, Background, Cursor colors. b"10" | b"11" | b"12" => { if params.len() >= 2 { diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs index f6bcb022..900d2a76 100644 --- a/alacritty_terminal/src/grid/row.rs +++ b/alacritty_terminal/src/grid/row.rs @@ -171,6 +171,16 @@ impl<T> Row<T> { } } +impl<'a, T> IntoIterator for &'a Row<T> { + type IntoIter = slice::Iter<'a, T>; + type Item = &'a T; + + #[inline] + fn into_iter(self) -> slice::Iter<'a, T> { + self.inner.iter() + } +} + impl<'a, T> IntoIterator for &'a mut Row<T> { type IntoIter = slice::IterMut<'a, T>; type Item = &'a mut T; diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 716bcf9b..d3f45e9a 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -1,3 +1,4 @@ +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use bitflags::bitflags; @@ -33,6 +34,53 @@ bitflags! { } } +/// Counter for hyperlinks without explicit ID. +static HYPERLINK_ID_SUFFIX: AtomicU32 = AtomicU32::new(0); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +pub struct Hyperlink { + inner: Arc<HyperlinkInner>, +} + +impl Hyperlink { + pub fn new<T: ToString>(id: Option<T>, uri: T) -> Self { + let inner = Arc::new(HyperlinkInner::new(id, uri)); + Self { inner } + } + + pub fn id(&self) -> &str { + &self.inner.id + } + + pub fn uri(&self) -> &str { + &self.inner.uri + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] +struct HyperlinkInner { + /// Identifier for the given hyperlink. + id: String, + + /// Resource identifier of the hyperlink. + uri: String, +} + +impl HyperlinkInner { + pub fn new<T: ToString>(id: Option<T>, uri: T) -> Self { + let id = match id { + Some(id) => id.to_string(), + None => { + let mut id = HYPERLINK_ID_SUFFIX.fetch_add(1, Ordering::Relaxed).to_string(); + id.push_str("_alacritty"); + id + }, + }; + + Self { id, uri: uri.to_string() } + } +} + /// Trait for determining if a reset should be performed. pub trait ResetDiscriminant<T> { /// Value based on which equality for the reset will be determined. @@ -61,6 +109,8 @@ pub struct CellExtra { zerowidth: Vec<char>, underline_color: Option<Color>, + + hyperlink: Option<Hyperlink>, } /// Content and attributes of a single cell in the terminal grid. @@ -113,14 +163,17 @@ impl Cell { /// Set underline color on the cell. pub fn set_underline_color(&mut self, color: Option<Color>) { // If we reset color and we don't have zerowidth we should drop extra storage. - if color.is_none() && self.extra.as_ref().map_or(true, |extra| !extra.zerowidth.is_empty()) + if color.is_none() + && self + .extra + .as_ref() + .map_or(true, |extra| !extra.zerowidth.is_empty() || extra.hyperlink.is_some()) { self.extra = None; - return; + } else { + let extra = self.extra.get_or_insert(Default::default()); + Arc::make_mut(extra).underline_color = color; } - - let extra = self.extra.get_or_insert(Default::default()); - Arc::make_mut(extra).underline_color = color; } /// Underline color stored in this cell. @@ -128,6 +181,27 @@ impl Cell { pub fn underline_color(&self) -> Option<Color> { self.extra.as_ref()?.underline_color } + + /// Set hyperlink. + pub fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) { + let should_drop = hyperlink.is_none() + && self.extra.as_ref().map_or(true, |extra| { + !extra.zerowidth.is_empty() || extra.underline_color.is_some() + }); + + if should_drop { + self.extra = None; + } else { + let extra = self.extra.get_or_insert(Default::default()); + Arc::make_mut(extra).hyperlink = hyperlink; + } + } + + /// Hyperlink stored in this cell. + #[inline] + pub fn hyperlink(&self) -> Option<Hyperlink> { + self.extra.as_ref()?.hyperlink.clone() + } } impl GridCell for Cell { diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index d600cc78..1f3269c2 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -16,7 +16,7 @@ use crate::event::{Event, EventListener}; use crate::grid::{Dimensions, Grid, GridIterator, Scroll}; use crate::index::{self, Boundary, Column, Direction, Line, Point, Side}; use crate::selection::{Selection, SelectionRange, SelectionType}; -use crate::term::cell::{Cell, Flags, LineLength}; +use crate::term::cell::{Cell, Flags, Hyperlink, LineLength}; use crate::term::color::{Colors, Rgb}; use crate::vi_mode::{ViModeCursor, ViMotion}; @@ -1679,6 +1679,12 @@ impl<T: EventListener> Handler for Term<T> { } } + #[inline] + fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) { + trace!("Setting hyperlink: {:?}", hyperlink); + self.grid.cursor.template.set_hyperlink(hyperlink); + } + /// Set a terminal attribute. #[inline] fn terminal_attribute(&mut self, attr: Attr) { |