diff options
author | Christian Duerr <contact@christianduerr.com> | 2023-09-19 18:04:55 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-19 16:04:55 +0000 |
commit | 76b565ada7f9511b0bd718d25bfd2ce26ba78578 (patch) | |
tree | 8712320e5d04a28f98eb8d922c2c2837ffac4da1 | |
parent | 90deacff851b27244c93795ddbc7d91fe7ea3cb4 (diff) | |
download | r-alacritty-vte-76b565ada7f9511b0bd718d25bfd2ce26ba78578.tar.gz r-alacritty-vte-76b565ada7f9511b0bd718d25bfd2ce26ba78578.tar.bz2 r-alacritty-vte-76b565ada7f9511b0bd718d25bfd2ce26ba78578.zip |
Switch sync escape from DCS to CSI
See alacritty/alacritty#6845.
-rw-r--r-- | src/ansi.rs | 116 |
1 files changed, 36 insertions, 80 deletions
diff --git a/src/ansi.rs b/src/ansi.rs index f71b773..918e35b 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -39,14 +39,14 @@ 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; +/// Number of bytes in the BSU/ESU CSI sequences. +const SYNC_ESCAPE_LEN: usize = 8; -/// 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']; +/// BSU CSI sequence for beginning or extending synchronized updates. +const BSU_CSI: [u8; SYNC_ESCAPE_LEN] = *b"\x1b[?2026h"; -/// 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']; +/// ESU CSI sequence for terminating synchronized updates. +const ESU_CSI: [u8; SYNC_ESCAPE_LEN] = *b"\x1b[?2026l"; #[derive(Debug, PartialEq, Eq, Hash)] pub struct Hyperlink { @@ -252,9 +252,6 @@ struct ProcessorState<T: Timeout> { /// 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<T>, } @@ -264,33 +261,16 @@ struct SyncState<T: Timeout> { /// Handler for synchronized updates. timeout: T, - /// Sync DCS waiting for termination sequence. - pending_dcs: Option<Dcs>, - /// Bytes read during the synchronized update. buffer: Vec<u8>, } impl<T: Timeout> Default for SyncState<T> { fn default() -> Self { - Self { - buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), - pending_dcs: None, - timeout: T::default(), - } + Self { buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), timeout: T::default() } } } -/// Pending DCS sequence. -#[derive(Debug)] -enum Dcs { - /// Begin of the synchronized update. - SyncStart, - - /// End of the synchronized update. - SyncEnd, -} - /// The processor wraps a `crate::Parser` to ultimately call methods on a Handler. #[cfg(not(feature = "no_std"))] #[derive(Default)] @@ -363,46 +343,29 @@ impl<T: Timeout> Processor<T> { { 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(), - } + // Handle sync CSI escape sequences. + self.advance_sync_csi(handler); } - /// Find the start of sync DCS sequences. - fn advance_sync_dcs_start(&mut self) { + /// Handle BSU/ESU CSI sequences during synchronized update. + fn advance_sync_csi<H>(&mut self, handler: &mut H) + 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_START_LEN); + let offset = len.saturating_sub(SYNC_ESCAPE_LEN); let end = &self.state.sync_state.buffer[offset..]; + // 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 == 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); - } - } - - /// Parse the DCS termination sequence for synchronized updates. - fn advance_sync_dcs_end<H>(&mut self, handler: &mut H, byte: u8) - where - H: Handler, - { - 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.set_timeout(SYNC_UPDATE_TIMEOUT); - }, - Some(Dcs::SyncEnd) => self.stop_sync(handler), - None => (), - }, + 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); } } } @@ -1168,18 +1131,10 @@ where #[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 - ), - } + debug!( + "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}", + params, intermediates, ignore, action + ); } #[inline] @@ -1189,13 +1144,7 @@ where #[inline] fn unhook(&mut self) { - match self.state.dcs { - Some(Dcs::SyncStart) => { - self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); - }, - Some(Dcs::SyncEnd) => (), - _ => debug!("[unhandled unhook]"), - } + debug!("[unhandled unhook]"); } #[inline] @@ -1469,7 +1418,14 @@ where }, ('h', intermediates) => { for param in params_iter.map(|param| param[0]) { - match Mode::from_primitive(intermediates.first(), param) { + let intermediate = intermediates.first(); + + // Handle sync updates opaquely. + if intermediate == Some(&b'?') && param == 2026 { + self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); + } + + match Mode::from_primitive(intermediate, param) { Some(mode) => handler.set_mode(mode), None => unhandled!(), } |