aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2023-09-19 18:04:55 +0200
committerGitHub <noreply@github.com>2023-09-19 16:04:55 +0000
commit76b565ada7f9511b0bd718d25bfd2ce26ba78578 (patch)
tree8712320e5d04a28f98eb8d922c2c2837ffac4da1
parent90deacff851b27244c93795ddbc7d91fe7ea3cb4 (diff)
downloadr-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.rs116
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!(),
}