1
1
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:
Wez Furlong 2018-07-14 16:26:03 -07:00
parent ca7c22ce74
commit 60b19aa6b4
9 changed files with 778 additions and 8 deletions

View File

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

View File

@ -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),

View File

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

View File

@ -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;

View File

@ -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.