1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-18 19:01:36 +03:00

Add parsing/encoding for Esc sequences (non-CSI, non-OSC)

This commit is contained in:
Wez Furlong 2018-07-15 14:17:22 -07:00
parent f692a6809a
commit a96e4f29ba
5 changed files with 211 additions and 23 deletions

View File

@ -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"]

View File

@ -1,6 +1,5 @@
//! Colors for attributes
use num;
use palette;
use palette::Srgb;
use serde::{self, Deserialize, Deserializer};

170
src/escape/esc.rs Normal file
View File

@ -0,0 +1,170 @@
use escape::EncodeEscape;
use num::{self, ToPrimitive};
use std;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Esc {
Unspecified {
intermediate: Option<u8>,
/// 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<u8>, control: u8) -> Self {
Self::internal_parse(intermediate, control).unwrap_or_else(|_| Esc::Unspecified {
intermediate,
control,
})
}
fn internal_parse(intermediate: Option<u8>, control: u8) -> Result<Self, ()> {
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<W: std::io::Write>(&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));
}
}

View File

@ -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<T: EncodeEscape> EncodeEscape for [T] {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Esc {
Unspecified {
params: Vec<i64>,
// TODO: can we just make intermediates a single u8?
intermediates: Vec<u8>,
/// 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.

View File

@ -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");
}
}