mirror of
https://github.com/wez/wezterm.git
synced 2024-12-19 03:11:31 +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"
|
failure = "~0.1"
|
||||||
vte = "0.3.2"
|
vte = "0.3.2"
|
||||||
num = "0.2.0"
|
num = "0.2.0"
|
||||||
num-derive = "0.2.2"
|
|
||||||
num-traits = "0.2.5"
|
num-traits = "0.2.5"
|
||||||
|
|
||||||
|
[dependencies.num-derive]
|
||||||
|
version = "~0.2"
|
||||||
|
features = ["full-syntax"]
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
//! Colors for attributes
|
//! Colors for attributes
|
||||||
|
|
||||||
use num;
|
|
||||||
use palette;
|
use palette;
|
||||||
use palette::Srgb;
|
use palette::Srgb;
|
||||||
use serde::{self, Deserialize, Deserializer};
|
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;
|
use std;
|
||||||
|
|
||||||
pub mod csi;
|
pub mod csi;
|
||||||
|
pub mod esc;
|
||||||
pub mod osc;
|
pub mod osc;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
use self::csi::CSI;
|
use self::csi::CSI;
|
||||||
|
use self::esc::Esc;
|
||||||
use self::osc::OperatingSystemCommand;
|
use self::osc::OperatingSystemCommand;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@ -43,7 +45,7 @@ impl EncodeEscape for Action {
|
|||||||
Action::DeviceControl(_) => unimplemented!(),
|
Action::DeviceControl(_) => unimplemented!(),
|
||||||
Action::OperatingSystemCommand(osc) => osc.encode_escape(w),
|
Action::OperatingSystemCommand(osc) => osc.encode_escape(w),
|
||||||
Action::CSI(csi) => csi.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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum DeviceControlMode {
|
pub enum DeviceControlMode {
|
||||||
/// Identify device control mode from the encoded parameters.
|
/// 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(
|
fn esc_dispatch(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: &[i64],
|
_params: &[i64],
|
||||||
intermediates: &[u8],
|
intermediates: &[u8],
|
||||||
ignored_extra_intermediates: bool,
|
_ignored_extra_intermediates: bool,
|
||||||
control: u8,
|
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 {
|
(self.callback)(Action::Esc(Esc::Unspecified {
|
||||||
params: params.to_vec(),
|
intermediate: if intermediates.len() == 1 {
|
||||||
intermediates: intermediates.to_vec(),
|
Some(intermediates[0])
|
||||||
ignored_extra_intermediates,
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
control,
|
control,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -177,4 +182,28 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(encode(&actions), "\x1b]532534523;hello\x07");
|
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