diff options
author | Christian Duerr <contact@christianduerr.com> | 2025-01-09 06:27:15 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-09 06:27:15 +0000 |
commit | 7321a442a6fc0fc5b6d6ed7af364477d25e706fd (patch) | |
tree | 11ff2608e63a160b8b204b6f78ec3977f019d081 /src/ansi.rs | |
parent | 89c12df969145ffb5084d1122627d7292c2c638f (diff) | |
download | r-alacritty-vte-7321a442a6fc0fc5b6d6ed7af364477d25e706fd.tar.gz r-alacritty-vte-7321a442a6fc0fc5b6d6ed7af364477d25e706fd.tar.bz2 r-alacritty-vte-7321a442a6fc0fc5b6d6ed7af364477d25e706fd.zip |
Switch parser to multi-byte processing
This patch overhauls the `Parser::advance` API to operate on byte slices
instead of individual bytes, which allows for additional performance
optimizations.
VTE does not support C1 escapes and C0 escapes always start with an
escape character. This makes it possible to simplify processing if a
byte stream is determined to not contain any escapes. The `memchr` crate
provides a battle-tested implementation for SIMD-accelerated byte
searches, which is why this implementation makes use of it.
VTE also only supports UTF8 characters in the ground state, which means
that the new non-escape parsing path is able to rely completely on STD's
`str::from_utf8` since `memchr` gives us the full length of the plain
text character buffer. This allows us to completely remove `utf8parse`
and all related code.
We also make use of `memchr` in the synchronized escape handling in
`ansi.rs`, since it relies heavily on scanning large amounts of text for
the extension/termination escape sequences.
Diffstat (limited to 'src/ansi.rs')
-rw-r--r-- | src/ansi.rs | 460 |
1 files changed, 307 insertions, 153 deletions
diff --git a/src/ansi.rs b/src/ansi.rs index 8cac26d..fa5b1ed 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -11,21 +11,20 @@ extern crate alloc; use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use bitflags::bitflags; - use core::convert::TryFrom; use core::fmt::{self, Display, Formatter, Write}; +#[cfg(not(feature = "no_std"))] +use core::ops::Mul; use core::ops::{Add, Sub}; use core::str::FromStr; use core::time::Duration; -use core::{iter, str}; - -#[cfg(not(feature = "no_std"))] -use core::ops::Mul; - +use core::{iter, mem, str}; #[cfg(not(feature = "no_std"))] use std::time::Instant; +use bitflags::bitflags; +#[doc(inline)] +pub use cursor_icon; use cursor_icon::CursorIcon; use log::debug; #[cfg(feature = "serde")] @@ -33,9 +32,6 @@ use serde::{Deserialize, Serialize}; use crate::{Params, ParamsIter}; -#[doc(inline)] -pub use cursor_icon; - /// Maximum time before a synchronized update is aborted. const SYNC_UPDATE_TIMEOUT: Duration = Duration::from_millis(150); @@ -168,9 +164,9 @@ impl FromStr for Rgb { match u32::from_str_radix(chars, 16) { Ok(mut color) => { - let b = (color & 0xff) as u8; + let b = (color & 0xFF) as u8; color >>= 8; - let g = (color & 0xff) as u8; + let g = (color & 0xFF) as u8; color >>= 8; let r = color as u8; Ok(Rgb { r, g, b }) @@ -237,14 +233,8 @@ fn parse_number(input: &[u8]) -> Option<u8> { 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; - } + let digit = c.to_digit(10)?; + num = num.checked_mul(10).and_then(|v| v.checked_add(digit as u8))?; } Some(num) } @@ -270,11 +260,12 @@ struct SyncState<T: Timeout> { impl<T: Timeout> Default for SyncState<T> { fn default() -> Self { - Self { buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), timeout: T::default() } + Self { buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), timeout: Default::default() } } } -/// The processor wraps a `crate::Parser` to ultimately call methods on a Handler. +/// The processor wraps a `crate::Parser` to ultimately call methods on a +/// Handler. #[cfg(not(feature = "no_std"))] #[derive(Default)] pub struct Processor<T: Timeout = StdSyncHandler> { @@ -282,7 +273,8 @@ pub struct Processor<T: Timeout = StdSyncHandler> { parser: crate::Parser, } -/// The processor wraps a `crate::Parser` to ultimately call methods on a Handler. +/// The processor wraps a `crate::Parser` to ultimately call methods on a +/// Handler. #[cfg(feature = "no_std")] #[derive(Default)] pub struct Processor<T: Timeout> { @@ -303,15 +295,19 @@ impl<T: Timeout> Processor<T> { /// Process a new byte from the PTY. #[inline] - pub fn advance<H>(&mut self, handler: &mut H, byte: u8) + pub fn advance<H>(&mut self, handler: &mut H, bytes: &[u8]) where H: Handler, { - if self.state.sync_state.timeout.pending_timeout() { - self.advance_sync(handler, byte); - } else { - let mut performer = Performer::new(&mut self.state, handler); - self.parser.advance(&mut performer, byte); + let mut processed = 0; + while processed != bytes.len() { + if self.state.sync_state.timeout.pending_timeout() { + processed += self.advance_sync(handler, &bytes[processed..]); + } else { + let mut performer = Performer::new(&mut self.state, handler); + processed += + self.parser.advance_until_terminated(&mut performer, &bytes[processed..]); + } } } @@ -320,18 +316,45 @@ impl<T: Timeout> Processor<T> { where H: Handler, { + self.stop_sync_internal(handler, None); + } + + /// End a synchronized update. + /// + /// The `bsu_offset` parameter should be passed if the sync buffer contains + /// a new BSU escape that is not part of the current synchronized + /// update. + fn stop_sync_internal<H>(&mut self, handler: &mut H, bsu_offset: Option<usize>) + 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); + // + // NOTE: We do not use `advance_until_terminated` here since BSU sequences are + // processed automatically during the synchronized update. + let buffer = mem::take(&mut self.state.sync_state.buffer); + let offset = bsu_offset.unwrap_or(buffer.len()); + let mut performer = Performer::new(&mut self.state, handler); + self.parser.advance(&mut performer, &buffer[..offset]); + self.state.sync_state.buffer = buffer; + + match bsu_offset { + // Just clear processed bytes if there is a new BSU. + // + // NOTE: We do not need to re-process for a new ESU since the `advance_sync` + // function checks for BSUs in reverse. + Some(bsu_offset) => { + let new_len = self.state.sync_state.buffer.len() - bsu_offset; + self.state.sync_state.buffer.copy_within(bsu_offset.., 0); + self.state.sync_state.buffer.truncate(new_len); + }, + // Report mode and clear state if no new BSU is present. + None => { + handler.unset_private_mode(NamedPrivateMode::SyncUpdate.into()); + self.state.sync_state.timeout.clear_timeout(); + self.state.sync_state.buffer.clear(); + }, } - - // Report that update ended, since we could end due to timeout. - handler.unset_private_mode(NamedPrivateMode::SyncUpdate.into()); - // Resetting state after processing makes sure we don't interpret buffered sync escapes. - self.state.sync_state.buffer.clear(); - self.state.sync_state.timeout.clear_timeout(); } /// Number of bytes in the synchronization buffer. @@ -341,36 +364,56 @@ impl<T: Timeout> Processor<T> { } /// Process a new byte during a synchronized update. + /// + /// Returns the number of bytes processed. #[cold] - fn advance_sync<H>(&mut self, handler: &mut H, byte: u8) + fn advance_sync<H>(&mut self, handler: &mut H, bytes: &[u8]) -> usize where H: Handler, { - self.state.sync_state.buffer.push(byte); + // Advance sync parser or stop sync if we'd exceed the maximum buffer size. + if self.state.sync_state.buffer.len() + bytes.len() >= SYNC_BUFFER_SIZE - 1 { + // Terminate the synchronized update. + self.stop_sync_internal(handler, None); - // Handle sync CSI escape sequences. - self.advance_sync_csi(handler); + // Just parse the bytes normally. + let mut performer = Performer::new(&mut self.state, handler); + self.parser.advance_until_terminated(&mut performer, bytes) + } else { + self.state.sync_state.buffer.extend(bytes); + self.advance_sync_csi(handler, bytes.len()); + bytes.len() + } } /// Handle BSU/ESU CSI sequences during synchronized update. - fn advance_sync_csi<H>(&mut self, handler: &mut H) + fn advance_sync_csi<H>(&mut self, handler: &mut H, new_bytes: usize) where H: Handler, { - // Get the last few bytes for comparison. - let len = self.state.sync_state.buffer.len(); - let offset = len.saturating_sub(SYNC_ESCAPE_LEN); - let end = &self.state.sync_state.buffer[offset..]; + // Get constraints within which a new escape character might be relevant. + let buffer_len = self.state.sync_state.buffer.len(); + let start_offset = (buffer_len - new_bytes).saturating_sub(SYNC_ESCAPE_LEN - 1); + let end_offset = buffer_len.saturating_sub(SYNC_ESCAPE_LEN - 1); + let search_buffer = &self.state.sync_state.buffer[start_offset..end_offset]; + // Search for termination/extension escapes in the added bytes. + // // NOTE: It is technically legal to specify multiple private modes in the same // escape, but we only allow EXACTLY `\e[?2026h`/`\e[?2026l` to keep the parser - // reasonable. - // - // Check for extension/termination of the synchronized update. - if end == BSU_CSI { - self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); - } else if end == ESU_CSI || len >= SYNC_BUFFER_SIZE - 1 { - self.stop_sync(handler); + // more simple. + let mut bsu_offset = None; + for index in memchr::memchr_iter(0x1B, search_buffer).rev() { + let offset = start_offset + index; + let escape = &self.state.sync_state.buffer[offset..offset + SYNC_ESCAPE_LEN]; + + if escape == BSU_CSI { + self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); + bsu_offset = Some(offset); + } else if escape == ESU_CSI { + self.stop_sync_internal(handler, bsu_offset); + break; + } } } } @@ -382,13 +425,16 @@ impl<T: Timeout> Processor<T> { struct Performer<'a, H: Handler, T: Timeout> { state: &'a mut ProcessorState<T>, handler: &'a mut H, + + /// Whether the parser should be prematurely terminated. + terminated: bool, } impl<'a, H: Handler + 'a, T: Timeout> Performer<'a, H, T> { /// Create a performer. #[inline] pub fn new<'b>(state: &'b mut ProcessorState<T>, handler: &'b mut H) -> Performer<'b, H, T> { - Performer { state, handler } + Performer { state, handler, terminated: Default::default() } } } @@ -710,13 +756,14 @@ bitflags! { /// /// This only applies to keys corresponding to ascii characters. /// -/// For the details on how to implement the mode handling correctly, consult [`XTerm's -/// implementation`] and the [`output`] of XTerm's provided [`perl script`]. Some libraries and -/// implementations also use the [`fixterms`] definition of the `CSI u`. +/// For the details on how to implement the mode handling correctly, consult +/// [`XTerm's implementation`] and the [`output`] of XTerm's provided [`perl +/// script`]. Some libraries and implementations also use the [`fixterms`] +/// definition of the `CSI u`. /// -/// The end escape sequence has a `CSI char; modifiers u` form while the original -/// `CSI 27 ; modifier ; char ~`. The clients should prefer the `CSI u`, since it has -/// more adoption. +/// The end escape sequence has a `CSI char; modifiers u` form while the +/// original `CSI 27 ; modifier ; char ~`. The clients should prefer the `CSI +/// u`, since it has more adoption. /// /// [`XTerm's implementation`]: https://invisible-island.net/xterm/modified-keys.html /// [`perl script`]: https://github.com/ThomasDickey/xterm-snapshots/blob/master/vttests/modify-keys.pl @@ -727,12 +774,14 @@ bitflags! { pub enum ModifyOtherKeys { /// Reset the state. Reset, - /// Enables this feature except for keys with well-known behavior, e.g., Tab, Backspace and - /// some special control character cases which are built into the X11 library (e.g., - /// Control-Space to make a NUL, or Control-3 to make an Escape character). + /// Enables this feature except for keys with well-known behavior, e.g., + /// Tab, Backspace and some special control character cases which are + /// built into the X11 library (e.g., Control-Space to make a NUL, or + /// Control-3 to make an Escape character). /// /// Escape sequences shouldn't be emitted under the following circumstances: - /// - When the key is in range of `[64;127]` and the modifier is either Control or Shift + /// - When the key is in range of `[64;127]` and the modifier is either + /// Control or Shift /// - When the key combination is a known control combination alias /// /// For more details, consult the [`example`] for the suggested translation. @@ -740,9 +789,10 @@ pub enum ModifyOtherKeys { /// [`example`]: https://github.com/alacritty/vte/blob/master/doc/modifyOtherKeys-example.txt EnableExceptWellDefined, /// Enables this feature for all keys including the exceptions of - /// [`Self::EnableExceptWellDefined`]. XTerm still ignores the special cases built into the - /// X11 library. Any shifted (modified) ordinary key send an escape sequence. The Alt- and - /// Meta- modifiers cause XTerm to send escape sequences. + /// [`Self::EnableExceptWellDefined`]. XTerm still ignores the special + /// cases built into the X11 library. Any shifted (modified) ordinary + /// key send an escape sequence. The Alt- and Meta- modifiers cause + /// XTerm to send escape sequences. /// /// For more details, consult the [`example`] for the suggested translation. /// @@ -1203,16 +1253,20 @@ impl StandardCharset { pub enum ScpCharPath { /// SCP's first parameter value of 0. Behavior is implementation defined. Default, - /// SCP's first parameter value of 1 which sets character path to LEFT-TO-RIGHT. + /// SCP's first parameter value of 1 which sets character path to + /// LEFT-TO-RIGHT. LTR, - /// SCP's first parameter value of 2 which sets character path to RIGHT-TO-LEFT. + /// SCP's first parameter value of 2 which sets character path to + /// RIGHT-TO-LEFT. RTL, } -/// SCP control's second parameter which determines update mode/direction between components. +/// SCP control's second parameter which determines update mode/direction +/// between components. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ScpUpdateMode { - /// SCP's second parameter value of 0 (the default). Implementation dependant update. + /// SCP's second parameter value of 0 (the default). Implementation + /// dependant update. ImplementationDependant, /// SCP's second parameter value of 1. /// @@ -1351,8 +1405,8 @@ where return; } - // Link parameters are in format of `key1=value1:key2=value2`. Currently only key - // `id` is defined. + // 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=")) @@ -1547,6 +1601,7 @@ where // Handle sync updates opaquely. if param == NamedPrivateMode::SyncUpdate as u16 { self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); + self.terminated = true; } handler.set_private_mode(PrivateMode::new(param)) @@ -1761,6 +1816,11 @@ where _ => unhandled!(), } } + + #[inline] + fn terminated(&self) -> bool { + self.terminated + } } #[inline] @@ -1943,7 +2003,7 @@ pub mod C0 { /// Unit Separator. pub const US: u8 = 0x1F; /// Delete, should be ignored by terminal. - pub const DEL: u8 = 0x7f; + pub const DEL: u8 = 0x7F; } // Tests for parsing escape sequences. @@ -1954,22 +2014,24 @@ mod tests { use super::*; #[derive(Default)] - pub struct TestSyncHandler; + pub struct TestSyncHandler { + is_sync: usize, + } impl Timeout for TestSyncHandler { #[inline] fn set_timeout(&mut self, _: Duration) { - unreachable!() + self.is_sync += 1; } #[inline] fn clear_timeout(&mut self) { - unreachable!() + self.is_sync = 0; } #[inline] fn pending_timeout(&self) -> bool { - false + self.is_sync != 0 } } @@ -2028,72 +2090,60 @@ mod tests { #[test] fn parse_control_attribute() { - static BYTES: &[u8] = &[0x1b, b'[', b'1', b'm']; + static BYTES: &[u8] = &[0x1B, b'[', b'1', b'm']; let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in BYTES { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, BYTES); assert_eq!(handler.attr, Some(Attr::Bold)); } #[test] fn parse_terminal_identity_csi() { - let bytes: &[u8] = &[0x1b, b'[', b'1', b'c']; + let bytes: &[u8] = &[0x1B, b'[', b'1', b'c']; let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); assert!(!handler.identity_reported); handler.reset_state(); - let bytes: &[u8] = &[0x1b, b'[', b'c']; + let bytes: &[u8] = &[0x1B, b'[', b'c']; - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); assert!(handler.identity_reported); handler.reset_state(); - let bytes: &[u8] = &[0x1b, b'[', b'0', b'c']; + let bytes: &[u8] = &[0x1B, b'[', b'0', b'c']; - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); assert!(handler.identity_reported); } #[test] fn parse_terminal_identity_esc() { - let bytes: &[u8] = &[0x1b, b'Z']; + let bytes: &[u8] = &[0x1B, b'Z']; let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); assert!(handler.identity_reported); handler.reset_state(); - let bytes: &[u8] = &[0x1b, b'#', b'Z']; + let bytes: &[u8] = &[0x1B, b'#', b'Z']; let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); assert!(!handler.identity_reported); handler.reset_state(); @@ -2102,16 +2152,14 @@ mod tests { #[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';', + 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::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in BYTES { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, BYTES); let spec = Rgb { r: 128, g: 66, b: 255 }; @@ -2122,38 +2170,34 @@ mod tests { #[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' ', + 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' ', 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::<TestSyncHandler>::new(); - for byte in BYTES { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, BYTES); } #[test] fn parse_designate_g0_as_line_drawing() { - static BYTES: &[u8] = &[0x1b, b'(', b'0']; + static BYTES: &[u8] = &[0x1B, b'(', b'0']; let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in BYTES { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, BYTES); assert_eq!(handler.index, CharsetIndex::G0); assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); @@ -2161,37 +2205,35 @@ mod tests { #[test] fn parse_designate_g1_as_line_drawing_and_invoke() { - static BYTES: &[u8] = &[0x1b, b')', b'0', 0x0e]; + static BYTES: &[u8] = &[0x1B, b')', b'0', 0x0E]; let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in &BYTES[..3] { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, &BYTES[..3]); assert_eq!(handler.index, CharsetIndex::G1); assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); let mut handler = MockHandler::default(); - parser.advance(&mut handler, BYTES[3]); + 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 })); + 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 })); + 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] @@ -2228,11 +2270,9 @@ mod tests { let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); - assert_eq!(handler.color, Some(Rgb { r: 0xf0, g: 0xf0, b: 0xf0 })); + assert_eq!(handler.color, Some(Rgb { r: 0xF0, g: 0xF0, b: 0xF0 })); } #[test] @@ -2242,9 +2282,7 @@ mod tests { let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); assert_eq!(handler.reset_colors, vec![1]); } @@ -2256,9 +2294,7 @@ mod tests { let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); let expected: Vec<usize> = (0..256).collect(); assert_eq!(handler.reset_colors, expected); @@ -2271,30 +2307,148 @@ mod tests { let mut parser = Processor::<TestSyncHandler>::new(); let mut handler = MockHandler::default(); - for byte in bytes { - parser.advance(&mut handler, *byte); - } + parser.advance(&mut handler, bytes); let expected: Vec<usize> = (0..256).collect(); assert_eq!(handler.reset_colors, expected); } #[test] + fn partial_sync_updates() { + let mut parser = Processor::<TestSyncHandler>::new(); + let mut handler = MockHandler::default(); + + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_none()); + + // Start synchronized update. + + parser.advance(&mut handler, b"\x1b[?20"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_none()); + + parser.advance(&mut handler, b"26h"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + // Dispatch some data. + + parser.advance(&mut handler, b"random \x1b[31m stuff"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + // Extend synchronized update. + + parser.advance(&mut handler, b"\x1b[?20"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + parser.advance(&mut handler, b"26h"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 2); + assert!(handler.attr.is_none()); + + // Terminate synchronized update. + + parser.advance(&mut handler, b"\x1b[?20"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 2); + assert!(handler.attr.is_none()); + + parser.advance(&mut handler, b"26l"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_some()); + } + + #[test] + fn sync_bursts_buffer() { + let mut parser = Processor::<TestSyncHandler>::new(); + let mut handler = MockHandler::default(); + + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_none()); + + // Repeat test twice to ensure internal state is reset properly. + for _ in 0..2 { + // Start synchronized update. + parser.advance(&mut handler, b"\x1b[?2026h"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + // Ensure sync works. + parser.advance(&mut handler, b"\x1b[31m"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + // Exceed sync buffer dimensions. + parser.advance(&mut handler, "a".repeat(SYNC_BUFFER_SIZE).as_bytes()); + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.take().is_some()); + + // Ensure new events are dispatched directly. + parser.advance(&mut handler, b"\x1b[31m"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.take().is_some()); + } + } + + #[test] + fn mixed_sync_escape() { + let mut parser = Processor::<TestSyncHandler>::new(); + let mut handler = MockHandler::default(); + + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_none()); + + // Start synchronized update with immediate SGR. + parser.advance(&mut handler, b"\x1b[?2026h\x1b[31m"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + // Terminate synchronized update and check for SGR. + parser.advance(&mut handler, b"\x1b[?2026l"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_some()); + } + + #[test] + fn sync_bsu_with_esu() { + let mut parser = Processor::<TestSyncHandler>::new(); + let mut handler = MockHandler::default(); + + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert!(handler.attr.is_none()); + + // Start synchronized update with immediate SGR. + parser.advance(&mut handler, b"\x1b[?2026h\x1b[1m"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 1); + assert!(handler.attr.is_none()); + + // Terminate synchronized update, but immediately start a new one. + parser.advance(&mut handler, b"\x1b[?2026l\x1b[?2026h\x1b[4m"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 2); + assert_eq!(handler.attr.take(), Some(Attr::Bold)); + + // Terminate again, expecting one buffered SGR. + parser.advance(&mut handler, b"\x1b[?2026l"); + assert_eq!(parser.state.sync_state.timeout.is_sync, 0); + assert_eq!(handler.attr.take(), Some(Attr::Underline)); + } + + #[test] #[cfg(not(feature = "no_std"))] fn contrast() { - let rgb1 = Rgb { r: 0xff, g: 0xff, b: 0xff }; + 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 }; + 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 }; + 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 }; + let rgb2 = Rgb { r: 0xFE, g: 0xDC, b: 0xBA }; assert!((rgb1.contrast(rgb2) - 9.786_558_997_257_74).abs() < f64::EPSILON); } } |