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 = "~1.0"
|
||||||
serde_derive = "~1.0"
|
serde_derive = "~1.0"
|
||||||
failure = "~0.1"
|
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,
|
Double = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum Blink {
|
||||||
|
None = 0,
|
||||||
|
Slow = 1,
|
||||||
|
Rapid = 2,
|
||||||
|
}
|
||||||
|
|
||||||
impl CellAttributes {
|
impl CellAttributes {
|
||||||
bitfield!(intensity, set_intensity, Intensity, 0b11, 0);
|
bitfield!(intensity, set_intensity, Intensity, 0b11, 0);
|
||||||
bitfield!(underline, set_underline, Underline, 0b11, 2);
|
bitfield!(underline, set_underline, Underline, 0b11, 2);
|
||||||
bitfield!(italic, set_italic, 4);
|
bitfield!(blink, set_blink, Blink, 0b11, 4);
|
||||||
bitfield!(blink, set_blink, 5);
|
bitfield!(italic, set_italic, 6);
|
||||||
bitfield!(reverse, set_reverse, 6);
|
bitfield!(reverse, set_reverse, 7);
|
||||||
bitfield!(strikethrough, set_strikethrough, 7);
|
bitfield!(strikethrough, set_strikethrough, 8);
|
||||||
bitfield!(invisible, set_invisible, 8);
|
bitfield!(invisible, set_invisible, 9);
|
||||||
|
|
||||||
pub fn set_foreground<C: Into<ColorAttribute>>(&mut self, foreground: C) -> &mut Self {
|
pub fn set_foreground<C: Into<ColorAttribute>>(&mut self, foreground: C) -> &mut Self {
|
||||||
self.foreground = foreground.into();
|
self.foreground = foreground.into();
|
||||||
@ -164,7 +172,7 @@ pub enum AttributeChange {
|
|||||||
Intensity(Intensity),
|
Intensity(Intensity),
|
||||||
Underline(Underline),
|
Underline(Underline),
|
||||||
Italic(bool),
|
Italic(bool),
|
||||||
Blink(bool),
|
Blink(Blink),
|
||||||
Reverse(bool),
|
Reverse(bool),
|
||||||
StrikeThrough(bool),
|
StrikeThrough(bool),
|
||||||
Invisible(bool),
|
Invisible(bool),
|
||||||
|
13
src/color.rs
13
src/color.rs
@ -97,6 +97,7 @@ pub enum ColorSpec {
|
|||||||
Default,
|
Default,
|
||||||
/// Use either a raw number, or use values from the `AnsiColor` enum
|
/// Use either a raw number, or use values from the `AnsiColor` enum
|
||||||
PaletteIndex(u8),
|
PaletteIndex(u8),
|
||||||
|
TrueColor(RgbColor),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ColorSpec {
|
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)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct ColorAttribute {
|
pub struct ColorAttribute {
|
||||||
/// Used if the terminal supports full color
|
/// 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;
|
extern crate terminfo;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
extern crate num;
|
||||||
|
extern crate vte;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate num_derive;
|
||||||
|
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
pub mod escape;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//! Rendering of Changes using terminfo
|
//! Rendering of Changes using terminfo
|
||||||
use cell::{AttributeChange, CellAttributes, Intensity, Underline};
|
use cell::{AttributeChange, Blink, CellAttributes, Intensity, Underline};
|
||||||
use color::ColorSpec;
|
use color::ColorSpec;
|
||||||
use failure;
|
use failure;
|
||||||
use render::Renderer;
|
use render::Renderer;
|
||||||
@ -95,7 +95,7 @@ impl Renderer for TerminfoRenderer {
|
|||||||
.bold(attr.intensity() == Intensity::Bold)
|
.bold(attr.intensity() == Intensity::Bold)
|
||||||
.dim(attr.intensity() == Intensity::Half)
|
.dim(attr.intensity() == Intensity::Half)
|
||||||
.underline(attr.underline() != Underline::None)
|
.underline(attr.underline() != Underline::None)
|
||||||
.blink(attr.blink())
|
.blink(attr.blink() != Blink::None)
|
||||||
.reverse(attr.reverse())
|
.reverse(attr.reverse())
|
||||||
.invisible(attr.invisible())
|
.invisible(attr.invisible())
|
||||||
.to(WriteWrapper::new(out))?;
|
.to(WriteWrapper::new(out))?;
|
||||||
@ -123,6 +123,9 @@ impl Renderer for TerminfoRenderer {
|
|||||||
tc.blue
|
tc.blue
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
(_, _, ColorSpec::TrueColor(_)) => {
|
||||||
|
// TrueColor was specified with no fallback :-(
|
||||||
|
}
|
||||||
(_, _, ColorSpec::Default) => {
|
(_, _, ColorSpec::Default) => {
|
||||||
// Terminfo doesn't define a reset color to default, so
|
// Terminfo doesn't define a reset color to default, so
|
||||||
// we use the ANSI code.
|
// we use the ANSI code.
|
||||||
@ -147,6 +150,9 @@ impl Renderer for TerminfoRenderer {
|
|||||||
tc.blue
|
tc.blue
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
(_, _, ColorSpec::TrueColor(_)) => {
|
||||||
|
// TrueColor was specified with no fallback :-(
|
||||||
|
}
|
||||||
(_, _, ColorSpec::Default) => {
|
(_, _, ColorSpec::Default) => {
|
||||||
// Terminfo doesn't define a reset color to default, so
|
// Terminfo doesn't define a reset color to default, so
|
||||||
// we use the ANSI code.
|
// we use the ANSI code.
|
||||||
|
Loading…
Reference in New Issue
Block a user