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:
parent
f692a6809a
commit
a96e4f29ba
@ -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"]
|
||||
|
@ -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
170
src/escape/esc.rs
Normal 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));
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user