diff options
author | Anhad Singh <62820092+Andy-Python-Programmer@users.noreply.github.com> | 2023-05-24 06:35:58 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-23 20:35:58 +0000 |
commit | cb7ad5b7e6893787c2006cc8cb09fbbc4711c0f7 (patch) | |
tree | e8c5315bb620e6d4e1564dfd6825303b498a3d6d | |
parent | f0379f2da751e81ba05bbf65ecb5e59590f39be4 (diff) | |
download | alacritty-cb7ad5b7e6893787c2006cc8cb09fbbc4711c0f7.tar.gz alacritty-cb7ad5b7e6893787c2006cc8cb09fbbc4711c0f7.zip |
Switch to VTE's built-in ansi feature
Co-authored-by: Christian Duerr <contact@christianduerr.com>
-rw-r--r-- | Cargo.lock | 14 | ||||
-rw-r--r-- | alacritty/src/config/bell.rs | 11 | ||||
-rw-r--r-- | alacritty/src/config/color.rs | 68 | ||||
-rw-r--r-- | alacritty/src/config/window.rs | 18 | ||||
-rw-r--r-- | alacritty/src/display/color.rs | 12 | ||||
-rw-r--r-- | alacritty/src/display/content.rs | 11 | ||||
-rw-r--r-- | alacritty/src/display/hint.rs | 14 | ||||
-rw-r--r-- | alacritty/src/display/mod.rs | 2 | ||||
-rw-r--r-- | alacritty/src/renderer/rects.rs | 2 | ||||
-rw-r--r-- | alacritty_terminal/Cargo.toml | 2 | ||||
-rw-r--r-- | alacritty_terminal/src/ansi.rs | 1871 | ||||
-rw-r--r-- | alacritty_terminal/src/config/mod.rs | 21 | ||||
-rw-r--r-- | alacritty_terminal/src/event_loop.rs | 5 | ||||
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 13 | ||||
-rw-r--r-- | alacritty_terminal/src/term/color.rs | 104 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 76 | ||||
-rw-r--r-- | alacritty_terminal/tests/ref.rs | 2 |
17 files changed, 208 insertions, 2038 deletions
@@ -1487,18 +1487,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.57" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4ec6d5fe0b140acb27c9a0444118cf55bfbb4e0b259739429abb4521dd67c16" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1945,10 +1945,12 @@ dependencies = [ [[package]] name = "vte" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ + "log", + "serde", "utf8parse", "vte_generate_state_changes", ] diff --git a/alacritty/src/config/bell.rs b/alacritty/src/config/bell.rs index 2516e2b3..fbf5be2f 100644 --- a/alacritty/src/config/bell.rs +++ b/alacritty/src/config/bell.rs @@ -23,7 +23,7 @@ pub struct BellConfig { impl Default for BellConfig { fn default() -> Self { Self { - color: Rgb { r: 255, g: 255, b: 255 }, + color: Rgb::new(255, 255, 255), animation: Default::default(), command: Default::default(), duration: Default::default(), @@ -39,7 +39,7 @@ impl BellConfig { /// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert /// Penner's Easing Functions. -#[derive(ConfigDeserialize, Clone, Copy, Debug, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Clone, Copy, Debug, PartialEq, Eq)] pub enum BellAnimation { // CSS animation. Ease, @@ -56,15 +56,10 @@ pub enum BellAnimation { // Penner animation. EaseOutQuint, // Penner animation. + #[default] EaseOutExpo, // Penner animation. EaseOutCirc, // Penner animation. Linear, } - -impl Default for BellAnimation { - fn default() -> Self { - BellAnimation::EaseOutExpo - } -} diff --git a/alacritty/src/config/color.rs b/alacritty/src/config/color.rs index 5028347c..23c18e50 100644 --- a/alacritty/src/config/color.rs +++ b/alacritty/src/config/color.rs @@ -52,8 +52,8 @@ pub struct HintStartColors { impl Default for HintStartColors { fn default() -> Self { Self { - foreground: CellRgb::Rgb(Rgb { r: 0x1d, g: 0x1f, b: 0x21 }), - background: CellRgb::Rgb(Rgb { r: 0xe9, g: 0xff, b: 0x5e }), + foreground: CellRgb::Rgb(Rgb::new(0x1d, 0x1f, 0x21)), + background: CellRgb::Rgb(Rgb::new(0xe9, 0xff, 0x5e)), } } } @@ -67,8 +67,8 @@ pub struct HintEndColors { impl Default for HintEndColors { fn default() -> Self { Self { - foreground: CellRgb::Rgb(Rgb { r: 0xe9, g: 0xff, b: 0x5e }), - background: CellRgb::Rgb(Rgb { r: 0x1d, g: 0x1f, b: 0x21 }), + foreground: CellRgb::Rgb(Rgb::new(0xe9, 0xff, 0x5e)), + background: CellRgb::Rgb(Rgb::new(0x1d, 0x1f, 0x21)), } } } @@ -139,8 +139,8 @@ pub struct FocusedMatchColors { impl Default for FocusedMatchColors { fn default() -> Self { Self { - background: CellRgb::Rgb(Rgb { r: 0x00, g: 0x00, b: 0x00 }), - foreground: CellRgb::Rgb(Rgb { r: 0xff, g: 0xff, b: 0xff }), + background: CellRgb::Rgb(Rgb::new(0x00, 0x00, 0x00)), + foreground: CellRgb::Rgb(Rgb::new(0xff, 0xff, 0xff)), } } } @@ -154,8 +154,8 @@ pub struct MatchColors { impl Default for MatchColors { fn default() -> Self { Self { - background: CellRgb::Rgb(Rgb { r: 0xff, g: 0xff, b: 0xff }), - foreground: CellRgb::Rgb(Rgb { r: 0x00, g: 0x00, b: 0x00 }), + background: CellRgb::Rgb(Rgb::new(0xff, 0xff, 0xff)), + foreground: CellRgb::Rgb(Rgb::new(0x00, 0x00, 0x00)), } } } @@ -177,8 +177,8 @@ pub struct PrimaryColors { impl Default for PrimaryColors { fn default() -> Self { PrimaryColors { - background: Rgb { r: 0x1d, g: 0x1f, b: 0x21 }, - foreground: Rgb { r: 0xc5, g: 0xc8, b: 0xc6 }, + background: Rgb::new(0x1d, 0x1f, 0x21), + foreground: Rgb::new(0xc5, 0xc8, 0xc6), bright_foreground: Default::default(), dim_foreground: Default::default(), } @@ -200,14 +200,14 @@ pub struct NormalColors { impl Default for NormalColors { fn default() -> Self { NormalColors { - black: Rgb { r: 0x1d, g: 0x1f, b: 0x21 }, - red: Rgb { r: 0xcc, g: 0x66, b: 0x66 }, - green: Rgb { r: 0xb5, g: 0xbd, b: 0x68 }, - yellow: Rgb { r: 0xf0, g: 0xc6, b: 0x74 }, - blue: Rgb { r: 0x81, g: 0xa2, b: 0xbe }, - magenta: Rgb { r: 0xb2, g: 0x94, b: 0xbb }, - cyan: Rgb { r: 0x8a, g: 0xbe, b: 0xb7 }, - white: Rgb { r: 0xc5, g: 0xc8, b: 0xc6 }, + black: Rgb::new(0x1d, 0x1f, 0x21), + red: Rgb::new(0xcc, 0x66, 0x66), + green: Rgb::new(0xb5, 0xbd, 0x68), + yellow: Rgb::new(0xf0, 0xc6, 0x74), + blue: Rgb::new(0x81, 0xa2, 0xbe), + magenta: Rgb::new(0xb2, 0x94, 0xbb), + cyan: Rgb::new(0x8a, 0xbe, 0xb7), + white: Rgb::new(0xc5, 0xc8, 0xc6), } } } @@ -227,14 +227,14 @@ pub struct BrightColors { impl Default for BrightColors { fn default() -> Self { BrightColors { - black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, - red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, - green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, - yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, - cyan: Rgb { r: 0x70, g: 0xc0, b: 0xb1 }, - white: Rgb { r: 0xea, g: 0xea, b: 0xea }, + black: Rgb::new(0x66, 0x66, 0x66), + red: Rgb::new(0xd5, 0x4e, 0x53), + green: Rgb::new(0xb9, 0xca, 0x4a), + yellow: Rgb::new(0xe7, 0xc5, 0x47), + blue: Rgb::new(0x7a, 0xa6, 0xda), + magenta: Rgb::new(0xc3, 0x97, 0xd8), + cyan: Rgb::new(0x70, 0xc0, 0xb1), + white: Rgb::new(0xea, 0xea, 0xea), } } } @@ -254,14 +254,14 @@ pub struct DimColors { impl Default for DimColors { fn default() -> Self { DimColors { - black: Rgb { r: 0x13, g: 0x14, b: 0x15 }, - red: Rgb { r: 0x86, g: 0x43, b: 0x43 }, - green: Rgb { r: 0x77, g: 0x7c, b: 0x44 }, - yellow: Rgb { r: 0x9e, g: 0x82, b: 0x4c }, - blue: Rgb { r: 0x55, g: 0x6a, b: 0x7d }, - magenta: Rgb { r: 0x75, g: 0x61, b: 0x7b }, - cyan: Rgb { r: 0x5b, g: 0x7d, b: 0x78 }, - white: Rgb { r: 0x82, g: 0x84, b: 0x82 }, + black: Rgb::new(0x13, 0x14, 0x15), + red: Rgb::new(0x86, 0x43, 0x43), + green: Rgb::new(0x77, 0x7c, 0x44), + yellow: Rgb::new(0x9e, 0x82, 0x4c), + blue: Rgb::new(0x55, 0x6a, 0x7d), + magenta: Rgb::new(0x75, 0x61, 0x7b), + cyan: Rgb::new(0x5b, 0x7d, 0x78), + white: Rgb::new(0x82, 0x84, 0x82), } } } diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs index 98bc18b6..db29fd85 100644 --- a/alacritty/src/config/window.rs +++ b/alacritty/src/config/window.rs @@ -154,8 +154,9 @@ impl Default for Identity { } } -#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] pub enum StartupMode { + #[default] Windowed, Maximized, Fullscreen, @@ -163,14 +164,9 @@ pub enum StartupMode { SimpleFullscreen, } -impl Default for StartupMode { - fn default() -> StartupMode { - StartupMode::Windowed - } -} - -#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] pub enum Decorations { + #[default] Full, #[cfg(target_os = "macos")] Transparent, @@ -179,12 +175,6 @@ pub enum Decorations { None, } -impl Default for Decorations { - fn default() -> Decorations { - Decorations::Full - } -} - /// Window Dimensions. /// /// Newtype to avoid passing values incorrectly. diff --git a/alacritty/src/display/color.rs b/alacritty/src/display/color.rs index 6e0de048..054ca314 100644 --- a/alacritty/src/display/color.rs +++ b/alacritty/src/display/color.rs @@ -95,11 +95,11 @@ impl List { { self[index] = indexed_color.color; } else { - self[index] = Rgb { - r: if r == 0 { 0 } else { r * 40 + 55 }, - b: if b == 0 { 0 } else { b * 40 + 55 }, - g: if g == 0 { 0 } else { g * 40 + 55 }, - }; + self[index] = Rgb::new( + if r == 0 { 0 } else { r * 40 + 55 }, + if b == 0 { 0 } else { b * 40 + 55 }, + if g == 0 { 0 } else { g * 40 + 55 }, + ); } index += 1; } @@ -126,7 +126,7 @@ impl List { } let value = i * 10 + 8; - self[index] = Rgb { r: value, g: value, b: value }; + self[index] = Rgb::new(value, value, value); index += 1; } diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index ca49c01a..da211094 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -121,7 +121,7 @@ impl<'a> RenderableContent<'a> { let insufficient_contrast = (!matches!(cursor_color, CellRgb::Rgb(_)) || !matches!(text_color, CellRgb::Rgb(_))) - && cell.fg.contrast(cell.bg) < MIN_CURSOR_CONTRAST; + && cell.fg.contrast(*cell.bg) < MIN_CURSOR_CONTRAST; // Convert from cell colors to RGB. let mut text_color = text_color.color(cell.fg, cell.bg); @@ -307,8 +307,11 @@ impl RenderableCell { let config = &content.config; match fg { Color::Spec(rgb) => match flags & Flags::DIM { - Flags::DIM => rgb * DIM_FACTOR, - _ => rgb, + Flags::DIM => { + let rgb: Rgb = rgb.into(); + rgb * DIM_FACTOR + }, + _ => rgb.into(), }, Color::Named(ansi) => { match (config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { @@ -350,7 +353,7 @@ impl RenderableCell { #[inline] fn compute_bg_rgb(content: &mut RenderableContent<'_>, bg: Color) -> Rgb { match bg { - Color::Spec(rgb) => rgb, + Color::Spec(rgb) => rgb.into(), Color::Named(ansi) => content.color(ansi as usize), Color::Indexed(idx) => content.color(idx as usize), } diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index 12047011..c30a88c5 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -647,25 +647,25 @@ mod tests { #[test] fn collect_unique_hyperlinks() { let mut term = mock_term("000\r\n111"); - term.goto(Line(0), Column(0)); + term.goto(0, 0); let hyperlink_foo = Hyperlink::new(Some("1"), String::from("foo")); let hyperlink_bar = Hyperlink::new(Some("2"), String::from("bar")); // Create 2 hyperlinks on the first line. - term.set_hyperlink(Some(hyperlink_foo.clone())); + term.set_hyperlink(Some(hyperlink_foo.clone().into())); term.input('b'); term.input('a'); - term.set_hyperlink(Some(hyperlink_bar.clone())); + term.set_hyperlink(Some(hyperlink_bar.clone().into())); term.input('r'); - term.set_hyperlink(Some(hyperlink_foo.clone())); - term.goto(Line(1), Column(0)); + term.set_hyperlink(Some(hyperlink_foo.clone().into())); + term.goto(1, 0); // Ditto for the second line. - term.set_hyperlink(Some(hyperlink_foo)); + term.set_hyperlink(Some(hyperlink_foo.into())); term.input('b'); term.input('a'); - term.set_hyperlink(Some(hyperlink_bar)); + term.set_hyperlink(Some(hyperlink_bar.into())); term.input('r'); term.set_hyperlink(None); diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index 4d9c1540..b575b1a8 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -73,7 +73,7 @@ const BACKWARD_SEARCH_LABEL: &str = "Backward Search: "; const SHORTENER: char = '…'; /// Color which is used to highlight damaged rects when debugging. -const DAMAGE_RECT_COLOR: Rgb = Rgb { r: 255, g: 0, b: 255 }; +const DAMAGE_RECT_COLOR: Rgb = Rgb::new(255, 0, 255); #[derive(Debug)] pub enum Error { diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs index 31818f60..8f6204c2 100644 --- a/alacritty/src/renderer/rects.rs +++ b/alacritty/src/renderer/rects.rs @@ -366,7 +366,7 @@ impl RectRenderer { let y = -rect.y / half_height + 1.0; let width = rect.width / half_width; let height = rect.height / half_height; - let Rgb { r, g, b } = rect.color; + let (r, g, b) = rect.color.as_tuple(); let a = (rect.alpha * 255.) as u8; // Make quad vertices. diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 5e4efd0b..99a14e11 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -30,7 +30,7 @@ regex-automata = "0.1.9" serde = { version = "1", features = ["derive", "rc"] } serde_yaml = "0.8" unicode-width = "0.1" -vte = { version = "0.10.0", default-features = false } +vte = { version = "0.11.1", default-features = false, features = ["ansi", "serde"] } [target.'cfg(unix)'.dependencies] nix = { version = "0.26.2", default-features = false, features = ["term"] } diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index c47007d8..b596cc82 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -1,1862 +1,67 @@ //! ANSI Terminal Stream Parsing. -use std::convert::TryFrom; -use std::fmt::Write; -use std::time::{Duration, Instant}; -use std::{iter, str}; +pub use vte::ansi::*; -use log::{debug, trace}; -use serde::{Deserialize, Serialize}; -use vte::{Params, ParamsIter}; +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub struct CursorShapeShim(CursorShape); -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. -const SYNC_UPDATE_TIMEOUT: Duration = Duration::from_millis(150); - -/// Maximum number of bytes read in one synchronized update (2MiB). -const SYNC_BUFFER_SIZE: usize = 0x20_0000; - -/// Number of bytes in the synchronized update DCS sequence before the passthrough parameters. -const SYNC_ESCAPE_START_LEN: usize = 5; - -/// Start of the DCS sequence for beginning synchronized updates. -const SYNC_START_ESCAPE_START: [u8; SYNC_ESCAPE_START_LEN] = [b'\x1b', b'P', b'=', b'1', b's']; - -/// Start of the DCS sequence for terminating synchronized updates. -const SYNC_END_ESCAPE_START: [u8; SYNC_ESCAPE_START_LEN] = [b'\x1b', b'P', b'=', b'2', b's']; - -/// Parse colors in XParseColor format. -fn xparse_color(color: &[u8]) -> Option<Rgb> { - if !color.is_empty() && color[0] == b'#' { - parse_legacy_color(&color[1..]) - } else if color.len() >= 4 && &color[..4] == b"rgb:" { - parse_rgb_color(&color[4..]) - } else { - None +impl Default for CursorShapeShim { + fn default() -> CursorShapeShim { + CursorShapeShim(CursorShape::Block) } } -/// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format. -fn parse_rgb_color(color: &[u8]) -> Option<Rgb> { - let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>(); - - if colors.len() != 3 { - return None; +impl From<CursorShapeShim> for CursorShape { + fn from(value: CursorShapeShim) -> Self { + value.0 } - - // Scale values instead of filling with `0`s. - let scale = |input: &str| { - if input.len() > 4 { - None - } else { - let max = u32::pow(16, input.len() as u32) - 1; - let value = u32::from_str_radix(input, 16).ok()?; - Some((255 * value / max) as u8) - } - }; - - Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? }) } -/// Parse colors in `#r(rrr)g(ggg)b(bbb)` format. -fn parse_legacy_color(color: &[u8]) -> Option<Rgb> { - let item_len = color.len() / 3; - - // Truncate/Fill to two byte precision. - let color_from_slice = |slice: &[u8]| { - let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4; - Some((col >> (4 * slice.len().saturating_sub(1))) as u8) - }; - - Some(Rgb { - r: color_from_slice(&color[0..item_len])?, - g: color_from_slice(&color[item_len..item_len * 2])?, - b: color_from_slice(&color[item_len * 2..])?, - }) -} - -fn parse_number(input: &[u8]) -> Option<u8> { - if input.is_empty() { - return None; - } - let mut num: u8 = 0; - for c in input { - let c = *c as char; - if let Some(digit) = c.to_digit(10) { - num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) { - Some(v) => v, - None => return None, - } - } else { - return None; - } - } - Some(num) -} - -/// Internal state for VTE processor. -#[derive(Debug, Default)] -struct ProcessorState { - /// Last processed character for repetition. - preceding_char: Option<char>, - - /// DCS sequence waiting for termination. - dcs: Option<Dcs>, - - /// State for synchronized terminal updates. - sync_state: SyncState, -} - -#[derive(Debug)] -struct SyncState { - /// Expiration time of the synchronized update. - timeout: Option<Instant>, - - /// Sync DCS waiting for termination sequence. - pending_dcs: Option<Dcs>, - - /// Bytes read during the synchronized update. - buffer: Vec<u8>, -} - -impl Default for SyncState { - fn default() -> Self { - Self { buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), pending_dcs: None, timeout: None } - } -} - -/// Pending DCS sequence. -#[derive(Debug)] -enum Dcs { - /// Begin of the synchronized update. - SyncStart, - - /// End of the synchronized update. - SyncEnd, -} - -/// The processor wraps a `vte::Parser` to ultimately call methods on a Handler. -#[derive(Default)] -pub struct Processor { - state: ProcessorState, - parser: vte::Parser, -} - -impl Processor { - #[inline] - pub fn new() -> Self { - Self::default() - } - - /// Process a new byte from the PTY. - #[inline] - pub fn advance<H>(&mut self, handler: &mut H, byte: u8) - where - H: Handler, - { - if self.state.sync_state.timeout.is_none() { - let mut performer = Performer::new(&mut self.state, handler); - self.parser.advance(&mut performer, byte); - } else { - self.advance_sync(handler, byte); - } - } - - /// End a synchronized update. - pub fn stop_sync<H>(&mut self, handler: &mut H) - where - H: Handler, - { - // Process all synchronized bytes. - for i in 0..self.state.sync_state.buffer.len() { - let byte = self.state.sync_state.buffer[i]; - let mut performer = Performer::new(&mut self.state, handler); - self.parser.advance(&mut performer, byte); - } - - // Resetting state after processing makes sure we don't interpret buffered sync escapes. - self.state.sync_state.buffer.clear(); - self.state.sync_state.timeout = None; - } +struct CursorShapeVisitor; - /// Synchronized update expiration time. - #[inline] - pub fn sync_timeout(&self) -> Option<&Instant> { - self.state.sync_state.timeout.as_ref() - } +impl<'de> serde::de::Visitor<'de> for CursorShapeVisitor { + type Value = CursorShapeShim; - /// Number of bytes in the synchronization buffer. - #[inline] - pub fn sync_bytes_count(&self) -> usize { - self.state.sync_state.buffer.len() + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("one of `Block`, `Underline`, `Beam`") } - /// Process a new byte during a synchronized update. - #[cold] - fn advance_sync<H>(&mut self, handler: &mut H, byte: u8) + fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where - H: Handler, + E: serde::de::Error, { - self.state.sync_state.buffer.push(byte); - - // Handle sync DCS escape sequences. - match self.state.sync_state.pending_dcs { - Some(_) => self.advance_sync_dcs_end(handler, byte), - None => self.advance_sync_dcs_start(), - } - } - - /// Find the start of sync DCS sequences. - fn advance_sync_dcs_start(&mut self) { - // Get the last few bytes for comparison. - let len = self.state.sync_state.buffer.len(); - let offset = len.saturating_sub(SYNC_ESCAPE_START_LEN); - let end = &self.state.sync_state.buffer[offset..]; - - // Check for extension/termination of the synchronized update. - if end == SYNC_START_ESCAPE_START { - self.state.sync_state.pending_dcs = Some(Dcs::SyncStart); - } else if end == SYNC_END_ESCAPE_START || len >= SYNC_BUFFER_SIZE - 1 { - self.state.sync_state.pending_dcs = Some(Dcs::SyncEnd); + match s.to_lowercase().as_str() { + "block" => Ok(CursorShapeShim(CursorShape::Block)), + "underline" => Ok(CursorShapeShim(CursorShape::Underline)), + "beam" => Ok(CursorShapeShim(CursorShape::Beam)), + _ => Err(E::custom(format!( + "unknown variant `{0}`, expected {1}", + s, "one of `Block`, `Underline`, `Beam`" + ))), } } +} - /// Parse the DCS termination sequence for synchronized updates. - fn advance_sync_dcs_end<H>(&mut self, handler: &mut H, byte: u8) +impl<'de> serde::Deserialize<'de> for CursorShapeShim { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where - H: Handler, + D: serde::Deserializer<'de>, { - match byte { - // Ignore DCS passthrough characters. - 0x00..=0x17 | 0x19 | 0x1c..=0x7f | 0xa0..=0xff => (), - // Cancel the DCS sequence. - 0x18 | 0x1a | 0x80..=0x9f => self.state.sync_state.pending_dcs = None, - // Dispatch on ESC. - 0x1b => match self.state.sync_state.pending_dcs.take() { - Some(Dcs::SyncStart) => { - self.state.sync_state.timeout = Some(Instant::now() + SYNC_UPDATE_TIMEOUT); - }, - Some(Dcs::SyncEnd) => self.stop_sync(handler), - None => (), - }, - } + deserializer.deserialize_str(CursorShapeVisitor) } } -/// Helper type that implements `vte::Perform`. -/// -/// Processor creates a Performer when running advance and passes the Performer -/// to `vte::Parser`. -struct Performer<'a, H: Handler> { - state: &'a mut ProcessorState, - handler: &'a mut H, -} - -impl<'a, H: Handler + 'a> Performer<'a, H> { - /// Create a performer. - #[inline] - pub fn new<'b>(state: &'b mut ProcessorState, handler: &'b mut H) -> Performer<'b, H> { - Performer { state, handler } - } -} - -/// Type that handles actions from the parser. -/// -/// XXX Should probably not provide default impls for everything, but it makes -/// writing specific handler impls for tests far easier. -pub trait Handler { - /// OSC to set window title. - fn set_title(&mut self, _: Option<String>) {} - - /// Set the cursor style. - fn set_cursor_style(&mut self, _: Option<CursorStyle>) {} - - /// Set the cursor shape. - fn set_cursor_shape(&mut self, _shape: CursorShape) {} - - /// A character to be displayed. - fn input(&mut self, _c: char) {} - - /// Set cursor to position. - fn goto(&mut self, _: Line, _: Column) {} - - /// Set cursor to specific row. - fn goto_line(&mut self, _: Line) {} - - /// Set cursor to specific column. - fn goto_col(&mut self, _: Column) {} - - /// Insert blank characters in current line starting from cursor. - fn insert_blank(&mut self, _: usize) {} - - /// Move cursor up `rows`. - fn move_up(&mut self, _: usize) {} - - /// Move cursor down `rows`. - fn move_down(&mut self, _: usize) {} - - /// Identify the terminal (should write back to the pty stream). - fn identify_terminal(&mut self, _intermediate: Option<char>) {} - - /// Report device status. - fn device_status(&mut self, _: usize) {} - - /// Move cursor forward `cols`. - fn move_forward(&mut self, _: Column) {} - - /// Move cursor backward `cols`. - fn move_backward(&mut self, _: Column) {} - - /// Move cursor down `rows` and set to column 1. - fn move_down_and_cr(&mut self, _: usize) {} - - /// Move cursor up `rows` and set to column 1. - fn move_up_and_cr(&mut self, _: usize) {} - - /// Put `count` tabs. - fn put_tab(&mut self, _count: u16) {} - - /// Backspace `count` characters. - fn backspace(&mut self) {} - - /// Carriage return. - fn carriage_return(&mut self) {} - - /// Linefeed. - fn linefeed(&mut self) {} - - /// Ring the bell. - /// - /// Hopefully this is never implemented. - fn bell(&mut self) {} - - /// Substitute char under cursor. - fn substitute(&mut self) {} - - /// Newline. - fn newline(&mut self) {} - - /// Set current position as a tabstop. - fn set_horizontal_tabstop(&mut self) {} - - /// Scroll up `rows` rows. - fn scroll_up(&mut self, _: usize) {} - - /// Scroll down `rows` rows. - fn scroll_down(&mut self, _: usize) {} - - /// Insert `count` blank lines. - fn insert_blank_lines(&mut self, _: usize) {} - - /// Delete `count` lines. - fn delete_lines(&mut self, _: usize) {} - - /// Erase `count` chars in current line following cursor. - /// - /// Erase means resetting to the default state (default colors, no content, - /// no mode flags). - fn erase_chars(&mut self, _: Column) {} - - /// Delete `count` chars. - /// - /// Deleting a character is like the delete key on the keyboard - everything - /// to the right of the deleted things is shifted left. - fn delete_chars(&mut self, _: usize) {} - - /// Move backward `count` tabs. - fn move_backward_tabs(&mut self, _count: u16) {} - - /// Move forward `count` tabs. - fn move_forward_tabs(&mut self, _count: u16) {} - - /// Save current cursor position. - fn save_cursor_position(&mut self) {} - - /// Restore cursor position. - fn restore_cursor_position(&mut self) {} - - /// Clear current line. - fn clear_line(&mut self, _mode: LineClearMode) {} - - /// Clear screen. - fn clear_screen(&mut self, _mode: ClearMode) {} - - /// Clear tab stops. - fn clear_tabs(&mut self, _mode: TabulationClearMode) {} - - /// Reset terminal state. - fn reset_state(&mut self) {} - - /// Reverse Index. - /// - /// Move the active position to the same horizontal position on the - /// preceding line. If the active position is at the top margin, a scroll - /// down is performed. - fn reverse_index(&mut self) {} - - /// Set a terminal attribute. - fn terminal_attribute(&mut self, _attr: Attr) {} - - /// Set mode. - fn set_mode(&mut self, _mode: Mode) {} - - /// Unset mode. - fn unset_mode(&mut self, _: Mode) {} - - /// DECSTBM - Set the terminal scrolling region. - fn set_scrolling_region(&mut self, _top: usize, _bottom: Option<usize>) {} - - /// DECKPAM - Set keypad to applications mode (ESCape instead of digits). - fn set_keypad_application_mode(&mut self) {} - - /// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq). - fn unset_keypad_application_mode(&mut self) {} - - /// Set one of the graphic character sets, G0 to G3, as the active charset. - /// - /// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in, - /// shift out and locking shift depending on the set being activated. - fn set_active_charset(&mut self, _: CharsetIndex) {} - - /// Assign a graphic character set to G0, G1, G2 or G3. - /// - /// 'Designate' a graphic character set as one of G0 to G3, so that it can - /// later be 'invoked' by `set_active_charset`. - fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {} - - /// Set an indexed color value. - fn set_color(&mut self, _: usize, _: Rgb) {} - - /// Respond to a color query escape sequence. - fn dynamic_color_sequence(&mut self, _: String, _: usize, _: &str) {} - - /// Reset an indexed color to original value. - fn reset_color(&mut self, _: usize) {} - - /// Store data into clipboard. - fn clipboard_store(&mut self, _: u8, _: &[u8]) {} - - /// Load data from clipboard. - fn clipboard_load(&mut self, _: u8, _: &str) {} - - /// Run the decaln routine. - fn decaln(&mut self) {} - - /// Push a title onto the stack. - fn push_title(&mut self) {} - - /// Pop the last title from the stack. - fn pop_title(&mut self) {} - - /// Report text area size in pixels. - fn text_area_size_pixels(&mut self) {} - - /// 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. -#[derive(ConfigDeserialize, Default, Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub struct CursorStyle { - pub shape: CursorShape, - pub blinking: bool, -} - -/// Terminal cursor shape. -#[derive(ConfigDeserialize, Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub enum CursorShape { - /// Cursor is a block like `▒`. - Block, - - /// Cursor is an underscore like `_`. - Underline, - - /// Cursor is a vertical bar `⎸`. - Beam, - - /// Cursor is a box like `☐`. - #[config(skip)] - HollowBlock, - - /// Invisible cursor. - #[config(skip)] - Hidden, -} - -impl Default for CursorShape { - fn default() -> CursorShape { - CursorShape::Block - } -} - -/// Terminal modes. -#[derive(Debug, Eq, PartialEq)] -pub enum Mode { - /// ?1 - CursorKeys = 1, - /// Select 80 or 132 columns per page (DECCOLM). - /// - /// CSI ? 3 h -> set 132 column font. - /// CSI ? 3 l -> reset 80 column font. - /// - /// Additionally, - /// - /// * set margins to default positions - /// * erases all data in page memory - /// * resets DECLRMM to unavailable - /// * clears data from the status line (if set to host-writable) - ColumnMode = 3, - /// IRM Insert Mode. - /// - /// NB should be part of non-private mode enum. - /// - /// * `CSI 4 h` change to insert mode - /// * `CSI 4 l` reset to replacement mode - Insert = 4, - /// ?6 - Origin = 6, - /// ?7 - LineWrap = 7, - /// ?12 - BlinkingCursor = 12, - /// 20 - /// - /// NB This is actually a private mode. We should consider adding a second - /// enumeration for public/private modesets. - LineFeedNewLine = 20, - /// ?25 - ShowCursor = 25, - /// ?1000 - ReportMouseClicks = 1000, - /// ?1002 - ReportCellMouseMotion = 1002, - /// ?1003 - ReportAllMouseMotion = 1003, - /// ?1004 - ReportFocusInOut = 1004, - /// ?1005 - Utf8Mouse = 1005, - /// ?1006 - SgrMouse = 1006, - /// ?1007 - AlternateScroll = 1007, - /// ?1042 - UrgencyHints = 1042, - /// ?1049 - SwapScreenAndSetRestoreCursor = 1049, - /// ?2004 - BracketedPaste = 2004, -} - -impl Mode { - /// Create mode from a primitive. - pub fn from_primitive(intermediate: Option<&u8>, num: u16) -> Option<Mode> { - let private = match intermediate { - Some(b'?') => true, - None => false, - _ => return None, - }; - - if private { - Some(match num { - 1 => Mode::CursorKeys, - 3 => Mode::ColumnMode, - 6 => Mode::Origin, - 7 => Mode::LineWrap, - 12 => Mode::BlinkingCursor, - 25 => Mode::ShowCursor, - 1000 => Mode::ReportMouseClicks, - 1002 => Mode::ReportCellMouseMotion, - 1003 => Mode::ReportAllMouseMotion, - 1004 => Mode::ReportFocusInOut, - 1005 => Mode::Utf8Mouse, - 1006 => Mode::SgrMouse, - 1007 => Mode::AlternateScroll, - 1042 => Mode::UrgencyHints, - 1049 => Mode::SwapScreenAndSetRestoreCursor, - 2004 => Mode::BracketedPaste, - _ => { - trace!("[unimplemented] primitive mode: {}", num); - return None; - }, - }) - } else { - Some(match num { - 4 => Mode::Insert, - 20 => Mode::LineFeedNewLine, - _ => return None, - }) - } - } -} - -/// Mode for clearing line. -/// -/// Relative to cursor. -#[derive(Debug)] -pub enum LineClearMode { - /// Clear right of cursor. - Right, - /// Clear left of cursor. - Left, - /// Clear entire line. - All, -} - -/// Mode for clearing terminal. -/// -/// Relative to cursor. -#[derive(Debug)] -pub enum ClearMode { - /// Clear below cursor. - Below, - /// Clear above cursor. - Above, - /// Clear entire terminal. - All, - /// Clear 'saved' lines (scrollback). - Saved, -} - -/// Mode for clearing tab stops. -#[derive(Debug)] -pub enum TabulationClearMode { - /// Clear stop under cursor. - Current, - /// Clear all stops. - All, -} - -/// Standard colors. -/// -/// The order here matters since the enum should be castable to a `usize` for -/// indexing a color list. -#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] -pub enum NamedColor { - /// Black. - Black = 0, - /// Red. - Red, - /// Green. - Green, - /// Yellow. - Yellow, - /// Blue. - Blue, - /// Magenta. - Magenta, - /// Cyan. - Cyan, - /// White. - White, - /// Bright black. - BrightBlack, - /// Bright red. - BrightRed, - /// Bright green. - BrightGreen, - /// Bright yellow. - BrightYellow, - /// Bright blue. - BrightBlue, - /// Bright magenta. - BrightMagenta, - /// Bright cyan. - BrightCyan, - /// Bright white. - BrightWhite, - /// The foreground color. - Foreground = 256, - /// The background color. - Background, - /// Color for the cursor itself. - Cursor, - /// Dim black. - DimBlack, - /// Dim red. - DimRed, - /// Dim green. - DimGreen, - /// Dim yellow. - DimYellow, - /// Dim blue. - DimBlue, - /// Dim magenta. - DimMagenta, - /// Dim cyan. - DimCyan, - /// Dim white. - DimWhite, - /// The bright foreground color. - BrightForeground, - /// Dim foreground. - DimForeground, -} - -impl NamedColor { - #[must_use] - pub fn to_bright(self) -> Self { - match self { - NamedColor::Foreground => NamedColor::BrightForeground, - NamedColor::Black => NamedColor::BrightBlack, - NamedColor::Red => NamedColor::BrightRed, - NamedColor::Green => NamedColor::BrightGreen, - NamedColor::Yellow => NamedColor::BrightYellow, - NamedColor::Blue => NamedColor::BrightBlue, - NamedColor::Magenta => NamedColor::BrightMagenta, - NamedColor::Cyan => NamedColor::BrightCyan, - NamedColor::White => NamedColor::BrightWhite, - NamedColor::DimForeground => NamedColor::Foreground, - NamedColor::DimBlack => NamedColor::Black, - NamedColor::DimRed => NamedColor::Red, - NamedColor::DimGreen => NamedColor::Green, - NamedColor::DimYellow => NamedColor::Yellow, - NamedColor::DimBlue => NamedColor::Blue, - NamedColor::DimMagenta => NamedColor::Magenta, - NamedColor::DimCyan => NamedColor::Cyan, - NamedColor::DimWhite => NamedColor::White, - val => val, - } - } - - #[must_use] - pub fn to_dim(self) -> Self { - match self { - NamedColor::Black => NamedColor::DimBlack, - NamedColor::Red => NamedColor::DimRed, - NamedColor::Green => NamedColor::DimGreen, - NamedColor::Yellow => NamedColor::DimYellow, - NamedColor::Blue => NamedColor::DimBlue, - NamedColor::Magenta => NamedColor::DimMagenta, - NamedColor::Cyan => NamedColor::DimCyan, - NamedColor::White => NamedColor::DimWhite, - NamedColor::Foreground => NamedColor::DimForeground, - NamedColor::BrightBlack => NamedColor::Black, - NamedColor::BrightRed => NamedColor::Red, - NamedColor::BrightGreen => NamedColor::Green, - NamedColor::BrightYellow => NamedColor::Yellow, - NamedColor::BrightBlue => NamedColor::Blue, - NamedColor::BrightMagenta => NamedColor::Magenta, - NamedColor::BrightCyan => NamedColor::Cyan, - NamedColor::BrightWhite => NamedColor::White, - NamedColor::BrightForeground => NamedColor::Foreground, - val => val, - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] -pub enum Color { - Named(NamedColor), - Spec(Rgb), - Indexed(u8), -} - -/// Terminal character attributes. -#[derive(Debug, Eq, PartialEq)] -pub enum Attr { - /// Clear all special abilities. - Reset, - /// Bold text. - Bold, - /// Dim or secondary color. - Dim, - /// Italic text. - Italic, - /// Underline text. - Underline, - /// Underlined twice. - DoubleUnderline, - /// Undercurled text. - Undercurl, - /// Dotted underlined text. - DottedUnderline, - /// Dashed underlined text. - DashedUnderline, - /// Blink cursor slowly. - BlinkSlow, - /// Blink cursor fast. - BlinkFast, - /// Invert colors. - Reverse, - /// Do not display characters. - Hidden, - /// Strikeout text. - Strike, - /// Cancel bold. - CancelBold, - /// Cancel bold and dim. - CancelBoldDim, - /// Cancel italic. - CancelItalic, - /// Cancel all underlines. - CancelUnderline, - /// Cancel blink. - CancelBlink, - /// Cancel inversion. - CancelReverse, - /// Cancel text hiding. - CancelHidden, - /// Cancel strikeout. - CancelStrike, - /// Set indexed foreground color. - Foreground(Color), - /// Set indexed background color. - Background(Color), - /// Underline color. - UnderlineColor(Option<Color>), -} - -/// Identifiers which can be assigned to a graphic character set. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CharsetIndex { - /// Default set, is designated as ASCII at startup. - G0, - G1, - G2, - G3, -} - -impl Default for CharsetIndex { - fn default() -> Self { - CharsetIndex::G0 - } -} - -/// Standard or common character sets which can be designated as G0-G3. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum StandardCharset { - Ascii, - SpecialCharacterAndLineDrawing, -} - -impl Default for StandardCharset { - fn default() -> Self { - StandardCharset::Ascii - } -} - -impl StandardCharset { - /// Switch/Map character to the active charset. Ascii is the common case and - /// for that we want to do as little as possible. - #[inline] - pub fn map(self, c: char) -> char { - match self { - StandardCharset::Ascii => c, - StandardCharset::SpecialCharacterAndLineDrawing => match c { - '_' => ' ', - '`' => '◆', - 'a' => '▒', - 'b' => '\u{2409}', // Symbol for horizontal tabulation - 'c' => '\u{240c}', // Symbol for form feed - 'd' => '\u{240d}', // Symbol for carriage return - 'e' => '\u{240a}', // Symbol for line feed - 'f' => '°', - 'g' => '±', - 'h' => '\u{2424}', // Symbol for newline - 'i' => '\u{240b}', // Symbol for vertical tabulation - 'j' => '┘', - 'k' => '┐', - 'l' => '┌', - 'm' => '└', - 'n' => '┼', - 'o' => '⎺', - 'p' => '⎻', - 'q' => '─', - 'r' => '⎼', - 's' => '⎽', - 't' => '├', - 'u' => '┤', - 'v' => '┴', - 'w' => '┬', - 'x' => '│', - 'y' => '≤', - 'z' => '≥', - '{' => 'π', - '|' => '≠', - '}' => '£', - '~' => '·', - _ => c, - }, - } - } -} - -impl<'a, H> vte::Perform for Performer<'a, H> -where - H: Handler + 'a, -{ - #[inline] - fn print(&mut self, c: char) { - self.handler.input(c); - self.state.preceding_char = Some(c); - } - - #[inline] - fn execute(&mut self, byte: u8) { - match byte { - C0::HT => self.handler.put_tab(1), - C0::BS => self.handler.backspace(), - C0::CR => self.handler.carriage_return(), - C0::LF | C0::VT | C0::FF => self.handler.linefeed(), - C0::BEL => self.handler.bell(), - C0::SUB => self.handler.substitute(), - C0::SI => self.handler.set_active_charset(CharsetIndex::G0), - C0::SO => self.handler.set_active_charset(CharsetIndex::G1), - _ => debug!("[unhandled] execute byte={:02x}", byte), - } - } - - #[inline] - fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) { - match (action, intermediates) { - ('s', [b'=']) => { - // Start a synchronized update. The end is handled with a separate parser. - if params.iter().next().map_or(false, |param| param[0] == 1) { - self.state.dcs = Some(Dcs::SyncStart); - } - }, - _ => debug!( - "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}", - params, intermediates, ignore, action - ), - } - } - - #[inline] - fn put(&mut self, byte: u8) { - debug!("[unhandled put] byte={:?}", byte); - } - - #[inline] - fn unhook(&mut self) { - match self.state.dcs { - Some(Dcs::SyncStart) => { - self.state.sync_state.timeout = Some(Instant::now() + SYNC_UPDATE_TIMEOUT); - }, - Some(Dcs::SyncEnd) => (), - _ => debug!("[unhandled unhook]"), - } - } - - #[inline] - fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { - let terminator = if bell_terminated { "\x07" } else { "\x1b\\" }; - - fn unhandled(params: &[&[u8]]) { - let mut buf = String::new(); - for items in params { - buf.push('['); - for item in *items { - let _ = write!(buf, "{:?}", *item as char); - } - buf.push_str("],"); - } - debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!()); - } - - if params.is_empty() || params[0].is_empty() { - return; - } - - match params[0] { - // Set window title. - b"0" | b"2" => { - if params.len() >= 2 { - let title = params[1..] - .iter() - .flat_map(|x| str::from_utf8(x)) - .collect::<Vec<&str>>() - .join(";") - .trim() - .to_owned(); - self.handler.set_title(Some(title)); - return; - } - unhandled(params); - }, - - // Set color index. - b"4" => { - if params.len() <= 1 || params.len() % 2 == 0 { - unhandled(params); - return; - } - - for chunk in params[1..].chunks(2) { - let index = match parse_number(chunk[0]) { - Some(index) => index, - None => { - unhandled(params); - continue; - }, - }; - - if let Some(c) = xparse_color(chunk[1]) { - self.handler.set_color(index as usize, c); - } else if chunk[1] == b"?" { - let prefix = format!("4;{index}"); - self.handler.dynamic_color_sequence(prefix, index as usize, terminator); - } else { - unhandled(params); - } - } - }, - - // Hyperlink. - b"8" if params.len() > 2 => { - let link_params = params[1]; - - // NOTE: The escape sequence is of form 'OSC 8 ; params ; URI ST', where - // URI is URL-encoded. However `;` is a special character and might be - // passed as is, thus we need to rebuild the URI. - let mut uri = str::from_utf8(params[2]).unwrap_or_default().to_string(); - for param in params[3..].iter() { - uri.push(';'); - uri.push_str(str::from_utf8(param).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 { - if let Some(mut dynamic_code) = parse_number(params[0]) { - for param in ¶ms[1..] { - // 10 is the first dynamic color, also the foreground. - let offset = dynamic_code as usize - 10; - let index = NamedColor::Foreground as usize + offset; - - // End of setting dynamic colors. - if index > NamedColor::Cursor as usize { - unhandled(params); - break; - } - - if let Some(color) = xparse_color(param) { - self.handler.set_color(index, color); - } else if param == b"?" { - self.handler.dynamic_color_sequence( - dynamic_code.to_string(), - index, - terminator, - ); - } else { - unhandled(params); - } - dynamic_code += 1; - } - return; - } - } - unhandled(params); - }, - - // Set cursor style. - b"50" => { - if params.len() >= 2 - && params[1].len() >= 13 - && params[1][0..12] == *b"CursorShape=" - { - let shape = match params[1][12] as char { - '0' => CursorShape::Block, - '1' => CursorShape::Beam, - '2' => CursorShape::Underline, - _ => return unhandled(params), - }; - self.handler.set_cursor_shape(shape); - return; - } - unhandled(params); - }, - - // Set clipboard. - b"52" => { - if params.len() < 3 { - return unhandled(params); - } - - let clipboard = params[1].first().unwrap_or(&b'c'); - match params[2] { - b"?" => self.handler.clipboard_load(*clipboard, terminator), - base64 => self.handler.clipboard_store(*clipboard, base64), - } - }, - - // Reset color index. - b"104" => { - // Reset all color indexes when no parameters are given. - if params.len() == 1 || params[1].is_empty() { - for i in 0..256 { - self.handler.reset_color(i); - } - return; - } - - // Reset color indexes given as parameters. - for param in ¶ms[1..] { - match parse_number(param) { - Some(index) => self.handler.reset_color(index as usize), - None => unhandled(params), - } - } - }, - - // Reset foreground color. - b"110" => self.handler.reset_color(NamedColor::Foreground as usize), - - // Reset background color. - b"111" => self.handler.reset_color(NamedColor::Background as usize), - - // Reset text cursor color. - b"112" => self.handler.reset_color(NamedColor::Cursor as usize), - - _ => unhandled(params), - } - } - - #[allow(clippy::cognitive_complexity)] - #[inline] - fn csi_dispatch( +impl alacritty_config::SerdeReplace for CursorShapeShim { + fn replace( &mut self, - params: &Params, - intermediates: &[u8], - has_ignored_intermediates: bool, - action: char, - ) { - macro_rules! unhandled { - () => {{ - debug!( - "[Unhandled CSI] action={:?}, params={:?}, intermediates={:?}", - action, params, intermediates - ); - }}; - } - - if has_ignored_intermediates || intermediates.len() > 1 { - unhandled!(); - return; - } - - let mut params_iter = params.iter(); - let handler = &mut self.handler; - - let mut next_param_or = |default: u16| match params_iter.next() { - Some(&[param, ..]) if param != 0 => param, - _ => default, - }; - - match (action, intermediates) { - ('@', []) => handler.insert_blank(next_param_or(1) as usize), - ('A', []) => handler.move_up(next_param_or(1) as usize), - ('B', []) | ('e', []) => handler.move_down(next_param_or(1) as usize), - ('b', []) => { - if let Some(c) = self.state.preceding_char { - for _ in 0..next_param_or(1) { - handler.input(c); - } - } else { - debug!("tried to repeat with no preceding char"); - } - }, - ('C', []) | ('a', []) => handler.move_forward(Column(next_param_or(1) as usize)), - ('c', intermediates) if next_param_or(0) == 0 => { - handler.identify_terminal(intermediates.first().map(|&i| i as char)) - }, - ('D', []) => handler.move_backward(Column(next_param_or(1) as usize)), - ('d', []) => handler.goto_line(Line(next_param_or(1) as i32 - 1)), - ('E', []) => handler.move_down_and_cr(next_param_or(1) as usize), - ('F', []) => handler.move_up_and_cr(next_param_or(1) as usize), - ('G', []) | ('`', []) => handler.goto_col(Column(next_param_or(1) as usize - 1)), - ('g', []) => { - let mode = match next_param_or(0) { - 0 => TabulationClearMode::Current, - 3 => TabulationClearMode::All, - _ => { - unhandled!(); - return; - }, - }; - - handler.clear_tabs(mode); - }, - ('H', []) | ('f', []) => { - let y = next_param_or(1) as i32; - let x = next_param_or(1) as usize; - handler.goto(Line(y - 1), Column(x - 1)); - }, - ('h', intermediates) => { - for param in params_iter.map(|param| param[0]) { - match Mode::from_primitive(intermediates.first(), param) { - Some(mode) => handler.set_mode(mode), - None => unhandled!(), - } - } - }, - ('I', []) => handler.move_forward_tabs(next_param_or(1)), - ('J', []) => { - let mode = match next_param_or(0) { - 0 => ClearMode::Below, - 1 => ClearMode::Above, - 2 => ClearMode::All, - 3 => ClearMode::Saved, - _ => { - unhandled!(); - return; - }, - }; - - handler.clear_screen(mode); - }, - ('K', []) => { - let mode = match next_param_or(0) { - 0 => LineClearMode::Right, - 1 => LineClearMode::Left, - 2 => LineClearMode::All, - _ => { - unhandled!(); - return; - }, - }; - - handler.clear_line(mode); - }, - ('L', []) => handler.insert_blank_lines(next_param_or(1) as usize), - ('l', intermediates) => { - for param in params_iter.map(|param| param[0]) { - match Mode::from_primitive(intermediates.first(), param) { - Some(mode) => handler.unset_mode(mode), - None => unhandled!(), - } - } - }, - ('M', []) => handler.delete_lines(next_param_or(1) as usize), - ('m', []) => { - if params.is_empty() { - handler.terminal_attribute(Attr::Reset); - } else { - for attr in attrs_from_sgr_parameters(&mut params_iter) { - match attr { - Some(attr) => handler.terminal_attribute(attr), - None => unhandled!(), - } - } - } - }, - ('n', []) => handler.device_status(next_param_or(0) as usize), - ('P', []) => handler.delete_chars(next_param_or(1) as usize), - ('q', [b' ']) => { - // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. - let cursor_style_id = next_param_or(0); - let shape = match cursor_style_id { - 0 => None, - 1 | 2 => Some(CursorShape::Block), - 3 | 4 => Some(CursorShape::Underline), - 5 | 6 => Some(CursorShape::Beam), - _ => { - unhandled!(); - return; - }, - }; - let cursor_style = - shape.map(|shape| CursorStyle { shape, blinking: cursor_style_id % 2 == 1 }); - - handler.set_cursor_style(cursor_style); - }, - ('r', []) => { - let top = next_param_or(1) as usize; - let bottom = - params_iter.next().map(|param| param[0] as usize).filter(|¶m| param != 0); - - handler.set_scrolling_region(top, bottom); - }, - ('S', []) => handler.scroll_up(next_param_or(1) as usize), - ('s', []) => handler.save_cursor_position(), - ('T', []) => handler.scroll_down(next_param_or(1) as usize), - ('t', []) => match next_param_or(1) as usize { - 14 => handler.text_area_size_pixels(), - 18 => handler.text_area_size_chars(), - 22 => handler.push_title(), - 23 => handler.pop_title(), - _ => unhandled!(), - }, - ('u', []) => handler.restore_cursor_position(), - ('X', []) => handler.erase_chars(Column(next_param_or(1) as usize)), - ('Z', []) => handler.move_backward_tabs(next_param_or(1)), - _ => unhandled!(), - } - } - - #[inline] - fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) { - macro_rules! unhandled { - () => {{ - debug!( - "[unhandled] esc_dispatch ints={:?}, byte={:?} ({:02x})", - intermediates, byte as char, byte - ); - }}; - } - - macro_rules! configure_charset { - ($charset:path, $intermediates:expr) => {{ - let index: CharsetIndex = match $intermediates { - [b'('] => CharsetIndex::G0, - [b')'] => CharsetIndex::G1, - [b'*'] => CharsetIndex::G2, - [b'+'] => CharsetIndex::G3, - _ => { - unhandled!(); - return; - }, - }; - self.handler.configure_charset(index, $charset) - }}; - } - - match (byte, intermediates) { - (b'B', intermediates) => configure_charset!(StandardCharset::Ascii, intermediates), - (b'D', []) => self.handler.linefeed(), - (b'E', []) => { - self.handler.linefeed(); - self.handler.carriage_return(); - }, - (b'H', []) => self.handler.set_horizontal_tabstop(), - (b'M', []) => self.handler.reverse_index(), - (b'Z', []) => self.handler.identify_terminal(None), - (b'c', []) => self.handler.reset_state(), - (b'0', intermediates) => { - configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing, intermediates) - }, - (b'7', []) => self.handler.save_cursor_position(), - (b'8', [b'#']) => self.handler.decaln(), - (b'8', []) => self.handler.restore_cursor_position(), - (b'=', []) => self.handler.set_keypad_application_mode(), - (b'>', []) => self.handler.unset_keypad_application_mode(), - // String terminator, do nothing (parser handles as string terminator). - (b'\\', []) => (), - _ => unhandled!(), - } - } -} - -#[inline] -fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> { - let mut attrs = Vec::with_capacity(params.size_hint().0); - - while let Some(param) = params.next() { - let attr = match param { - [0] => Some(Attr::Reset), - [1] => Some(Attr::Bold), - [2] => Some(Attr::Dim), - [3] => Some(Attr::Italic), - [4, 0] => Some(Attr::CancelUnderline), - [4, 2] => Some(Attr::DoubleUnderline), - [4, 3] => Some(Attr::Undercurl), - [4, 4] => Some(Attr::DottedUnderline), - [4, 5] => Some(Attr::DashedUnderline), - [4, ..] => Some(Attr::Underline), - [5] => Some(Attr::BlinkSlow), - [6] => Some(Attr::BlinkFast), - [7] => Some(Attr::Reverse), - [8] => Some(Attr::Hidden), - [9] => Some(Attr::Strike), - [21] => Some(Attr::CancelBold), - [22] => Some(Attr::CancelBoldDim), - [23] => Some(Attr::CancelItalic), - [24] => Some(Attr::CancelUnderline), - [25] => Some(Attr::CancelBlink), - [27] => Some(Attr::CancelReverse), - [28] => Some(Attr::CancelHidden), - [29] => Some(Attr::CancelStrike), - [30] => Some(Attr::Foreground(Color::Named(NamedColor::Black))), - [31] => Some(Attr::Foreground(Color::Named(NamedColor::Red))), - [32] => Some(Attr::Foreground(Color::Named(NamedColor::Green))), - [33] => Some(Attr::Foreground(Color::Named(NamedColor::Yellow))), - [34] => Some(Attr::Foreground(Color::Named(NamedColor::Blue))), - [35] => Some(Attr::Foreground(Color::Named(NamedColor::Magenta))), - [36] => Some(Attr::Foreground(Color::Named(NamedColor::Cyan))), - [37] => Some(Attr::Foreground(Color::Named(NamedColor::White))), - [38] => { - let mut iter = params.map(|param| param[0]); - parse_sgr_color(&mut iter).map(Attr::Foreground) - }, - [38, params @ ..] => handle_colon_rgb(params).map(Attr::Foreground), - [39] => Some(Attr::Foreground(Color::Named(NamedColor::Foreground))), - [40] => Some(Attr::Background(Color::Named(NamedColor::Black))), - [41] => Some(Attr::Background(Color::Named(NamedColor::Red))), - [42] => Some(Attr::Background(Color::Named(NamedColor::Green))), - [43] => Some(Attr::Background(Color::Named(NamedColor::Yellow))), - [44] => Some(Attr::Background(Color::Named(NamedColor::Blue))), - [45] => Some(Attr::Background(Color::Named(NamedColor::Magenta))), - [46] => Some(Attr::Background(Color::Named(NamedColor::Cyan))), - [47] => Some(Attr::Background(Color::Named(NamedColor::White))), - [48] => { - let mut iter = params.map(|param| param[0]); - parse_sgr_color(&mut iter).map(Attr::Background) - }, - [48, params @ ..] => handle_colon_rgb(params).map(Attr::Background), - [49] => Some(Attr::Background(Color::Named(NamedColor::Background))), - [58] => { - let mut iter = params.map(|param| param[0]); - parse_sgr_color(&mut iter).map(|color| Attr::UnderlineColor(Some(color))) - }, - [58, params @ ..] => { - handle_colon_rgb(params).map(|color| Attr::UnderlineColor(Some(color))) - }, - [59] => Some(Attr::UnderlineColor(None)), - [90] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))), - [91] => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))), - [92] => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))), - [93] => Some(Attr::Foreground(Color::Named(NamedColor::BrightYellow))), - [94] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlue))), - [95] => Some(Attr::Foreground(Color::Named(NamedColor::BrightMagenta))), - [96] => Some(Attr::Foreground(Color::Named(NamedColor::BrightCyan))), - [97] => Some(Attr::Foreground(Color::Named(NamedColor::BrightWhite))), - [100] => Some(Attr::Background(Color::Named(NamedColor::BrightBlack))), - [101] => Some(Attr::Background(Color::Named(NamedColor::BrightRed))), - [102] => Some(Attr::Background(Color::Named(NamedColor::BrightGreen))), - [103] => Some(Attr::Background(Color::Named(NamedColor::BrightYellow))), - [104] => Some(Attr::Background(Color::Named(NamedColor::BrightBlue))), - [105] => Some(Attr::Background(Color::Named(NamedColor::BrightMagenta))), - [106] => Some(Attr::Background(Color::Named(NamedColor::BrightCyan))), - [107] => Some(Attr::Background(Color::Named(NamedColor::BrightWhite))), - _ => None, - }; - attrs.push(attr); - } - - attrs -} - -/// Handle colon separated rgb color escape sequence. -#[inline] -fn handle_colon_rgb(params: &[u16]) -> Option<Color> { - let rgb_start = if params.len() > 4 { 2 } else { 1 }; - let rgb_iter = params[rgb_start..].iter().copied(); - let mut iter = iter::once(params[0]).chain(rgb_iter); - - parse_sgr_color(&mut iter) -} - -/// Parse a color specifier from list of attributes. -fn parse_sgr_color(params: &mut dyn Iterator<Item = u16>) -> Option<Color> { - match params.next() { - Some(2) => Some(Color::Spec(Rgb { - r: u8::try_from(params.next()?).ok()?, - g: u8::try_from(params.next()?).ok()?, - b: u8::try_from(params.next()?).ok()?, - })), - Some(5) => Some(Color::Indexed(u8::try_from(params.next()?).ok()?)), - _ => None, - } -} - -/// C0 set of 7-bit control characters (from ANSI X3.4-1977). -#[allow(non_snake_case)] -pub mod C0 { - /// Null filler, terminal should ignore this character. - pub const NUL: u8 = 0x00; - /// Start of Header. - pub const SOH: u8 = 0x01; - /// Start of Text, implied end of header. - pub const STX: u8 = 0x02; - /// End of Text, causes some terminal to respond with ACK or NAK. - pub const ETX: u8 = 0x03; - /// End of Transmission. - pub const EOT: u8 = 0x04; - /// Enquiry, causes terminal to send ANSWER-BACK ID. - pub const ENQ: u8 = 0x05; - /// Acknowledge, usually sent by terminal in response to ETX. - pub const ACK: u8 = 0x06; - /// Bell, triggers the bell, buzzer, or beeper on the terminal. - pub const BEL: u8 = 0x07; - /// Backspace, can be used to define overstruck characters. - pub const BS: u8 = 0x08; - /// Horizontal Tabulation, move to next predetermined position. - pub const HT: u8 = 0x09; - /// Linefeed, move to same position on next line (see also NL). - pub const LF: u8 = 0x0A; - /// Vertical Tabulation, move to next predetermined line. - pub const VT: u8 = 0x0B; - /// Form Feed, move to next form or page. - pub const FF: u8 = 0x0C; - /// Carriage Return, move to first character of current line. - pub const CR: u8 = 0x0D; - /// Shift Out, switch to G1 (other half of character set). - pub const SO: u8 = 0x0E; - /// Shift In, switch to G0 (normal half of character set). - pub const SI: u8 = 0x0F; - /// Data Link Escape, interpret next control character specially. - pub const DLE: u8 = 0x10; - /// (DC1) Terminal is allowed to resume transmitting. - pub const XON: u8 = 0x11; - /// Device Control 2, causes ASR-33 to activate paper-tape reader. - pub const DC2: u8 = 0x12; - /// (DC2) Terminal must pause and refrain from transmitting. - pub const XOFF: u8 = 0x13; - /// Device Control 4, causes ASR-33 to deactivate paper-tape reader. - pub const DC4: u8 = 0x14; - /// Negative Acknowledge, used sometimes with ETX and ACK. - pub const NAK: u8 = 0x15; - /// Synchronous Idle, used to maintain timing in Sync communication. - pub const SYN: u8 = 0x16; - /// End of Transmission block. - pub const ETB: u8 = 0x17; - /// Cancel (makes VT100 abort current escape sequence if any). - pub const CAN: u8 = 0x18; - /// End of Medium. - pub const EM: u8 = 0x19; - /// Substitute (VT100 uses this to display parity errors). - pub const SUB: u8 = 0x1A; - /// Prefix to an escape sequence. - pub const ESC: u8 = 0x1B; - /// File Separator. - pub const FS: u8 = 0x1C; - /// Group Separator. - pub const GS: u8 = 0x1D; - /// Record Separator (sent by VT132 in block-transfer mode). - pub const RS: u8 = 0x1E; - /// Unit Separator. - pub const US: u8 = 0x1F; - /// Delete, should be ignored by terminal. - pub const DEL: u8 = 0x7f; -} - -// Tests for parsing escape sequences. -// -// Byte sequences used in these tests are recording of pty stdout. -#[cfg(test)] -mod tests { - use super::*; - - use crate::term::color::Rgb; - - struct MockHandler { - index: CharsetIndex, - charset: StandardCharset, - attr: Option<Attr>, - identity_reported: bool, - color: Option<Rgb>, - reset_colors: Vec<usize>, - } - - impl Handler for MockHandler { - fn terminal_attribute(&mut self, attr: Attr) { - self.attr = Some(attr); - } - - fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { - self.index = index; - self.charset = charset; - } - - fn set_active_charset(&mut self, index: CharsetIndex) { - self.index = index; - } - - fn identify_terminal(&mut self, _intermediate: Option<char>) { - self.identity_reported = true; - } - - fn reset_state(&mut self) { - *self = Self::default(); - } - - fn set_color(&mut self, _: usize, c: Rgb) { - self.color = Some(c); - } - - fn reset_color(&mut self, index: usize) { - self.reset_colors.push(index) - } - } - - impl Default for MockHandler { - fn default() -> MockHandler { - MockHandler { - index: CharsetIndex::G0, - charset: StandardCharset::Ascii, - attr: None, - identity_reported: false, - color: None, - reset_colors: Vec::new(), - } - } - } - - #[test] - fn parse_control_attribute() { - static BYTES: &[u8] = &[0x1b, b'[', b'1', b'm']; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in BYTES { - parser.advance(&mut handler, *byte); - } - - assert_eq!(handler.attr, Some(Attr::Bold)); - } - - #[test] - fn parse_terminal_identity_csi() { - let bytes: &[u8] = &[0x1b, b'[', b'1', b'c']; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert!(!handler.identity_reported); - handler.reset_state(); - - let bytes: &[u8] = &[0x1b, b'[', b'c']; - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert!(handler.identity_reported); - handler.reset_state(); - - let bytes: &[u8] = &[0x1b, b'[', b'0', b'c']; - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert!(handler.identity_reported); - } - - #[test] - fn parse_terminal_identity_esc() { - let bytes: &[u8] = &[0x1b, b'Z']; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert!(handler.identity_reported); - handler.reset_state(); - - let bytes: &[u8] = &[0x1b, b'#', b'Z']; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert!(!handler.identity_reported); - handler.reset_state(); - } - - #[test] - fn parse_truecolor_attr() { - static BYTES: &[u8] = &[ - 0x1b, b'[', b'3', b'8', b';', b'2', b';', b'1', b'2', b'8', b';', b'6', b'6', b';', - b'2', b'5', b'5', b'm', - ]; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in BYTES { - parser.advance(&mut handler, *byte); - } - - let spec = Rgb { r: 128, g: 66, b: 255 }; - - assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec)))); - } - - /// No exactly a test; useful for debugging. - #[test] - fn parse_zsh_startup() { - static BYTES: &[u8] = &[ - 0x1b, b'[', b'1', b'm', 0x1b, b'[', b'7', b'm', b'%', 0x1b, b'[', b'2', b'7', b'm', - 0x1b, b'[', b'1', b'm', 0x1b, b'[', b'0', b'm', b' ', b' ', b' ', b' ', b' ', b' ', - b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', - b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', - b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', - b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', - b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', - b' ', b' ', b' ', b'\r', b' ', b'\r', b'\r', 0x1b, b'[', b'0', b'm', 0x1b, b'[', b'2', - b'7', b'm', 0x1b, b'[', b'2', b'4', b'm', 0x1b, b'[', b'J', b'j', b'w', b'i', b'l', - b'm', b'@', b'j', b'w', b'i', b'l', b'm', b'-', b'd', b'e', b's', b'k', b' ', 0x1b, - b'[', b'0', b'1', b';', b'3', b'2', b'm', 0xe2, 0x9e, 0x9c, b' ', 0x1b, b'[', b'0', - b'1', b';', b'3', b'2', b'm', b' ', 0x1b, b'[', b'3', b'6', b'm', b'~', b'/', b'c', - b'o', b'd', b'e', - ]; - - let mut handler = MockHandler::default(); - let mut parser = Processor::new(); - - for byte in BYTES { - parser.advance(&mut handler, *byte); - } - } - - #[test] - fn parse_designate_g0_as_line_drawing() { - static BYTES: &[u8] = &[0x1b, b'(', b'0']; - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in BYTES { - parser.advance(&mut handler, *byte); - } - - assert_eq!(handler.index, CharsetIndex::G0); - assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); - } - - #[test] - fn parse_designate_g1_as_line_drawing_and_invoke() { - static BYTES: &[u8] = &[0x1b, b')', b'0', 0x0e]; - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in &BYTES[..3] { - parser.advance(&mut handler, *byte); - } - - assert_eq!(handler.index, CharsetIndex::G1); - assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); - - let mut handler = MockHandler::default(); - parser.advance(&mut handler, BYTES[3]); - - assert_eq!(handler.index, CharsetIndex::G1); - } - - #[test] - fn parse_valid_rgb_colors() { - assert_eq!(xparse_color(b"rgb:f/e/d"), Some(Rgb { r: 0xff, g: 0xee, b: 0xdd })); - assert_eq!(xparse_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff })); - assert_eq!(xparse_color(b"rgb:f/ed1/cb23"), Some(Rgb { r: 0xff, g: 0xec, b: 0xca })); - assert_eq!(xparse_color(b"rgb:ffff/0/0"), Some(Rgb { r: 0xff, g: 0x0, b: 0x0 })); - } - - #[test] - fn parse_valid_legacy_rgb_colors() { - assert_eq!(xparse_color(b"#1af"), Some(Rgb { r: 0x10, g: 0xa0, b: 0xf0 })); - assert_eq!(xparse_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff })); - assert_eq!(xparse_color(b"#110aa0ff0"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff })); - assert_eq!(xparse_color(b"#1100aa00ff00"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff })); - } - - #[test] - fn parse_invalid_rgb_colors() { - assert_eq!(xparse_color(b"rgb:0//"), None); - assert_eq!(xparse_color(b"rgb://///"), None); - } - - #[test] - fn parse_invalid_legacy_rgb_colors() { - assert_eq!(xparse_color(b"#"), None); - assert_eq!(xparse_color(b"#f"), None); - } - - #[test] - fn parse_invalid_number() { - assert_eq!(parse_number(b"1abc"), None); - } - - #[test] - fn parse_valid_number() { - assert_eq!(parse_number(b"123"), Some(123)); - } - - #[test] - fn parse_number_too_large() { - assert_eq!(parse_number(b"321"), None); - } - - #[test] - fn parse_osc4_set_color() { - let bytes: &[u8] = b"\x1b]4;0;#fff\x1b\\"; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert_eq!(handler.color, Some(Rgb { r: 0xf0, g: 0xf0, b: 0xf0 })); - } - - #[test] - fn parse_osc104_reset_color() { - let bytes: &[u8] = b"\x1b]104;1;\x1b\\"; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - assert_eq!(handler.reset_colors, vec![1]); - } - - #[test] - fn parse_osc104_reset_all_colors() { - let bytes: &[u8] = b"\x1b]104;\x1b\\"; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); - } - - let expected: Vec<usize> = (0..256).collect(); - assert_eq!(handler.reset_colors, expected); - } - - #[test] - fn parse_osc104_reset_all_colors_no_semicolon() { - let bytes: &[u8] = b"\x1b]104\x1b\\"; - - let mut parser = Processor::new(); - let mut handler = MockHandler::default(); - - for byte in bytes { - parser.advance(&mut handler, *byte); + key: &str, + value: serde_yaml::Value, + ) -> Result<(), Box<dyn std::error::Error>> { + if !key.is_empty() { + return Err(format!("Fields \"{0}\" do not exist", key).into()); } - let expected: Vec<usize> = (0..256).collect(); - assert_eq!(handler.reset_colors, expected); + *self = serde::Deserialize::deserialize(value)?; + Ok(()) } } diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index f17c327f..2d921b6e 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -8,7 +8,7 @@ use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; mod scrolling; -use crate::ansi::{CursorShape, CursorStyle}; +use crate::ansi::{CursorShapeShim, CursorStyle}; pub use crate::config::scrolling::{Scrolling, MAX_SCROLLBACK_LINES}; @@ -129,10 +129,10 @@ impl Cursor { #[derive(SerdeReplace, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] #[serde(untagged)] pub enum ConfigCursorStyle { - Shape(CursorShape), + Shape(CursorShapeShim), WithBlinking { #[serde(default)] - shape: CursorShape, + shape: CursorShapeShim, #[serde(default)] blinking: CursorBlinking, }, @@ -140,7 +140,7 @@ pub enum ConfigCursorStyle { impl Default for ConfigCursorStyle { fn default() -> Self { - Self::Shape(CursorShape::default()) + Self::Shape(CursorShapeShim::default()) } } @@ -157,28 +157,23 @@ impl ConfigCursorStyle { impl From<ConfigCursorStyle> for CursorStyle { fn from(config_style: ConfigCursorStyle) -> Self { match config_style { - ConfigCursorStyle::Shape(shape) => Self { shape, blinking: false }, + ConfigCursorStyle::Shape(shape) => Self { shape: shape.into(), blinking: false }, ConfigCursorStyle::WithBlinking { shape, blinking } => { - Self { shape, blinking: blinking.into() } + Self { shape: shape.into(), blinking: blinking.into() } }, } } } -#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(ConfigDeserialize, Default, Debug, Copy, Clone, PartialEq, Eq)] pub enum CursorBlinking { Never, + #[default] Off, On, Always, } -impl Default for CursorBlinking { - fn default() -> Self { - CursorBlinking::Off - } -} - impl CursorBlinking { fn blinking_override(&self) -> Option<bool> { match self { diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index 34169801..61dc69bc 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -333,8 +333,9 @@ where 'event_loop: loop { // Wakeup the event loop when a synchronized update timeout was reached. - let sync_timeout = state.parser.sync_timeout(); - let timeout = sync_timeout.map(|st| st.saturating_duration_since(Instant::now())); + let handler = state.parser.sync_timeout(); + let timeout = + handler.sync_timeout().map(|st| st.saturating_duration_since(Instant::now())); if let Err(err) = self.poll.poll(&mut events, timeout) { match err.kind() { diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 5253ede1..ddf6a745 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use bitflags::bitflags; use serde::{Deserialize, Serialize}; +use vte::ansi::Hyperlink as VteHyperlink; use crate::ansi::{Color, NamedColor}; use crate::grid::{self, GridCell}; @@ -57,6 +58,18 @@ impl Hyperlink { } } +impl From<VteHyperlink> for Hyperlink { + fn from(value: VteHyperlink) -> Self { + Self::new(value.id, value.uri) + } +} + +impl From<Hyperlink> for VteHyperlink { + fn from(val: Hyperlink) -> Self { + VteHyperlink { id: Some(val.id().to_owned()), uri: val.uri().to_owned() } + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] struct HyperlinkInner { /// Identifier for the given hyperlink. diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index 8f193b39..b4bdba3a 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -1,89 +1,62 @@ use std::fmt::{self, Display, Formatter}; -use std::ops::{Add, Index, IndexMut, Mul}; +use std::ops::{Add, Deref, Index, IndexMut, Mul}; use std::str::FromStr; -use log::trace; use serde::de::{Error as _, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; use serde_yaml::Value; use alacritty_config_derive::SerdeReplace; +use vte::ansi::Rgb as VteRgb; + use crate::ansi::NamedColor; /// Number of terminal colors. pub const COUNT: usize = 269; #[derive(SerdeReplace, Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)] -pub struct Rgb { - pub r: u8, - pub g: u8, - pub b: u8, -} +pub struct Rgb(VteRgb); impl Rgb { - /// Implementation of W3C's luminance - /// [algorithm](https://www.w3.org/TR/WCAG20/#relativeluminancedef) - fn luminance(self) -> f64 { - let channel_luminance = |channel| { - let channel = channel as f64 / 255.; - if channel <= 0.03928 { - channel / 12.92 - } else { - f64::powf((channel + 0.055) / 1.055, 2.4) - } - }; - - let r_luminance = channel_luminance(self.r); - let g_luminance = channel_luminance(self.g); - let b_luminance = channel_luminance(self.b); + #[inline] + pub const fn new(r: u8, g: u8, b: u8) -> Self { + Self(VteRgb { r, g, b }) + } - 0.2126 * r_luminance + 0.7152 * g_luminance + 0.0722 * b_luminance + #[inline] + pub fn as_tuple(self) -> (u8, u8, u8) { + (self.0.r, self.0.g, self.0.b) } +} - /// Implementation of [W3C's contrast algorithm]. - /// - /// [W3C's contrast algorithm]: https://www.w3.org/TR/WCAG20/#contrast-ratiodef - pub fn contrast(self, other: Rgb) -> f64 { - let self_luminance = self.luminance(); - let other_luminance = other.luminance(); +impl From<VteRgb> for Rgb { + fn from(value: VteRgb) -> Self { + Self(value) + } +} - let (darker, lighter) = if self_luminance > other_luminance { - (other_luminance, self_luminance) - } else { - (self_luminance, other_luminance) - }; +impl Deref for Rgb { + type Target = VteRgb; - (lighter + 0.05) / (darker + 0.05) + fn deref(&self) -> &Self::Target { + &self.0 } } -// A multiply function for Rgb, as the default dim is just *2/3. impl Mul<f32> for Rgb { type Output = Rgb; - fn mul(self, rhs: f32) -> Rgb { - let result = Rgb { - r: (f32::from(self.r) * rhs).clamp(0.0, 255.0) as u8, - g: (f32::from(self.g) * rhs).clamp(0.0, 255.0) as u8, - b: (f32::from(self.b) * rhs).clamp(0.0, 255.0) as u8, - }; - - trace!("Scaling RGB by {} from {:?} to {:?}", rhs, self, result); - - result + fn mul(self, rhs: f32) -> Self::Output { + Rgb(self.0 * rhs) } } impl Add<Rgb> for Rgb { type Output = Rgb; - fn add(self, rhs: Rgb) -> Rgb { - Rgb { - r: self.r.saturating_add(rhs.r), - g: self.g.saturating_add(rhs.g), - b: self.b.saturating_add(rhs.b), - } + fn add(self, rhs: Rgb) -> Self::Output { + Rgb(self.0 + rhs.0) } } @@ -130,7 +103,7 @@ impl<'de> Deserialize<'de> for Rgb { // Attempt to deserialize from struct form. if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) { - return Ok(Rgb { r, g, b }); + return Ok(Rgb::new(r, g, b)); } // Deserialize from hex notation (either 0xff00ff or #ff00ff). @@ -163,7 +136,7 @@ impl FromStr for Rgb { let g = (color & 0xff) as u8; color >>= 8; let r = color as u8; - Ok(Rgb { r, g, b }) + Ok(Rgb::new(r, g, b)) }, Err(_) => Err(()), } @@ -283,26 +256,3 @@ impl IndexMut<NamedColor> for Colors { &mut self.0[index as usize] } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn contrast() { - let rgb1 = Rgb { r: 0xff, g: 0xff, b: 0xff }; - let rgb2 = Rgb { r: 0x00, g: 0x00, b: 0x00 }; - assert!((rgb1.contrast(rgb2) - 21.).abs() < f64::EPSILON); - - let rgb1 = Rgb { r: 0xff, g: 0xff, b: 0xff }; - assert!((rgb1.contrast(rgb1) - 1.).abs() < f64::EPSILON); - - let rgb1 = Rgb { r: 0xff, g: 0x00, b: 0xff }; - let rgb2 = Rgb { r: 0x00, g: 0xff, b: 0x00 }; - assert!((rgb1.contrast(rgb2) - 2.285_543_608_124_253_3).abs() < f64::EPSILON); - - let rgb1 = Rgb { r: 0x12, g: 0x34, b: 0x56 }; - let rgb2 = Rgb { r: 0xfe, g: 0xdc, b: 0xba }; - assert!((rgb1.contrast(rgb2) - 9.786_558_997_257_74).abs() < f64::EPSILON); - } -} diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index bdfd6c9b..7c4abb57 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -7,6 +7,7 @@ use std::{cmp, mem, ptr, slice, str}; use bitflags::bitflags; use log::{debug, trace}; use unicode_width::UnicodeWidthChar; +use vte::ansi::{Hyperlink as VteHyperlink, Rgb as VteRgb}; use crate::ansi::{ self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, NamedColor, StandardCharset, @@ -16,8 +17,8 @@ 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, Hyperlink, LineLength}; -use crate::term::color::{Colors, Rgb}; +use crate::term::cell::{Cell, Flags, LineLength}; +use crate::term::color::Colors; use crate::vi_mode::{ViModeCursor, ViMotion}; pub mod cell; @@ -1069,7 +1070,10 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn goto(&mut self, line: Line, col: Column) { + fn goto(&mut self, line: i32, col: usize) { + let line = Line(line); + let col = Column(col); + trace!("Going to: line={}, col={}", line, col); let (y_offset, max_y) = if self.mode.contains(TermMode::ORIGIN) { (self.scroll_region.start, self.scroll_region.end - 1) @@ -1085,15 +1089,15 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn goto_line(&mut self, line: Line) { + fn goto_line(&mut self, line: i32) { trace!("Going to line: {}", line); - self.goto(line, self.grid.cursor.point.column) + self.goto(line, self.grid.cursor.point.column.0) } #[inline] - fn goto_col(&mut self, col: Column) { + fn goto_col(&mut self, col: usize) { trace!("Going to column: {}", col); - self.goto(self.grid.cursor.point.line, col) + self.goto(self.grid.cursor.point.line.0, col) } #[inline] @@ -1127,17 +1131,23 @@ impl<T: EventListener> Handler for Term<T> { #[inline] fn move_up(&mut self, lines: usize) { trace!("Moving up: {}", lines); - self.goto(self.grid.cursor.point.line - lines, self.grid.cursor.point.column) + + let line = self.grid.cursor.point.line - lines; + let column = self.grid.cursor.point.column; + self.goto(line.0, column.0) } #[inline] fn move_down(&mut self, lines: usize) { trace!("Moving down: {}", lines); - self.goto(self.grid.cursor.point.line + lines, self.grid.cursor.point.column) + + let line = self.grid.cursor.point.line + lines; + let column = self.grid.cursor.point.column; + self.goto(line.0, column.0) } #[inline] - fn move_forward(&mut self, cols: Column) { + fn move_forward(&mut self, cols: usize) { trace!("Moving forward: {}", cols); let last_column = cmp::min(self.grid.cursor.point.column + cols, self.last_column()); @@ -1149,9 +1159,9 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn move_backward(&mut self, cols: Column) { + fn move_backward(&mut self, cols: usize) { trace!("Moving backward: {}", cols); - let column = self.grid.cursor.point.column.saturating_sub(cols.0); + let column = self.grid.cursor.point.column.saturating_sub(cols); let cursor_line = self.grid.cursor.point.line.0 as usize; self.damage.damage_line(cursor_line, column, self.grid.cursor.point.column.0); @@ -1198,13 +1208,17 @@ impl<T: EventListener> Handler for Term<T> { #[inline] fn move_down_and_cr(&mut self, lines: usize) { trace!("Moving down and cr: {}", lines); - self.goto(self.grid.cursor.point.line + lines, Column(0)) + + let line = self.grid.cursor.point.line + lines; + self.goto(line.0, 0) } #[inline] fn move_up_and_cr(&mut self, lines: usize) { trace!("Moving up and cr: {}", lines); - self.goto(self.grid.cursor.point.line - lines, Column(0)) + + let line = self.grid.cursor.point.line - lines; + self.goto(line.0, 0) } /// Insert tab at cursor position. @@ -1362,7 +1376,7 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn erase_chars(&mut self, count: Column) { + fn erase_chars(&mut self, count: usize) { let cursor = &self.grid.cursor; trace!("Erasing chars: count={}, col={}", count, cursor.point.column); @@ -1479,9 +1493,11 @@ impl<T: EventListener> Handler for Term<T> { /// Set the indexed color value. #[inline] - fn set_color(&mut self, index: usize, color: Rgb) { + fn set_color(&mut self, index: usize, color: VteRgb) { trace!("Setting color[{}] = {:?}", index, color); + let color = color.into(); + // Damage terminal if the color changed and it's not the cursor. if index != NamedColor::Cursor as usize && self.colors[index] != Some(color) { self.mark_fully_damaged(); @@ -1679,9 +1695,9 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) { + fn set_hyperlink(&mut self, hyperlink: Option<VteHyperlink>) { trace!("Setting hyperlink: {:?}", hyperlink); - self.grid.cursor.template.set_hyperlink(hyperlink); + self.grid.cursor.template.set_hyperlink(hyperlink.map(|e| e.into())); } /// Set a terminal attribute. @@ -1858,7 +1874,7 @@ impl<T: EventListener> Handler for Term<T> { let screen_lines = Line(self.screen_lines() as i32); self.scroll_region.start = cmp::min(start, screen_lines); self.scroll_region.end = cmp::min(end, screen_lines); - self.goto(Line(0), Column(0)); + self.goto(0, 0); } #[inline] @@ -2756,7 +2772,7 @@ mod tests { // Reset terminal for partial damage tests since it's initialized as fully damaged. term.reset_damage(); - term.goto(Line(1), Column(1)); + term.goto(1, 1); // NOTE While we can use `[Term::damage]` to access terminal damage information, in the // following tests we will be accessing `term.damage.lines` directly to avoid adding extra @@ -2766,13 +2782,13 @@ mod tests { assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 1, right: 1 }); term.damage.reset(num_cols); - term.move_forward(Column(3)); + term.move_forward(3); assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 1, right: 4 }); term.damage.reset(num_cols); - term.move_backward(Column(8)); + term.move_backward(8); assert_eq!(term.damage.lines[1], LineDamageBounds { line: 1, left: 0, right: 4 }); - term.goto(Line(5), Column(5)); + term.goto(5, 5); term.damage.reset(num_cols); term.backspace(); @@ -2795,7 +2811,7 @@ mod tests { term.wrapline(); assert_eq!(term.damage.lines[6], LineDamageBounds { line: 6, left: 3, right: 3 }); assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 0 }); - term.move_forward(Column(3)); + term.move_forward(3); term.move_up(1); term.damage.reset(num_cols); @@ -2808,20 +2824,20 @@ mod tests { assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 3 }); term.damage.reset(num_cols); - term.erase_chars(Column(5)); + term.erase_chars(5); assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right: 5 }); term.damage.reset(num_cols); term.delete_chars(3); let right = term.columns() - 1; assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 0, right }); - term.move_forward(Column(term.columns())); + term.move_forward(term.columns()); term.damage.reset(num_cols); term.move_backward_tabs(1); assert_eq!(term.damage.lines[7], LineDamageBounds { line: 7, left: 8, right }); term.save_cursor_position(); - term.goto(Line(1), Column(1)); + term.goto(1, 1); term.damage.reset(num_cols); term.restore_cursor_position(); @@ -2896,12 +2912,12 @@ mod tests { term.reset_damage(); let color_index = 257; - term.set_color(color_index, Rgb::default()); + term.set_color(color_index, VteRgb::default()); assert!(term.damage.is_fully_damaged); term.reset_damage(); // Setting the same color once again shouldn't trigger full damage. - term.set_color(color_index, Rgb::default()); + term.set_color(color_index, VteRgb::default()); assert!(!term.damage.is_fully_damaged); term.reset_color(color_index); @@ -2909,7 +2925,7 @@ mod tests { term.reset_damage(); // We shouldn't trigger fully damage when cursor gets update. - term.set_color(NamedColor::Cursor as usize, Rgb::default()); + term.set_color(NamedColor::Cursor as usize, VteRgb::default()); assert!(!term.damage.is_fully_damaged); // However requesting terminal damage should mark terminal as fully damaged in `Insert` diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index 848a0ad1..48704fb8 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -109,7 +109,7 @@ fn ref_test(dir: &Path) { config.scrolling.set_history(ref_config.history_size); let mut terminal = Term::new(&config, &size, Mock); - let mut parser = ansi::Processor::new(); + let mut parser: ansi::Processor = ansi::Processor::new(); for byte in recording { parser.advance(&mut terminal, byte); |