mirror of
https://github.com/wez/wezterm.git
synced 2024-12-19 03:11:31 +03:00
add plumbing for escape sequence parsing
This commit is contained in:
parent
ca7c22ce74
commit
60b19aa6b4
@ -10,3 +10,7 @@ palette = "~0.4"
|
||||
serde = "~1.0"
|
||||
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"
|
||||
|
20
src/cell.rs
20
src/cell.rs
@ -86,14 +86,22 @@ pub enum Underline {
|
||||
Double = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum Blink {
|
||||
None = 0,
|
||||
Slow = 1,
|
||||
Rapid = 2,
|
||||
}
|
||||
|
||||
impl CellAttributes {
|
||||
bitfield!(intensity, set_intensity, Intensity, 0b11, 0);
|
||||
bitfield!(underline, set_underline, Underline, 0b11, 2);
|
||||
bitfield!(italic, set_italic, 4);
|
||||
bitfield!(blink, set_blink, 5);
|
||||
bitfield!(reverse, set_reverse, 6);
|
||||
bitfield!(strikethrough, set_strikethrough, 7);
|
||||
bitfield!(invisible, set_invisible, 8);
|
||||
bitfield!(blink, set_blink, Blink, 0b11, 4);
|
||||
bitfield!(italic, set_italic, 6);
|
||||
bitfield!(reverse, set_reverse, 7);
|
||||
bitfield!(strikethrough, set_strikethrough, 8);
|
||||
bitfield!(invisible, set_invisible, 9);
|
||||
|
||||
pub fn set_foreground<C: Into<ColorAttribute>>(&mut self, foreground: C) -> &mut Self {
|
||||
self.foreground = foreground.into();
|
||||
@ -164,7 +172,7 @@ pub enum AttributeChange {
|
||||
Intensity(Intensity),
|
||||
Underline(Underline),
|
||||
Italic(bool),
|
||||
Blink(bool),
|
||||
Blink(Blink),
|
||||
Reverse(bool),
|
||||
StrikeThrough(bool),
|
||||
Invisible(bool),
|
||||
|
13
src/color.rs
13
src/color.rs
@ -97,6 +97,7 @@ pub enum ColorSpec {
|
||||
Default,
|
||||
/// Use either a raw number, or use values from the `AnsiColor` enum
|
||||
PaletteIndex(u8),
|
||||
TrueColor(RgbColor),
|
||||
}
|
||||
|
||||
impl Default for ColorSpec {
|
||||
@ -105,6 +106,18 @@ impl Default for ColorSpec {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnsiColor> for ColorSpec {
|
||||
fn from(col: AnsiColor) -> Self {
|
||||
ColorSpec::PaletteIndex(col as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RgbColor> for ColorSpec {
|
||||
fn from(col: RgbColor) -> Self {
|
||||
ColorSpec::TrueColor(col)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ColorAttribute {
|
||||
/// Used if the terminal supports full color
|
||||
|
429
src/escape/csi.rs
Normal file
429
src/escape/csi.rs
Normal file
@ -0,0 +1,429 @@
|
||||
use cell::{Blink, Intensity, Underline};
|
||||
use color::{AnsiColor, ColorSpec, RgbColor};
|
||||
use num;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CSI {
|
||||
/// SGR: Set Graphics Rendition.
|
||||
/// These values affect how the character is rendered.
|
||||
Sgr(Sgr),
|
||||
|
||||
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 CSI sequence; this typically
|
||||
/// defines how to interpret the other parameters.
|
||||
control: char,
|
||||
},
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Sgr {
|
||||
/// Resets rendition to defaults. Typically switches off
|
||||
/// all other Sgr options, but may have greater or lesser impact.
|
||||
Reset,
|
||||
/// Set the intensity/bold level
|
||||
Intensity(Intensity),
|
||||
Underline(Underline),
|
||||
Blink(Blink),
|
||||
Italic(bool),
|
||||
Inverse(bool),
|
||||
Invisible(bool),
|
||||
StrikeThrough(bool),
|
||||
Font(Font),
|
||||
Foreground(ColorSpec),
|
||||
Background(ColorSpec),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Font {
|
||||
Default,
|
||||
Alternate(u8),
|
||||
}
|
||||
|
||||
/// Constrol Sequence Initiator (CSI) Parser.
|
||||
/// Since many sequences allow for composition of actions by separating
|
||||
/// `;` character, we need to be able to iterate over
|
||||
/// the set of parsed actions from a given CSI sequence.
|
||||
/// `CSIParser` implements an Iterator that yields `CSI` instances as
|
||||
/// it parses them out from the input sequence.
|
||||
struct CSIParser<'a> {
|
||||
intermediates: &'a [u8],
|
||||
/// From vte::Perform: this flag is set when more than two intermediates
|
||||
/// arrived and subsequent characters were ignored.
|
||||
ignored_extra_intermediates: bool,
|
||||
control: char,
|
||||
/// While params is_some we have more data to consume. The advance_by
|
||||
/// method updates the slice as we consume data.
|
||||
/// In a number of cases an empty params list is used to indicate
|
||||
/// default values, especially for SGR, so we need to be careful not
|
||||
/// to update params to an empty slice.
|
||||
params: Option<&'a [i64]>,
|
||||
}
|
||||
|
||||
impl CSI {
|
||||
/// Parse a CSI sequence.
|
||||
/// Returns an iterator that yields individual CSI actions.
|
||||
/// Why not a single? Because sequences like `CSI [ 1 ; 3 m`
|
||||
/// embed two separate actions but are sent as a single unit.
|
||||
/// If no semantic meaning is known for a subsequence, the remainder
|
||||
/// of the sequence is returned wrapped in a `CSI::Unspecified` container.
|
||||
pub fn parse<'a>(
|
||||
params: &'a [i64],
|
||||
intermediates: &'a [u8],
|
||||
ignored_extra_intermediates: bool,
|
||||
control: char,
|
||||
) -> impl Iterator<Item = CSI> + 'a {
|
||||
CSIParser {
|
||||
intermediates,
|
||||
ignored_extra_intermediates,
|
||||
control,
|
||||
params: Some(params),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A little helper to convert i64 -> u8 if safe
|
||||
fn to_u8(v: i64) -> Result<u8, ()> {
|
||||
if v <= u8::max_value() as i64 {
|
||||
Ok(v as u8)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CSIParser<'a> {
|
||||
/// Consume some number of elements from params and update it.
|
||||
/// Take care to avoid setting params back to an empty slice
|
||||
/// as this would trigger returning a default value and/or
|
||||
/// an unterminated parse loop.
|
||||
fn advance_by<T>(&mut self, n: usize, params: &'a [i64], result: T) -> T {
|
||||
let (_, next) = params.split_at(n);
|
||||
if !next.is_empty() {
|
||||
self.params = Some(next);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_sgr_color(&mut self, params: &'a [i64]) -> Result<ColorSpec, ()> {
|
||||
if params.len() >= 5 && params[1] == 2 {
|
||||
let red = to_u8(params[2])?;
|
||||
let green = to_u8(params[3])?;
|
||||
let blue = to_u8(params[4])?;
|
||||
let res = RgbColor::new(red, green, blue).into();
|
||||
Ok(self.advance_by(5, params, res))
|
||||
} else if params.len() >= 3 && params[1] == 5 {
|
||||
let idx = to_u8(params[2])?;
|
||||
Ok(self.advance_by(3, params, ColorSpec::PaletteIndex(idx)))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn sgr(&mut self, params: &'a [i64]) -> Result<Sgr, ()> {
|
||||
if params.len() == 0 {
|
||||
// With no parameters, treat as equivalent to Reset.
|
||||
Ok(Sgr::Reset)
|
||||
} else {
|
||||
// Consume a single parameter and return the parsed result
|
||||
macro_rules! one {
|
||||
($t:expr) => {
|
||||
Ok(self.advance_by(1, params, $t))
|
||||
};
|
||||
};
|
||||
|
||||
match num::FromPrimitive::from_i64(params[0]) {
|
||||
None => Err(()),
|
||||
Some(sgr) => match sgr {
|
||||
SgrCode::Reset => one!(Sgr::Reset),
|
||||
SgrCode::IntensityBold => one!(Sgr::Intensity(Intensity::Bold)),
|
||||
SgrCode::IntensityDim => one!(Sgr::Intensity(Intensity::Half)),
|
||||
SgrCode::NormalIntensity => one!(Sgr::Intensity(Intensity::Normal)),
|
||||
SgrCode::UnderlineOn => one!(Sgr::Underline(Underline::Single)),
|
||||
SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)),
|
||||
SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)),
|
||||
SgrCode::BlinkOn => one!(Sgr::Blink(Blink::Slow)),
|
||||
SgrCode::RapidBlinkOn => one!(Sgr::Blink(Blink::Rapid)),
|
||||
SgrCode::BlinkOff => one!(Sgr::Blink(Blink::None)),
|
||||
SgrCode::ItalicOn => one!(Sgr::Italic(true)),
|
||||
SgrCode::ItalicOff => one!(Sgr::Italic(false)),
|
||||
SgrCode::ForegroundColor => {
|
||||
self.parse_sgr_color(params).map(|c| Sgr::Foreground(c))
|
||||
}
|
||||
SgrCode::ForegroundBlack => one!(Sgr::Foreground(AnsiColor::Black.into())),
|
||||
SgrCode::ForegroundRed => one!(Sgr::Foreground(AnsiColor::Maroon.into())),
|
||||
SgrCode::ForegroundGreen => one!(Sgr::Foreground(AnsiColor::Green.into())),
|
||||
SgrCode::ForegroundYellow => one!(Sgr::Foreground(AnsiColor::Olive.into())),
|
||||
SgrCode::ForegroundBlue => one!(Sgr::Foreground(AnsiColor::Navy.into())),
|
||||
SgrCode::ForegroundMagenta => one!(Sgr::Foreground(AnsiColor::Purple.into())),
|
||||
SgrCode::ForegroundCyan => one!(Sgr::Foreground(AnsiColor::Teal.into())),
|
||||
SgrCode::ForegroundWhite => one!(Sgr::Foreground(AnsiColor::Silver.into())),
|
||||
SgrCode::ForegroundDefault => one!(Sgr::Foreground(ColorSpec::Default)),
|
||||
SgrCode::ForegroundBrightBlack => one!(Sgr::Foreground(AnsiColor::Grey.into())),
|
||||
SgrCode::ForegroundBrightRed => one!(Sgr::Foreground(AnsiColor::Red.into())),
|
||||
SgrCode::ForegroundBrightGreen => one!(Sgr::Foreground(AnsiColor::Lime.into())),
|
||||
SgrCode::ForegroundBrightYellow => {
|
||||
one!(Sgr::Foreground(AnsiColor::Yellow.into()))
|
||||
}
|
||||
SgrCode::ForegroundBrightBlue => one!(Sgr::Foreground(AnsiColor::Blue.into())),
|
||||
SgrCode::ForegroundBrightMagenta => {
|
||||
one!(Sgr::Foreground(AnsiColor::Fuschia.into()))
|
||||
}
|
||||
SgrCode::ForegroundBrightCyan => one!(Sgr::Foreground(AnsiColor::Aqua.into())),
|
||||
SgrCode::ForegroundBrightWhite => {
|
||||
one!(Sgr::Foreground(AnsiColor::White.into()))
|
||||
}
|
||||
|
||||
SgrCode::BackgroundColor => {
|
||||
self.parse_sgr_color(params).map(|c| Sgr::Background(c))
|
||||
}
|
||||
SgrCode::BackgroundBlack => one!(Sgr::Background(AnsiColor::Black.into())),
|
||||
SgrCode::BackgroundRed => one!(Sgr::Background(AnsiColor::Maroon.into())),
|
||||
SgrCode::BackgroundGreen => one!(Sgr::Background(AnsiColor::Green.into())),
|
||||
SgrCode::BackgroundYellow => one!(Sgr::Background(AnsiColor::Olive.into())),
|
||||
SgrCode::BackgroundBlue => one!(Sgr::Background(AnsiColor::Navy.into())),
|
||||
SgrCode::BackgroundMagenta => one!(Sgr::Background(AnsiColor::Purple.into())),
|
||||
SgrCode::BackgroundCyan => one!(Sgr::Background(AnsiColor::Teal.into())),
|
||||
SgrCode::BackgroundWhite => one!(Sgr::Background(AnsiColor::Silver.into())),
|
||||
SgrCode::BackgroundDefault => one!(Sgr::Background(ColorSpec::Default)),
|
||||
SgrCode::BackgroundBrightBlack => one!(Sgr::Background(AnsiColor::Grey.into())),
|
||||
SgrCode::BackgroundBrightRed => one!(Sgr::Background(AnsiColor::Red.into())),
|
||||
SgrCode::BackgroundBrightGreen => one!(Sgr::Background(AnsiColor::Lime.into())),
|
||||
SgrCode::BackgroundBrightYellow => {
|
||||
one!(Sgr::Background(AnsiColor::Yellow.into()))
|
||||
}
|
||||
SgrCode::BackgroundBrightBlue => one!(Sgr::Background(AnsiColor::Blue.into())),
|
||||
SgrCode::BackgroundBrightMagenta => {
|
||||
one!(Sgr::Background(AnsiColor::Fuschia.into()))
|
||||
}
|
||||
SgrCode::BackgroundBrightCyan => one!(Sgr::Background(AnsiColor::Aqua.into())),
|
||||
SgrCode::BackgroundBrightWhite => {
|
||||
one!(Sgr::Background(AnsiColor::White.into()))
|
||||
}
|
||||
|
||||
SgrCode::InverseOn => one!(Sgr::Inverse(true)),
|
||||
SgrCode::InverseOff => one!(Sgr::Inverse(false)),
|
||||
SgrCode::InvisibleOn => one!(Sgr::Invisible(true)),
|
||||
SgrCode::InvisibleOff => one!(Sgr::Invisible(false)),
|
||||
SgrCode::StrikeThroughOn => one!(Sgr::StrikeThrough(true)),
|
||||
SgrCode::StrikeThroughOff => one!(Sgr::StrikeThrough(false)),
|
||||
SgrCode::DefaultFont => one!(Sgr::Font(Font::Default)),
|
||||
SgrCode::AltFont1 => one!(Sgr::Font(Font::Alternate(1))),
|
||||
SgrCode::AltFont2 => one!(Sgr::Font(Font::Alternate(2))),
|
||||
SgrCode::AltFont3 => one!(Sgr::Font(Font::Alternate(3))),
|
||||
SgrCode::AltFont4 => one!(Sgr::Font(Font::Alternate(4))),
|
||||
SgrCode::AltFont5 => one!(Sgr::Font(Font::Alternate(5))),
|
||||
SgrCode::AltFont6 => one!(Sgr::Font(Font::Alternate(6))),
|
||||
SgrCode::AltFont7 => one!(Sgr::Font(Font::Alternate(7))),
|
||||
SgrCode::AltFont8 => one!(Sgr::Font(Font::Alternate(8))),
|
||||
SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive)]
|
||||
pub enum SgrCode {
|
||||
Reset = 0,
|
||||
IntensityBold = 1,
|
||||
IntensityDim = 2,
|
||||
ItalicOn = 3,
|
||||
UnderlineOn = 4,
|
||||
/// Blinks < 150 times per minute
|
||||
BlinkOn = 5,
|
||||
/// Blinks > 150 times per minute
|
||||
RapidBlinkOn = 6,
|
||||
InverseOn = 7,
|
||||
InvisibleOn = 8,
|
||||
StrikeThroughOn = 9,
|
||||
DefaultFont = 10,
|
||||
AltFont1 = 11,
|
||||
AltFont2 = 12,
|
||||
AltFont3 = 13,
|
||||
AltFont4 = 14,
|
||||
AltFont5 = 15,
|
||||
AltFont6 = 16,
|
||||
AltFont7 = 17,
|
||||
AltFont8 = 18,
|
||||
AltFont9 = 19,
|
||||
// Fraktur = 20,
|
||||
UnderlineDouble = 21,
|
||||
NormalIntensity = 22,
|
||||
ItalicOff = 23,
|
||||
UnderlineOff = 24,
|
||||
BlinkOff = 25,
|
||||
InverseOff = 27,
|
||||
InvisibleOff = 28,
|
||||
StrikeThroughOff = 29,
|
||||
ForegroundBlack = 30,
|
||||
ForegroundRed = 31,
|
||||
ForegroundGreen = 32,
|
||||
ForegroundYellow = 33,
|
||||
ForegroundBlue = 34,
|
||||
ForegroundMagenta = 35,
|
||||
ForegroundCyan = 36,
|
||||
ForegroundWhite = 37,
|
||||
ForegroundDefault = 39,
|
||||
BackgroundBlack = 40,
|
||||
BackgroundRed = 41,
|
||||
BackgroundGreen = 42,
|
||||
BackgroundYellow = 43,
|
||||
BackgroundBlue = 44,
|
||||
BackgroundMagenta = 45,
|
||||
BackgroundCyan = 46,
|
||||
BackgroundWhite = 47,
|
||||
BackgroundDefault = 49,
|
||||
|
||||
ForegroundBrightBlack = 90,
|
||||
ForegroundBrightRed = 91,
|
||||
ForegroundBrightGreen = 92,
|
||||
ForegroundBrightYellow = 93,
|
||||
ForegroundBrightBlue = 94,
|
||||
ForegroundBrightMagenta = 95,
|
||||
ForegroundBrightCyan = 96,
|
||||
ForegroundBrightWhite = 97,
|
||||
|
||||
BackgroundBrightBlack = 100,
|
||||
BackgroundBrightRed = 101,
|
||||
BackgroundBrightGreen = 102,
|
||||
BackgroundBrightYellow = 103,
|
||||
BackgroundBrightBlue = 104,
|
||||
BackgroundBrightMagenta = 105,
|
||||
BackgroundBrightCyan = 106,
|
||||
BackgroundBrightWhite = 107,
|
||||
|
||||
/// Maybe followed either either a 256 color palette index or
|
||||
/// a sequence describing a true color rgb value
|
||||
ForegroundColor = 38,
|
||||
BackgroundColor = 48,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CSIParser<'a> {
|
||||
type Item = CSI;
|
||||
|
||||
fn next(&mut self) -> Option<CSI> {
|
||||
let params = self.params.take();
|
||||
|
||||
match (self.control, self.intermediates, params) {
|
||||
(_, _, None) => None,
|
||||
('m', &[], Some(params)) => match self.sgr(params) {
|
||||
Ok(sgr) => Some(CSI::Sgr(sgr)),
|
||||
Err(()) => Some(CSI::Unspecified {
|
||||
params: params.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: self.ignored_extra_intermediates,
|
||||
control: self.control,
|
||||
}),
|
||||
},
|
||||
|
||||
// Catch-all: just report the leftovers
|
||||
(control, intermediates, Some(params)) => Some(CSI::Unspecified {
|
||||
params: params.to_vec(),
|
||||
intermediates: intermediates.to_vec(),
|
||||
ignored_extra_intermediates: self.ignored_extra_intermediates,
|
||||
control,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
fn parse(control: char, params: &[i64]) -> Vec<CSI> {
|
||||
CSI::parse(params, &[], false, control).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
assert_eq!(parse('m', &[]), vec![CSI::Sgr(Sgr::Reset)]);
|
||||
assert_eq!(parse('m', &[0]), vec![CSI::Sgr(Sgr::Reset)]);
|
||||
assert_eq!(
|
||||
parse('m', &[1]),
|
||||
vec![CSI::Sgr(Sgr::Intensity(Intensity::Bold))]
|
||||
);
|
||||
assert_eq!(
|
||||
parse('m', &[1, 3]),
|
||||
vec![
|
||||
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
|
||||
CSI::Sgr(Sgr::Italic(true)),
|
||||
]
|
||||
);
|
||||
|
||||
// Verify that we propagate Unspecified for codes
|
||||
// that we don't recognize.
|
||||
assert_eq!(
|
||||
parse('m', &[1, 3, 1231231]),
|
||||
vec![
|
||||
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
|
||||
CSI::Sgr(Sgr::Italic(true)),
|
||||
CSI::Unspecified {
|
||||
params: [1231231].to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
parse('m', &[1, 1231231, 3]),
|
||||
vec![
|
||||
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
|
||||
CSI::Unspecified {
|
||||
params: [1231231, 3].to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
parse('m', &[1231231, 3]),
|
||||
vec![CSI::Unspecified {
|
||||
params: [1231231, 3].to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color() {
|
||||
assert_eq!(
|
||||
parse('m', &[38, 2]),
|
||||
vec![CSI::Unspecified {
|
||||
params: [38, 2].to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
}]
|
||||
);
|
||||
assert_eq!(
|
||||
parse('m', &[38, 2, 255, 255, 255]),
|
||||
vec![CSI::Sgr(Sgr::Foreground(ColorSpec::TrueColor(
|
||||
RgbColor::new(255, 255, 255),
|
||||
)))]
|
||||
);
|
||||
assert_eq!(
|
||||
parse('m', &[38, 5, 220, 255, 255]),
|
||||
vec![
|
||||
CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(220))),
|
||||
CSI::Unspecified {
|
||||
params: [255, 255].to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
116
src/escape/mod.rs
Normal file
116
src/escape/mod.rs
Normal file
@ -0,0 +1,116 @@
|
||||
//! This module provides the ability to parse escape sequences and attach
|
||||
//! semantic meaning to them. It can also encode the semantic values as
|
||||
//! escape sequences. It provides encoding and decoding functionality
|
||||
//! only; it does not provide terminal emulation facilities itself.
|
||||
use num;
|
||||
pub mod csi;
|
||||
pub mod osc;
|
||||
pub mod parser;
|
||||
|
||||
use self::csi::CSI;
|
||||
use self::osc::OperatingSystemCommand;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
/// Send a single printable character to the display
|
||||
Print(char),
|
||||
/// A C0 or C1 control code
|
||||
Control(Control),
|
||||
/// Device control. This is uncommon wrt. terminal emulation.
|
||||
DeviceControl(DeviceControlMode),
|
||||
/// A command that typically doesn't change the contents of the
|
||||
/// terminal, but rather influences how it displays or otherwise
|
||||
/// interacts with the rest of the system
|
||||
OperatingSystemCommand(OperatingSystemCommand),
|
||||
CSI(CSI),
|
||||
Esc(Esc),
|
||||
}
|
||||
|
||||
#[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.
|
||||
/// This mode is activated and must remain active until
|
||||
/// `Exit` is observed. While the mode is
|
||||
/// active, data is made available to the device mode via
|
||||
/// the `Data` variant.
|
||||
Enter {
|
||||
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,
|
||||
},
|
||||
/// Exit the current device control mode
|
||||
Exit,
|
||||
/// Data for the device mode to consume
|
||||
Data(u8),
|
||||
}
|
||||
|
||||
/// C0 or C1 control codes
|
||||
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum ControlCode {
|
||||
Null = 0,
|
||||
StartOfHeading = 1,
|
||||
StartOfText = 2,
|
||||
EndOfText = 3,
|
||||
EndOfTransmission = 4,
|
||||
Enquiry = 5,
|
||||
Acknowledge = 6,
|
||||
Bell = 7,
|
||||
Backspace = 8,
|
||||
HorizontalTab = b'\t',
|
||||
LineFeed = b'\n',
|
||||
VerticalTab = 0xb,
|
||||
FormFeed = 0xc,
|
||||
CarriageReturn = b'\r',
|
||||
ShiftOut = 0xe,
|
||||
ShiftIn = 0xf,
|
||||
DataLinkEscape = 0x10,
|
||||
DeviceControlOne = 0x11,
|
||||
DeviceControlTwo = 0x12,
|
||||
DeviceControlThree = 0x13,
|
||||
DeviceControlFour = 0x14,
|
||||
NegativeAcknowledge = 0x15,
|
||||
SynchronousIdle = 0x16,
|
||||
EndOfTransmissionBlock = 0x17,
|
||||
Cancel = 0x18,
|
||||
EndOfMedium = 0x19,
|
||||
Substitute = 0x1a,
|
||||
Escape = 0x1b,
|
||||
FileSeparator = 0x1c,
|
||||
GroupSeparator = 0x1d,
|
||||
RecordSeparator = 0x1e,
|
||||
UnitSeparator = 0x1f,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Control {
|
||||
Code(ControlCode),
|
||||
Unspecified(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for Control {
|
||||
fn from(b: u8) -> Self {
|
||||
match num::FromPrimitive::from_u8(b) {
|
||||
Some(result) => Control::Code(result),
|
||||
None => Control::Unspecified(b),
|
||||
}
|
||||
}
|
||||
}
|
37
src/escape/osc.rs
Normal file
37
src/escape/osc.rs
Normal file
@ -0,0 +1,37 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum OperatingSystemCommand {
|
||||
Unspecified(Vec<Vec<u8>>),
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive)]
|
||||
pub enum OperatingSystemCommandCode {
|
||||
SetIconNameAndWindowTitle = 0,
|
||||
SetIconName = 1,
|
||||
SetWindowTitle = 2,
|
||||
SetXWindowProperty = 3,
|
||||
ChangeColorNumber = 4,
|
||||
/// iTerm2
|
||||
ChangeTitleTabColor = 6,
|
||||
SetCurrentWorkingDirectory = 7,
|
||||
/// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||
Hyperlink = 8,
|
||||
/// iTerm2
|
||||
SystemNotification = 9,
|
||||
SetTextForegroundColor = 10,
|
||||
SetTextBackgroundColor = 11,
|
||||
SetTextCursorColor = 12,
|
||||
SetMouseForegroundColor = 13,
|
||||
SetMouseBackgroundColor = 14,
|
||||
SetTektronixForegroundColor = 15,
|
||||
SetTektronixBackgroundColor = 16,
|
||||
SetHighlightColor = 17,
|
||||
SetTektronixCursorColor = 18,
|
||||
SetLogFileName = 46,
|
||||
SetFont = 50,
|
||||
EmacsShell = 51,
|
||||
ManipulateSelectionData = 52,
|
||||
RxvtProprietary = 777,
|
||||
ITermProprietary = 1337,
|
||||
}
|
152
src/escape/parser/mod.rs
Normal file
152
src/escape/parser/mod.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use escape::{Action, DeviceControlMode, Esc, OperatingSystemCommand, CSI};
|
||||
use vte;
|
||||
|
||||
/// The `Parser` struct holds the state machine that is used to decode
|
||||
/// a sequence of bytes. The byte sequence can be streaming into the
|
||||
/// state machine.
|
||||
/// You can either have the parser trigger a callback as `Action`s are
|
||||
/// decoded, or have it return a `Vec<Action>` holding zero-or-more
|
||||
/// decoded actions.
|
||||
pub struct Parser {
|
||||
state_machine: vte::Parser,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state_machine: vte::Parser::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse<F: FnMut(Action)>(&mut self, bytes: &[u8], mut callback: F) {
|
||||
let mut perform = Performer {
|
||||
callback: &mut callback,
|
||||
};
|
||||
for b in bytes {
|
||||
self.state_machine.advance(&mut perform, *b);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_as_vec(&mut self, bytes: &[u8]) -> Vec<Action> {
|
||||
let mut result = Vec::new();
|
||||
self.parse(bytes, |action| result.push(action));
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
struct Performer<'a, F: FnMut(Action) + 'a> {
|
||||
callback: &'a mut F,
|
||||
}
|
||||
|
||||
impl<'a, F: FnMut(Action)> vte::Perform for Performer<'a, F> {
|
||||
fn print(&mut self, c: char) {
|
||||
(self.callback)(Action::Print(c));
|
||||
}
|
||||
|
||||
fn execute(&mut self, byte: u8) {
|
||||
(self.callback)(Action::Control(byte.into()));
|
||||
}
|
||||
|
||||
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignored_extra_intermediates: bool) {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Enter {
|
||||
params: params.to_vec(),
|
||||
intermediates: intermediates.to_vec(),
|
||||
ignored_extra_intermediates,
|
||||
}));
|
||||
}
|
||||
|
||||
fn put(&mut self, data: u8) {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Data(data)));
|
||||
}
|
||||
|
||||
fn unhook(&mut self) {
|
||||
(self.callback)(Action::DeviceControl(DeviceControlMode::Exit));
|
||||
}
|
||||
|
||||
fn osc_dispatch(&mut self, osc: &[&[u8]]) {
|
||||
let mut vec = Vec::new();
|
||||
for slice in osc {
|
||||
vec.push(slice.to_vec());
|
||||
}
|
||||
(self.callback)(Action::OperatingSystemCommand(
|
||||
OperatingSystemCommand::Unspecified(vec),
|
||||
));
|
||||
}
|
||||
|
||||
fn csi_dispatch(
|
||||
&mut self,
|
||||
params: &[i64],
|
||||
intermediates: &[u8],
|
||||
ignored_extra_intermediates: bool,
|
||||
control: char,
|
||||
) {
|
||||
for action in CSI::parse(params, intermediates, ignored_extra_intermediates, control) {
|
||||
(self.callback)(Action::CSI(action));
|
||||
}
|
||||
}
|
||||
|
||||
fn esc_dispatch(
|
||||
&mut self,
|
||||
params: &[i64],
|
||||
intermediates: &[u8],
|
||||
ignored_extra_intermediates: bool,
|
||||
control: u8,
|
||||
) {
|
||||
(self.callback)(Action::Esc(Esc::Unspecified {
|
||||
params: params.to_vec(),
|
||||
intermediates: intermediates.to_vec(),
|
||||
ignored_extra_intermediates,
|
||||
control,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use cell::Intensity;
|
||||
use escape::csi::Sgr;
|
||||
|
||||
#[test]
|
||||
fn basic_parse() {
|
||||
let mut p = Parser::new();
|
||||
let actions = p.parse_as_vec(b"hello");
|
||||
assert_eq!(
|
||||
vec![
|
||||
Action::Print('h'),
|
||||
Action::Print('e'),
|
||||
Action::Print('l'),
|
||||
Action::Print('l'),
|
||||
Action::Print('o'),
|
||||
],
|
||||
actions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_bold() {
|
||||
let mut p = Parser::new();
|
||||
let actions = p.parse_as_vec(b"\x1b[1mb");
|
||||
assert_eq!(
|
||||
vec![
|
||||
Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
|
||||
Action::Print('b'),
|
||||
],
|
||||
actions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_bold_italic() {
|
||||
let mut p = Parser::new();
|
||||
let actions = p.parse_as_vec(b"\x1b[1;3mb");
|
||||
assert_eq!(
|
||||
vec![
|
||||
Action::CSI(CSI::Sgr(Sgr::Intensity(Intensity::Bold))),
|
||||
Action::CSI(CSI::Sgr(Sgr::Italic(true))),
|
||||
Action::Print('b'),
|
||||
],
|
||||
actions
|
||||
);
|
||||
}
|
||||
}
|
@ -5,8 +5,13 @@ extern crate serde;
|
||||
extern crate terminfo;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate num;
|
||||
extern crate vte;
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
|
||||
pub mod cell;
|
||||
pub mod color;
|
||||
pub mod escape;
|
||||
pub mod render;
|
||||
pub mod screen;
|
||||
|
@ -1,5 +1,5 @@
|
||||
//! Rendering of Changes using terminfo
|
||||
use cell::{AttributeChange, CellAttributes, Intensity, Underline};
|
||||
use cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline};
|
||||
use color::ColorSpec;
|
||||
use failure;
|
||||
use render::Renderer;
|
||||
@ -95,7 +95,7 @@ impl Renderer for TerminfoRenderer {
|
||||
.bold(attr.intensity() == Intensity::Bold)
|
||||
.dim(attr.intensity() == Intensity::Half)
|
||||
.underline(attr.underline() != Underline::None)
|
||||
.blink(attr.blink())
|
||||
.blink(attr.blink() != Blink::None)
|
||||
.reverse(attr.reverse())
|
||||
.invisible(attr.invisible())
|
||||
.to(WriteWrapper::new(out))?;
|
||||
@ -123,6 +123,9 @@ impl Renderer for TerminfoRenderer {
|
||||
tc.blue
|
||||
)?;
|
||||
}
|
||||
(_, _, ColorSpec::TrueColor(_)) => {
|
||||
// TrueColor was specified with no fallback :-(
|
||||
}
|
||||
(_, _, ColorSpec::Default) => {
|
||||
// Terminfo doesn't define a reset color to default, so
|
||||
// we use the ANSI code.
|
||||
@ -147,6 +150,9 @@ impl Renderer for TerminfoRenderer {
|
||||
tc.blue
|
||||
)?;
|
||||
}
|
||||
(_, _, ColorSpec::TrueColor(_)) => {
|
||||
// TrueColor was specified with no fallback :-(
|
||||
}
|
||||
(_, _, ColorSpec::Default) => {
|
||||
// Terminfo doesn't define a reset color to default, so
|
||||
// we use the ANSI code.
|
||||
|
Loading…
Reference in New Issue
Block a user