From a96e4f29ba6cd3d20d079b5abd97b9cff6d91f7d Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 15 Jul 2018 14:17:22 -0700 Subject: [PATCH] Add parsing/encoding for Esc sequences (non-CSI, non-OSC) --- Cargo.toml | 5 +- src/color.rs | 1 - src/escape/esc.rs | 170 +++++++++++++++++++++++++++++++++++++++ src/escape/mod.rs | 19 +---- src/escape/parser/mod.rs | 39 +++++++-- 5 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 src/escape/esc.rs diff --git a/Cargo.toml b/Cargo.toml index 49cd02760..3054b6d40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,8 @@ serde_derive = "~1.0" failure = "~0.1" vte = "0.3.2" num = "0.2.0" -num-derive = "0.2.2" num-traits = "0.2.5" + +[dependencies.num-derive] +version = "~0.2" +features = ["full-syntax"] diff --git a/src/color.rs b/src/color.rs index c9eac3d82..9a6fc5f0a 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,6 +1,5 @@ //! Colors for attributes -use num; use palette; use palette::Srgb; use serde::{self, Deserialize, Deserializer}; diff --git a/src/escape/esc.rs b/src/escape/esc.rs new file mode 100644 index 000000000..f0439e06d --- /dev/null +++ b/src/escape/esc.rs @@ -0,0 +1,170 @@ +use escape::EncodeEscape; +use num::{self, ToPrimitive}; +use std; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Esc { + Unspecified { + intermediate: Option, + /// The final character in the Escape sequence; this typically + /// defines how to interpret the other parameters. + control: u8, + }, + Code(EscCode), +} + +macro_rules! esc { + ($low:expr) => { + ($low as isize) + }; + ($high:expr, $low:expr) => { + ((($high as isize) << 8) | ($low as isize)) + }; +} + +#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, Copy)] +pub enum EscCode { + /// RIS - Full Reset + FullReset = esc!('c'), + /// IND - Index. Note that for Vt52 and Windows 10 ANSI consoles, + /// this is interpreted as CursorUp + Index = esc!('D'), + /// NEL - Next Line + NextLine = esc!('E'), + /// Move the cursor to the bottom left corner of the screen + CursorPositionLowerLeft = esc!('F'), + /// HTS - Horizontal Tab Set + HorizontalTabSet = esc!('H'), + /// RI - Reverse Index – Performs the reverse operation of \n, moves cursor up one line, + /// maintains horizontal position, scrolls buffer if necessary + ReverseIndex = esc!('M'), + /// SS2 Single shift of G2 character set affects next character only + SingleShiftG2 = esc!('N'), + /// SS3 Single shift of G3 character set affects next character only + SingleShiftG3 = esc!('O'), + /// SPA - Start of Guarded Area + StartOfGuardedArea = esc!('V'), + /// EPA - End of Guarded Area + EndOfGuardedArea = esc!('W'), + /// SOS - Start of String + StartOfString = esc!('X'), + /// DECID - Return Terminal ID (obsolete form of CSI c - aka DA) + ReturnTerminalId = esc!('Z'), + /// ST - String Terminator + StringTerminator = esc!('\\'), + /// PM - Privacy Message + PrivacyMessage = esc!('^'), + /// APC - Application Program Command + ApplicationProgramCommand = esc!('_'), + + /// DECSC - Save cursor position + DecSaveCursorPosition = esc!('7'), + /// DECSR - Restore saved cursor position + DecRestoreCursorPosition = esc!('8'), + /// DECPAM - Application Keypad + DecApplicationKeyPad = esc!('='), + /// DECPNM - Normal Keypad + DecNormalKeyPad = esc!('>'), + + /// Designate Character Set – DEC Line Drawing + DecLineDrawing = esc!('(', '0'), + /// Designate Character Set – US ASCII + AsciiCharacterSet = esc!('(', 'B'), + + /// These are typically sent by the terminal when keys are pressed + ApplicationModeArrowUpPress = esc!('O', 'A'), + ApplicationModeArrowDownPress = esc!('O', 'B'), + ApplicationModeArrowRightPress = esc!('O', 'C'), + ApplicationModeArrowLeftPress = esc!('O', 'D'), + ApplicationModeHomePress = esc!('O', 'H'), + ApplicationModeEndPress = esc!('O', 'F'), + F1Press = esc!('O', 'P'), + F2Press = esc!('O', 'Q'), + F3Press = esc!('O', 'R'), + F4Press = esc!('O', 'S'), +} + +impl Esc { + pub fn parse(intermediate: Option, control: u8) -> Self { + Self::internal_parse(intermediate, control).unwrap_or_else(|_| Esc::Unspecified { + intermediate, + control, + }) + } + + fn internal_parse(intermediate: Option, control: u8) -> Result { + let packed = match intermediate { + Some(high) => ((high as u16) << 8) | control as u16, + None => control as u16, + }; + + let code = num::FromPrimitive::from_u16(packed).ok_or(())?; + + Ok(Esc::Code(code)) + } +} + +impl EncodeEscape for Esc { + // TODO: data size optimization opportunity: if we could somehow know that we + // had a run of CSI instances being encoded in sequence, we could + // potentially collapse them together. This is a few bytes difference in + // practice so it may not be worthwhile with modern networks. + fn encode_escape(&self, w: &mut W) -> Result<(), std::io::Error> { + w.write_all(&[0x1b])?; + use self::Esc::*; + match self { + Code(code) => { + let packed = code.to_u16() + .expect("num-derive failed to implement ToPrimitive"); + if packed > u8::max_value() as u16 { + let buf = [(packed >> 8) as u8, (packed & 0xff) as u8]; + w.write_all(&buf)?; + } else { + let buf = [(packed & 0xff) as u8]; + w.write_all(&buf)?; + } + } + Unspecified { + intermediate, + control, + } => { + if let Some(i) = intermediate { + let buf = [*i, *control]; + w.write_all(&buf)?; + } else { + let buf = [*control]; + w.write_all(&buf)?; + } + } + }; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn encode(osc: &Esc) -> String { + let mut res = Vec::new(); + osc.encode_escape(&mut res).unwrap(); + String::from_utf8(res).unwrap() + } + + fn parse(esc: &str) -> Esc { + let result = if esc.len() == 1 { + Esc::parse(None, esc.as_bytes()[0]) + } else { + Esc::parse(Some(esc.as_bytes()[0]), esc.as_bytes()[1]) + }; + + assert_eq!(encode(&result), format!("\x1b{}", esc)); + + result + } + + #[test] + fn test() { + assert_eq!(parse("(0"), Esc::Code(EscCode::DecLineDrawing)); + } +} diff --git a/src/escape/mod.rs b/src/escape/mod.rs index 7c69250c5..29820ffee 100644 --- a/src/escape/mod.rs +++ b/src/escape/mod.rs @@ -6,10 +6,12 @@ use num; use std; pub mod csi; +pub mod esc; pub mod osc; pub mod parser; use self::csi::CSI; +use self::esc::Esc; use self::osc::OperatingSystemCommand; #[derive(Debug, Clone, PartialEq, Eq)] @@ -43,7 +45,7 @@ impl EncodeEscape for Action { Action::DeviceControl(_) => unimplemented!(), Action::OperatingSystemCommand(osc) => osc.encode_escape(w), Action::CSI(csi) => csi.encode_escape(w), - Action::Esc(esc) => unimplemented!(), + Action::Esc(esc) => esc.encode_escape(w), } } } @@ -57,21 +59,6 @@ impl EncodeEscape for [T] { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Esc { - Unspecified { - params: Vec, - // TODO: can we just make intermediates a single u8? - intermediates: Vec, - /// if true, more than two intermediates arrived and the - /// remaining data was ignored - ignored_extra_intermediates: bool, - /// The final character in the Escape sequence; this typically - /// defines how to interpret the other parameters. - control: u8, - }, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum DeviceControlMode { /// Identify device control mode from the encoded parameters. diff --git a/src/escape/parser/mod.rs b/src/escape/parser/mod.rs index c0eb7bcd9..3892bf92e 100644 --- a/src/escape/parser/mod.rs +++ b/src/escape/parser/mod.rs @@ -82,15 +82,20 @@ impl<'a, F: FnMut(Action)> vte::Perform for Performer<'a, F> { fn esc_dispatch( &mut self, - params: &[i64], + _params: &[i64], intermediates: &[u8], - ignored_extra_intermediates: bool, + _ignored_extra_intermediates: bool, control: u8, ) { + // It doesn't appear to be possible for params.len() > 1 due to the way + // that the state machine in vte functions. As such, it also seems to + // be impossible for ignored_extra_intermediates to be true too. (self.callback)(Action::Esc(Esc::Unspecified { - params: params.to_vec(), - intermediates: intermediates.to_vec(), - ignored_extra_intermediates, + intermediate: if intermediates.len() == 1 { + Some(intermediates[0]) + } else { + None + }, control, })); } @@ -177,4 +182,28 @@ mod test { ); assert_eq!(encode(&actions), "\x1b]532534523;hello\x07"); } + + #[test] + fn basic_esc() { + let mut p = Parser::new(); + let actions = p.parse_as_vec(b"\x1bH"); + assert_eq!( + vec![Action::Esc(Esc::Unspecified { + intermediate: None, + control: b'H', + })], + actions + ); + assert_eq!(encode(&actions), "\x1bH"); + + let actions = p.parse_as_vec(b"\x1b%H"); + assert_eq!( + vec![Action::Esc(Esc::Unspecified { + intermediate: Some(b'%'), + control: b'H', + })], + actions + ); + assert_eq!(encode(&actions), "\x1b%H"); + } }