summaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/ansi.rs
diff options
context:
space:
mode:
authorTheodore Dubois <tblodt@icloud.com>2019-04-28 06:24:58 -0700
committerChristian Duerr <chrisduerr@users.noreply.github.com>2019-04-28 13:24:58 +0000
commitdbd8538762ef8968a493e1bf996e8693479ca783 (patch)
tree32ac2a6a5e01238a272d4ba534551d2e42903c7a /alacritty_terminal/src/ansi.rs
parent9c6d12ea2c863ba76015bdedc00db13b7307725a (diff)
downloadalacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.gz
alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.zip
Split alacritty into a separate crates
The crate containing the entry point is called alacritty, and the crate containing everything else is called alacritty_terminal.
Diffstat (limited to 'alacritty_terminal/src/ansi.rs')
-rw-r--r--alacritty_terminal/src/ansi.rs1568
1 files changed, 1568 insertions, 0 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
new file mode 100644
index 00000000..c0ebb79c
--- /dev/null
+++ b/alacritty_terminal/src/ansi.rs
@@ -0,0 +1,1568 @@
+// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//! ANSI Terminal Stream Parsing
+use std::io;
+use std::ops::Range;
+use std::str;
+
+use crate::index::{Column, Contains, Line};
+use base64;
+use glutin::MouseCursor;
+use vte;
+
+use crate::term::color::Rgb;
+
+// Parse color arguments
+//
+// Expect that color argument looks like "rgb:xx/xx/xx" or "#xxxxxx"
+fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
+ let mut iter = color.iter();
+
+ macro_rules! next {
+ () => {
+ iter.next().map(|v| *v as char)
+ };
+ }
+
+ macro_rules! parse_hex {
+ () => {{
+ let mut digit: u8 = 0;
+ let next = next!().and_then(|v| v.to_digit(16));
+ if let Some(value) = next {
+ digit = value as u8;
+ }
+
+ let next = next!().and_then(|v| v.to_digit(16));
+ if let Some(value) = next {
+ digit <<= 4;
+ digit += value as u8;
+ }
+ digit
+ }};
+ }
+
+ match next!() {
+ Some('r') => {
+ if next!() != Some('g') {
+ return None;
+ }
+ if next!() != Some('b') {
+ return None;
+ }
+ if next!() != Some(':') {
+ return None;
+ }
+
+ let r = parse_hex!();
+ let val = next!();
+ if val != Some('/') {
+ return None;
+ }
+ let g = parse_hex!();
+ if next!() != Some('/') {
+ return None;
+ }
+ let b = parse_hex!();
+
+ Some(Rgb { r, g, b })
+ },
+ Some('#') => Some(Rgb { r: parse_hex!(), g: parse_hex!(), b: parse_hex!() }),
+ _ => None,
+ }
+}
+
+fn parse_number(input: &[u8]) -> Option<u8> {
+ if input.is_empty() {
+ return None;
+ }
+ let mut num: u8 = 0;
+ for c in input {
+ let c = *c as char;
+ if let Some(digit) = c.to_digit(10) {
+ num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) {
+ Some(v) => v,
+ None => return None,
+ }
+ } else {
+ return None;
+ }
+ }
+ Some(num)
+}
+
+/// The processor wraps a `vte::Parser` to ultimately call methods on a Handler
+pub struct Processor {
+ state: ProcessorState,
+ parser: vte::Parser,
+}
+
+/// Internal state for VTE processor
+struct ProcessorState {
+ preceding_char: Option<char>,
+}
+
+/// Helper type that implements `vte::Perform`.
+///
+/// Processor creates a Performer when running advance and passes the Performer
+/// to `vte::Parser`.
+struct Performer<'a, H: Handler + TermInfo, W: io::Write> {
+ _state: &'a mut ProcessorState,
+ handler: &'a mut H,
+ writer: &'a mut W,
+}
+
+impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> {
+ /// Create a performer
+ #[inline]
+ pub fn new<'b>(
+ state: &'b mut ProcessorState,
+ handler: &'b mut H,
+ writer: &'b mut W,
+ ) -> Performer<'b, H, W> {
+ Performer { _state: state, handler, writer }
+ }
+}
+
+impl Default for Processor {
+ fn default() -> Processor {
+ Processor { state: ProcessorState { preceding_char: None }, parser: vte::Parser::new() }
+ }
+}
+
+impl Processor {
+ pub fn new() -> Processor {
+ Default::default()
+ }
+
+ #[inline]
+ pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
+ where
+ H: Handler + TermInfo,
+ W: io::Write,
+ {
+ let mut performer = Performer::new(&mut self.state, handler, writer);
+ self.parser.advance(&mut performer, byte);
+ }
+}
+
+/// Trait that provides properties of terminal
+pub trait TermInfo {
+ fn lines(&self) -> Line;
+ fn cols(&self) -> Column;
+}
+
+/// Type that handles actions from the parser
+///
+/// XXX Should probably not provide default impls for everything, but it makes
+/// writing specific handler impls for tests far easier.
+pub trait Handler {
+ /// OSC to set window title
+ fn set_title(&mut self, _: &str) {}
+
+ /// Set the window's mouse cursor
+ fn set_mouse_cursor(&mut self, _: MouseCursor) {}
+
+ /// Set the cursor style
+ fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
+
+ /// A character to be displayed
+ fn input(&mut self, _c: char) {}
+
+ /// Set cursor to position
+ fn goto(&mut self, _: Line, _: Column) {}
+
+ /// Set cursor to specific row
+ fn goto_line(&mut self, _: Line) {}
+
+ /// Set cursor to specific column
+ fn goto_col(&mut self, _: Column) {}
+
+ /// Insert blank characters in current line starting from cursor
+ fn insert_blank(&mut self, _: Column) {}
+
+ /// Move cursor up `rows`
+ fn move_up(&mut self, _: Line) {}
+
+ /// Move cursor down `rows`
+ fn move_down(&mut self, _: Line) {}
+
+ /// Identify the terminal (should write back to the pty stream)
+ ///
+ /// TODO this should probably return an io::Result
+ fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {}
+
+ // Report device status
+ fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {}
+
+ /// Move cursor forward `cols`
+ fn move_forward(&mut self, _: Column) {}
+
+ /// Move cursor backward `cols`
+ fn move_backward(&mut self, _: Column) {}
+
+ /// Move cursor down `rows` and set to column 1
+ fn move_down_and_cr(&mut self, _: Line) {}
+
+ /// Move cursor up `rows` and set to column 1
+ fn move_up_and_cr(&mut self, _: Line) {}
+
+ /// Put `count` tabs
+ fn put_tab(&mut self, _count: i64) {}
+
+ /// Backspace `count` characters
+ fn backspace(&mut self) {}
+
+ /// Carriage return
+ fn carriage_return(&mut self) {}
+
+ /// Linefeed
+ fn linefeed(&mut self) {}
+
+ /// Ring the bell
+ ///
+ /// Hopefully this is never implemented
+ fn bell(&mut self) {}
+
+ /// Substitute char under cursor
+ fn substitute(&mut self) {}
+
+ /// Newline
+ fn newline(&mut self) {}
+
+ /// Set current position as a tabstop
+ fn set_horizontal_tabstop(&mut self) {}
+
+ /// Scroll up `rows` rows
+ fn scroll_up(&mut self, _: Line) {}
+
+ /// Scroll down `rows` rows
+ fn scroll_down(&mut self, _: Line) {}
+
+ /// Insert `count` blank lines
+ fn insert_blank_lines(&mut self, _: Line) {}
+
+ /// Delete `count` lines
+ fn delete_lines(&mut self, _: Line) {}
+
+ /// Erase `count` chars in current line following cursor
+ ///
+ /// Erase means resetting to the default state (default colors, no content,
+ /// no mode flags)
+ fn erase_chars(&mut self, _: Column) {}
+
+ /// Delete `count` chars
+ ///
+ /// Deleting a character is like the delete key on the keyboard - everything
+ /// to the right of the deleted things is shifted left.
+ fn delete_chars(&mut self, _: Column) {}
+
+ /// Move backward `count` tabs
+ fn move_backward_tabs(&mut self, _count: i64) {}
+
+ /// Move forward `count` tabs
+ fn move_forward_tabs(&mut self, _count: i64) {}
+
+ /// Save current cursor position
+ fn save_cursor_position(&mut self) {}
+
+ /// Restore cursor position
+ fn restore_cursor_position(&mut self) {}
+
+ /// Clear current line
+ fn clear_line(&mut self, _mode: LineClearMode) {}
+
+ /// Clear screen
+ fn clear_screen(&mut self, _mode: ClearMode) {}
+
+ /// Clear tab stops
+ fn clear_tabs(&mut self, _mode: TabulationClearMode) {}
+
+ /// Reset terminal state
+ fn reset_state(&mut self) {}
+
+ /// Reverse Index
+ ///
+ /// Move the active position to the same horizontal position on the
+ /// preceding line. If the active position is at the top margin, a scroll
+ /// down is performed
+ fn reverse_index(&mut self) {}
+
+ /// set a terminal attribute
+ fn terminal_attribute(&mut self, _attr: Attr) {}
+
+ /// Set mode
+ fn set_mode(&mut self, _mode: Mode) {}
+
+ /// Unset mode
+ fn unset_mode(&mut self, _: Mode) {}
+
+ /// DECSTBM - Set the terminal scrolling region
+ fn set_scrolling_region(&mut self, _: Range<Line>) {}
+
+ /// DECKPAM - Set keypad to applications mode (ESCape instead of digits)
+ fn set_keypad_application_mode(&mut self) {}
+
+ /// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq)
+ fn unset_keypad_application_mode(&mut self) {}
+
+ /// Set one of the graphic character sets, G0 to G3, as the active charset.
+ ///
+ /// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in,
+ /// shift out and locking shift depending on the set being activated
+ fn set_active_charset(&mut self, _: CharsetIndex) {}
+
+ /// Assign a graphic character set to G0, G1, G2 or G3
+ ///
+ /// 'Designate' a graphic character set as one of G0 to G3, so that it can
+ /// later be 'invoked' by `set_active_charset`
+ fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {}
+
+ /// Set an indexed color value
+ fn set_color(&mut self, _: usize, _: Rgb) {}
+
+ /// Reset an indexed color to original value
+ fn reset_color(&mut self, _: usize) {}
+
+ /// Set the clipboard
+ fn set_clipboard(&mut self, _: &str) {}
+
+ /// Run the dectest routine
+ fn dectest(&mut self) {}
+}
+
+/// Describes shape of cursor
+#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
+pub enum CursorStyle {
+ /// Cursor is a block like `▒`
+ Block,
+
+ /// Cursor is an underscore like `_`
+ Underline,
+
+ /// Cursor is a vertical bar `⎸`
+ Beam,
+
+ /// Cursor is a box like `☐`
+ HollowBlock,
+
+ /// Invisible cursor
+ Hidden,
+}
+
+impl Default for CursorStyle {
+ fn default() -> CursorStyle {
+ CursorStyle::Block
+ }
+}
+
+/// Terminal modes
+#[derive(Debug, Eq, PartialEq)]
+pub enum Mode {
+ /// ?1
+ CursorKeys = 1,
+ /// Select 80 or 132 columns per page
+ ///
+ /// CSI ? 3 h -> set 132 column font
+ /// CSI ? 3 l -> reset 80 column font
+ ///
+ /// Additionally,
+ ///
+ /// * set margins to default positions
+ /// * erases all data in page memory
+ /// * resets DECLRMM to unavailable
+ /// * clears data from the status line (if set to host-writable)
+ DECCOLM = 3,
+ /// IRM Insert Mode
+ ///
+ /// NB should be part of non-private mode enum
+ ///
+ /// * `CSI 4 h` change to insert mode
+ /// * `CSI 4 l` reset to replacement mode
+ Insert = 4,
+ /// ?6
+ Origin = 6,
+ /// ?7
+ LineWrap = 7,
+ /// ?12
+ BlinkingCursor = 12,
+ /// 20
+ ///
+ /// NB This is actually a private mode. We should consider adding a second
+ /// enumeration for public/private modesets.
+ LineFeedNewLine = 20,
+ /// ?25
+ ShowCursor = 25,
+ /// ?1000
+ ReportMouseClicks = 1000,
+ /// ?1002
+ ReportCellMouseMotion = 1002,
+ /// ?1003
+ ReportAllMouseMotion = 1003,
+ /// ?1004
+ ReportFocusInOut = 1004,
+ /// ?1006
+ SgrMouse = 1006,
+ /// ?1049
+ SwapScreenAndSetRestoreCursor = 1049,
+ /// ?2004
+ BracketedPaste = 2004,
+}
+
+impl Mode {
+ /// Create mode from a primitive
+ ///
+ /// TODO lots of unhandled values..
+ pub fn from_primitive(private: bool, num: i64) -> Option<Mode> {
+ if private {
+ Some(match num {
+ 1 => Mode::CursorKeys,
+ 3 => Mode::DECCOLM,
+ 6 => Mode::Origin,
+ 7 => Mode::LineWrap,
+ 12 => Mode::BlinkingCursor,
+ 25 => Mode::ShowCursor,
+ 1000 => Mode::ReportMouseClicks,
+ 1002 => Mode::ReportCellMouseMotion,
+ 1003 => Mode::ReportAllMouseMotion,
+ 1004 => Mode::ReportFocusInOut,
+ 1006 => Mode::SgrMouse,
+ 1049 => Mode::SwapScreenAndSetRestoreCursor,
+ 2004 => Mode::BracketedPaste,
+ _ => {
+ trace!("[unimplemented] primitive mode: {}", num);
+ return None;
+ },
+ })
+ } else {
+ Some(match num {
+ 4 => Mode::Insert,
+ 20 => Mode::LineFeedNewLine,
+ _ => return None,
+ })
+ }
+ }
+}
+
+/// Mode for clearing line
+///
+/// Relative to cursor
+#[derive(Debug)]
+pub enum LineClearMode {
+ /// Clear right of cursor
+ Right,
+ /// Clear left of cursor
+ Left,
+ /// Clear entire line
+ All,
+}
+
+/// Mode for clearing terminal
+///
+/// Relative to cursor
+#[derive(Debug)]
+pub enum ClearMode {
+ /// Clear below cursor
+ Below,
+ /// Clear above cursor
+ Above,
+ /// Clear entire terminal
+ All,
+ /// Clear 'saved' lines (scrollback)
+ Saved,
+}
+
+/// Mode for clearing tab stops
+#[derive(Debug)]
+pub enum TabulationClearMode {
+ /// Clear stop under cursor
+ Current,
+ /// Clear all stops
+ All,
+}
+
+/// Standard colors
+///
+/// The order here matters since the enum should be castable to a `usize` for
+/// indexing a color list.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
+pub enum NamedColor {
+ /// Black
+ Black = 0,
+ /// Red
+ Red,
+ /// Green
+ Green,
+ /// Yellow
+ Yellow,
+ /// Blue
+ Blue,
+ /// Magenta
+ Magenta,
+ /// Cyan
+ Cyan,
+ /// White
+ White,
+ /// Bright black
+ BrightBlack,
+ /// Bright red
+ BrightRed,
+ /// Bright green
+ BrightGreen,
+ /// Bright yellow
+ BrightYellow,
+ /// Bright blue
+ BrightBlue,
+ /// Bright magenta
+ BrightMagenta,
+ /// Bright cyan
+ BrightCyan,
+ /// Bright white
+ BrightWhite,
+ /// The foreground color
+ Foreground = 256,
+ /// The background color
+ Background,
+ /// Color for the text under the cursor
+ CursorText,
+ /// Color for the cursor itself
+ Cursor,
+ /// Dim black
+ DimBlack,
+ /// Dim red
+ DimRed,
+ /// Dim green
+ DimGreen,
+ /// Dim yellow
+ DimYellow,
+ /// Dim blue
+ DimBlue,
+ /// Dim magenta
+ DimMagenta,
+ /// Dim cyan
+ DimCyan,
+ /// Dim white
+ DimWhite,
+ /// The bright foreground color
+ BrightForeground,
+ /// Dim foreground
+ DimForeground,
+}
+
+impl NamedColor {
+ pub fn to_bright(self) -> Self {
+ match self {
+ NamedColor::Foreground => NamedColor::BrightForeground,
+ NamedColor::Black => NamedColor::BrightBlack,
+ NamedColor::Red => NamedColor::BrightRed,
+ NamedColor::Green => NamedColor::BrightGreen,
+ NamedColor::Yellow => NamedColor::BrightYellow,
+ NamedColor::Blue => NamedColor::BrightBlue,
+ NamedColor::Magenta => NamedColor::BrightMagenta,
+ NamedColor::Cyan => NamedColor::BrightCyan,
+ NamedColor::White => NamedColor::BrightWhite,
+ NamedColor::DimForeground => NamedColor::Foreground,
+ NamedColor::DimBlack => NamedColor::Black,
+ NamedColor::DimRed => NamedColor::Red,
+ NamedColor::DimGreen => NamedColor::Green,
+ NamedColor::DimYellow => NamedColor::Yellow,
+ NamedColor::DimBlue => NamedColor::Blue,
+ NamedColor::DimMagenta => NamedColor::Magenta,
+ NamedColor::DimCyan => NamedColor::Cyan,
+ NamedColor::DimWhite => NamedColor::White,
+ val => val,
+ }
+ }
+
+ pub fn to_dim(self) -> Self {
+ match self {
+ NamedColor::Black => NamedColor::DimBlack,
+ NamedColor::Red => NamedColor::DimRed,
+ NamedColor::Green => NamedColor::DimGreen,
+ NamedColor::Yellow => NamedColor::DimYellow,
+ NamedColor::Blue => NamedColor::DimBlue,
+ NamedColor::Magenta => NamedColor::DimMagenta,
+ NamedColor::Cyan => NamedColor::DimCyan,
+ NamedColor::White => NamedColor::DimWhite,
+ NamedColor::Foreground => NamedColor::DimForeground,
+ NamedColor::BrightBlack => NamedColor::Black,
+ NamedColor::BrightRed => NamedColor::Red,
+ NamedColor::BrightGreen => NamedColor::Green,
+ NamedColor::BrightYellow => NamedColor::Yellow,
+ NamedColor::BrightBlue => NamedColor::Blue,
+ NamedColor::BrightMagenta => NamedColor::Magenta,
+ NamedColor::BrightCyan => NamedColor::Cyan,
+ NamedColor::BrightWhite => NamedColor::White,
+ NamedColor::BrightForeground => NamedColor::Foreground,
+ val => val,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Color {
+ Named(NamedColor),
+ Spec(Rgb),
+ Indexed(u8),
+}
+
+/// Terminal character attributes
+#[derive(Debug, Eq, PartialEq)]
+pub enum Attr {
+ /// Clear all special abilities
+ Reset,
+ /// Bold text
+ Bold,
+ /// Dim or secondary color
+ Dim,
+ /// Italic text
+ Italic,
+ /// Underscore text
+ Underscore,
+ /// Blink cursor slowly
+ BlinkSlow,
+ /// Blink cursor fast
+ BlinkFast,
+ /// Invert colors
+ Reverse,
+ /// Do not display characters
+ Hidden,
+ /// Strikeout text
+ Strike,
+ /// Cancel bold
+ CancelBold,
+ /// Cancel bold and dim
+ CancelBoldDim,
+ /// Cancel italic
+ CancelItalic,
+ /// Cancel underline
+ CancelUnderline,
+ /// Cancel blink
+ CancelBlink,
+ /// Cancel inversion
+ CancelReverse,
+ /// Cancel text hiding
+ CancelHidden,
+ /// Cancel strikeout
+ CancelStrike,
+ /// Set indexed foreground color
+ Foreground(Color),
+ /// Set indexed background color
+ Background(Color),
+}
+
+/// Identifiers which can be assigned to a graphic character set
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum CharsetIndex {
+ /// Default set, is designated as ASCII at startup
+ G0,
+ G1,
+ G2,
+ G3,
+}
+
+impl Default for CharsetIndex {
+ fn default() -> Self {
+ CharsetIndex::G0
+ }
+}
+
+/// Standard or common character sets which can be designated as G0-G3
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum StandardCharset {
+ Ascii,
+ SpecialCharacterAndLineDrawing,
+}
+
+impl Default for StandardCharset {
+ fn default() -> Self {
+ StandardCharset::Ascii
+ }
+}
+
+impl<'a, H, W> vte::Perform for Performer<'a, H, W>
+where
+ H: Handler + TermInfo + 'a,
+ W: io::Write + 'a,
+{
+ #[inline]
+ fn print(&mut self, c: char) {
+ self.handler.input(c);
+ self._state.preceding_char = Some(c);
+ }
+
+ #[inline]
+ fn execute(&mut self, byte: u8) {
+ match byte {
+ C0::HT => self.handler.put_tab(1),
+ C0::BS => self.handler.backspace(),
+ C0::CR => self.handler.carriage_return(),
+ C0::LF | C0::VT | C0::FF => self.handler.linefeed(),
+ C0::BEL => self.handler.bell(),
+ C0::SUB => self.handler.substitute(),
+ C0::SI => self.handler.set_active_charset(CharsetIndex::G0),
+ C0::SO => self.handler.set_active_charset(CharsetIndex::G1),
+ C1::NEL => self.handler.newline(),
+ C1::HTS => self.handler.set_horizontal_tabstop(),
+ C1::DECID => self.handler.identify_terminal(self.writer),
+ _ => debug!("[unhandled] execute byte={:02x}", byte),
+ }
+ }
+
+ #[inline]
+ fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) {
+ debug!(
+ "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}",
+ params, intermediates, ignore
+ );
+ }
+
+ #[inline]
+ fn put(&mut self, byte: u8) {
+ debug!("[unhandled put] byte={:?}", byte);
+ }
+
+ #[inline]
+ fn unhook(&mut self) {
+ debug!("[unhandled unhook]");
+ }
+
+ // TODO replace OSC parsing with parser combinators
+ #[inline]
+ fn osc_dispatch(&mut self, params: &[&[u8]]) {
+ fn unhandled(params: &[&[u8]]) {
+ let mut buf = String::new();
+ for items in params {
+ buf.push_str("[");
+ for item in *items {
+ buf.push_str(&format!("{:?},", *item as char));
+ }
+ buf.push_str("],");
+ }
+ debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!());
+ }
+
+ if params.is_empty() || params[0].is_empty() {
+ return;
+ }
+
+ match params[0] {
+ // Set window title
+ b"0" | b"2" => {
+ if params.len() >= 2 {
+ if let Ok(utf8_title) = str::from_utf8(params[1]) {
+ self.handler.set_title(utf8_title);
+ return;
+ }
+ }
+ unhandled(params);
+ },
+
+ // Set icon name
+ // This is ignored, since alacritty has no concept of tabs
+ b"1" => return,
+
+ // Set color index
+ b"4" => {
+ if params.len() > 1 && params.len() % 2 != 0 {
+ for chunk in params[1..].chunks(2) {
+ let index = parse_number(chunk[0]);
+ let color = parse_rgb_color(chunk[1]);
+ if let (Some(i), Some(c)) = (index, color) {
+ self.handler.set_color(i as usize, c);
+ return;
+ }
+ }
+ }
+ unhandled(params);
+ },
+
+ // Set foreground color
+ b"10" => {
+ if params.len() >= 2 {
+ if let Some(color) = parse_rgb_color(params[1]) {
+ self.handler.set_color(NamedColor::Foreground as usize, color);
+ return;
+ }
+ }
+ unhandled(params);
+ },
+
+ // Set background color
+ b"11" => {
+ if params.len() >= 2 {
+ if let Some(color) = parse_rgb_color(params[1]) {
+ self.handler.set_color(NamedColor::Background as usize, color);
+ return;
+ }
+ }
+ unhandled(params);
+ },
+
+ // Set text cursor color
+ b"12" => {
+ if params.len() >= 2 {
+ if let Some(color) = parse_rgb_color(params[1]) {
+ self.handler.set_color(NamedColor::Cursor as usize, color);
+ return;
+ }
+ }
+ unhandled(params);
+ },
+
+ // Set cursor style
+ b"50" => {
+ if params.len() >= 2
+ && params[1].len() >= 13
+ && params[1][0..12] == *b"CursorShape="
+ {
+ let style = match params[1][12] as char {
+ '0' => CursorStyle::Block,
+ '1' => CursorStyle::Beam,
+ '2' => CursorStyle::Underline,
+ _ => return unhandled(params),
+ };
+ self.handler.set_cursor_style(Some(style));
+ return;
+ }
+ unhandled(params);
+ },
+
+ // Set clipboard
+ b"52" => {
+ if params.len() < 3 {
+ return unhandled(params);
+ }
+
+ match params[2] {
+ b"?" => unhandled(params),
+ selection => {
+ if let Ok(string) = base64::decode(selection) {
+ if let Ok(utf8_string) = str::from_utf8(&string) {
+ self.handler.set_clipboard(utf8_string);
+ }
+ }
+ },
+ }
+ },
+
+ // Reset color index
+ b"104" => {
+ // Reset all color indexes when no parameters are given
+ if params.len() == 1 {
+ for i in 0..256 {
+ self.handler.reset_color(i);
+ }
+ return;
+ }
+
+ // Reset color indexes given as parameters
+ for param in &params[1..] {
+ match parse_number(param) {
+ Some(index) => self.handler.reset_color(index as usize),
+ None => unhandled(params),
+ }
+ }
+ },
+
+ // Reset foreground color
+ b"110" => self.handler.reset_color(NamedColor::Foreground as usize),
+
+ // Reset background color
+ b"111" => self.handler.reset_color(NamedColor::Background as usize),
+
+ // Reset text cursor color
+ b"112" => self.handler.reset_color(NamedColor::Cursor as usize),
+
+ _ => unhandled(params),
+ }
+ }
+
+ #[inline]
+ fn csi_dispatch(&mut self, args: &[i64], intermediates: &[u8], _ignore: bool, action: char) {
+ let private = intermediates.get(0).map(|b| *b == b'?').unwrap_or(false);
+ let handler = &mut self.handler;
+ let writer = &mut self.writer;
+
+ macro_rules! unhandled {
+ () => {{
+ debug!(
+ "[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}",
+ action, args, intermediates
+ );
+ return;
+ }};
+ }
+
+ macro_rules! arg_or_default {
+ (idx: $idx:expr, default: $default:expr) => {
+ args.get($idx)
+ .and_then(|v| if *v == 0 { None } else { Some(*v) })
+ .unwrap_or($default)
+ };
+ }
+
+ match action {
+ '@' => handler.insert_blank(Column(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'A' => {
+ handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize));
+ },
+ 'b' => {
+ if let Some(c) = self._state.preceding_char {
+ for _ in 0..arg_or_default!(idx: 0, default: 1) {
+ handler.input(c);
+ }
+ } else {
+ debug!("tried to repeat with no preceding char");
+ }
+ },
+ 'B' | 'e' => handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'c' => handler.identify_terminal(writer),
+ 'C' | 'a' => handler.move_forward(Column(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'D' => handler.move_backward(Column(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'E' => handler.move_down_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'F' => handler.move_up_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'g' => {
+ let mode = match arg_or_default!(idx: 0, default: 0) {
+ 0 => TabulationClearMode::Current,
+ 3 => TabulationClearMode::All,
+ _ => unhandled!(),
+ };
+
+ handler.clear_tabs(mode);
+ },
+ 'G' | '`' => handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1)),
+ 'H' | 'f' => {
+ let y = arg_or_default!(idx: 0, default: 1) as usize;
+ let x = arg_or_default!(idx: 1, default: 1) as usize;
+ handler.goto(Line(y - 1), Column(x - 1));
+ },
+ 'I' => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)),
+ 'J' => {
+ let mode = match arg_or_default!(idx: 0, default: 0) {
+ 0 => ClearMode::Below,
+ 1 => ClearMode::Above,
+ 2 => ClearMode::All,
+ 3 => ClearMode::Saved,
+ _ => unhandled!(),
+ };
+
+ handler.clear_screen(mode);
+ },
+ 'K' => {
+ let mode = match arg_or_default!(idx: 0, default: 0) {
+ 0 => LineClearMode::Right,
+ 1 => LineClearMode::Left,
+ 2 => LineClearMode::All,
+ _ => unhandled!(),
+ };
+
+ handler.clear_line(mode);
+ },
+ 'S' => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'T' => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'L' => handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'l' => {
+ for arg in args {
+ let mode = Mode::from_primitive(private, *arg);
+ match mode {
+ Some(mode) => handler.unset_mode(mode),
+ None => unhandled!(),
+ }
+ }
+ },
+ 'M' => handler.delete_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'X' => handler.erase_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'P' => handler.delete_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)),
+ 'Z' => handler.move_backward_tabs(arg_or_default!(idx: 0, default: 1)),
+ 'd' => handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1)),
+ 'h' => {
+ for arg in args {
+ let mode = Mode::from_primitive(private, *arg);
+ match mode {
+ Some(mode) => handler.set_mode(mode),
+ None => unhandled!(),
+ }
+ }
+ },
+ 'm' => {
+ // Sometimes a C-style for loop is just what you need
+ let mut i = 0; // C-for initializer
+ if args.is_empty() {
+ handler.terminal_attribute(Attr::Reset);
+ return;
+ }
+ loop {
+ if i >= args.len() {
+ // C-for condition
+ break;
+ }
+
+ let attr = match args[i] {
+ 0 => Attr::Reset,
+ 1 => Attr::Bold,
+ 2 => Attr::Dim,
+ 3 => Attr::Italic,
+ 4 => Attr::Underscore,
+ 5 => Attr::BlinkSlow,
+ 6 => Attr::BlinkFast,
+ 7 => Attr::Reverse,
+ 8 => Attr::Hidden,
+ 9 => Attr::Strike,
+ 21 => Attr::CancelBold,
+ 22 => Attr::CancelBoldDim,
+ 23 => Attr::CancelItalic,
+ 24 => Attr::CancelUnderline,
+ 25 => Attr::CancelBlink,
+ 27 => Attr::CancelReverse,
+ 28 => Attr::CancelHidden,
+ 29 => Attr::CancelStrike,
+ 30 => Attr::Foreground(Color::Named(NamedColor::Black)),
+ 31 => Attr::Foreground(Color::Named(NamedColor::Red)),
+ 32 => Attr::Foreground(Color::Named(NamedColor::Green)),
+ 33 => Attr::Foreground(Color::Named(NamedColor::Yellow)),
+ 34 => Attr::Foreground(Color::Named(NamedColor::Blue)),
+ 35 => Attr::Foreground(Color::Named(NamedColor::Magenta)),
+ 36 => Attr::Foreground(Color::Named(NamedColor::Cyan)),
+ 37 => Attr::Foreground(Color::Named(NamedColor::White)),
+ 38 => {
+ let mut start = 0;
+ if let Some(color) = parse_color(&args[i..], &mut start) {
+ i += start;
+ Attr::Foreground(color)
+ } else {
+ break;
+ }
+ },
+ 39 => Attr::Foreground(Color::Named(NamedColor::Foreground)),
+ 40 => Attr::Background(Color::Named(NamedColor::Black)),
+ 41 => Attr::Background(Color::Named(NamedColor::Red)),
+ 42 => Attr::Background(Color::Named(NamedColor::Green)),
+ 43 => Attr::Background(Color::Named(NamedColor::Yellow)),
+ 44 => Attr::Background(Color::Named(NamedColor::Blue)),
+ 45 => Attr::Background(Color::Named(NamedColor::Magenta)),
+ 46 => Attr::Background(Color::Named(NamedColor::Cyan)),
+ 47 => Attr::Background(Color::Named(NamedColor::White)),
+ 48 => {
+ let mut start = 0;
+ if let Some(color) = parse_color(&args[i..], &mut start) {
+ i += start;
+ Attr::Background(color)
+ } else {
+ break;
+ }
+ },
+ 49 => Attr::Background(Color::Named(NamedColor::Background)),
+ 90 => Attr::Foreground(Color::Named(NamedColor::BrightBlack)),
+ 91 => Attr::Foreground(Color::Named(NamedColor::BrightRed)),
+ 92 => Attr::Foreground(Color::Named(NamedColor::BrightGreen)),
+ 93 => Attr::Foreground(Color::Named(NamedColor::BrightYellow)),
+ 94 => Attr::Foreground(Color::Named(NamedColor::BrightBlue)),
+ 95 => Attr::Foreground(Color::Named(NamedColor::BrightMagenta)),
+ 96 => Attr::Foreground(Color::Named(NamedColor::BrightCyan)),
+ 97 => Attr::Foreground(Color::Named(NamedColor::BrightWhite)),
+ 100 => Attr::Background(Color::Named(NamedColor::BrightBlack)),
+ 101 => Attr::Background(Color::Named(NamedColor::BrightRed)),
+ 102 => Attr::Background(Color::Named(NamedColor::BrightGreen)),
+ 103 => Attr::Background(Color::Named(NamedColor::BrightYellow)),
+ 104 => Attr::Background(Color::Named(NamedColor::BrightBlue)),
+ 105 => Attr::Background(Color::Named(NamedColor::BrightMagenta)),
+ 106 => Attr::Background(Color::Named(NamedColor::BrightCyan)),
+ 107 => Attr::Background(Color::Named(NamedColor::BrightWhite)),
+ _ => unhandled!(),
+ };
+
+ handler.terminal_attribute(attr);
+
+ i += 1; // C-for expr
+ }
+ },
+ 'n' => handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize),
+ 'r' => {
+ if private {
+ unhandled!();
+ }
+ let arg0 = arg_or_default!(idx: 0, default: 1) as usize;
+ let top = Line(arg0 - 1);
+ // Bottom should be included in the range, but range end is not
+ // usually included. One option would be to use an inclusive
+ // range, but instead we just let the open range end be 1
+ // higher.
+ let arg1 = arg_or_default!(idx: 1, default: handler.lines().0 as _) as usize;
+ let bottom = Line(arg1);
+
+ handler.set_scrolling_region(top..bottom);
+ },
+ 's' => handler.save_cursor_position(),
+ 'u' => handler.restore_cursor_position(),
+ 'q' => {
+ let style = match arg_or_default!(idx: 0, default: 0) {
+ 0 => None,
+ 1 | 2 => Some(CursorStyle::Block),
+ 3 | 4 => Some(CursorStyle::Underline),
+ 5 | 6 => Some(CursorStyle::Beam),
+ _ => unhandled!(),
+ };
+
+ handler.set_cursor_style(style);
+ },
+ _ => unhandled!(),
+ }
+ }
+
+ #[inline]
+ fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], _ignore: bool, byte: u8) {
+ macro_rules! unhandled {
+ () => {{
+ debug!(
+ "[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
+ params, intermediates, byte as char, byte
+ );
+ return;
+ }};
+ }
+
+ macro_rules! configure_charset {
+ ($charset:path) => {{
+ let index: CharsetIndex = match intermediates.first().cloned() {
+ Some(b'(') => CharsetIndex::G0,
+ Some(b')') => CharsetIndex::G1,
+ Some(b'*') => CharsetIndex::G2,
+ Some(b'+') => CharsetIndex::G3,
+ _ => unhandled!(),
+ };
+ self.handler.configure_charset(index, $charset)
+ }};
+ }
+
+ match byte {
+ b'B' => configure_charset!(StandardCharset::Ascii),
+ b'D' => self.handler.linefeed(),
+ b'E' => {
+ self.handler.linefeed();
+ self.handler.carriage_return();
+ },
+ b'H' => self.handler.set_horizontal_tabstop(),
+ b'M' => self.handler.reverse_index(),
+ b'Z' => self.handler.identify_terminal(self.writer),
+ b'c' => self.handler.reset_state(),
+ b'0' => configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing),
+ b'7' => self.handler.save_cursor_position(),
+ b'8' => {
+ if !intermediates.is_empty() && intermediates[0] == b'#' {
+ self.handler.dectest();
+ } else {
+ self.handler.restore_cursor_position();
+ }
+ },
+ b'=' => self.handler.set_keypad_application_mode(),
+ b'>' => self.handler.unset_keypad_application_mode(),
+ b'\\' => (), // String terminator, do nothing (parser handles as string terminator)
+ _ => unhandled!(),
+ }
+ }
+}
+
+/// Parse a color specifier from list of attributes
+fn parse_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
+ if attrs.len() < 2 {
+ return None;
+ }
+
+ match attrs[*i + 1] {
+ 2 => {
+ // RGB color spec
+ if attrs.len() < 5 {
+ debug!("Expected RGB color spec; got {:?}", attrs);
+ return None;
+ }
+
+ let r = attrs[*i + 2];
+ let g = attrs[*i + 3];
+ let b = attrs[*i + 4];
+
+ *i += 4;
+
+ let range = 0..256;
+ if !range.contains_(r) || !range.contains_(g) || !range.contains_(b) {
+ debug!("Invalid RGB color spec: ({}, {}, {})", r, g, b);
+ return None;
+ }
+
+ Some(Color::Spec(Rgb { r: r as u8, g: g as u8, b: b as u8 }))
+ },
+ 5 => {
+ if attrs.len() < 3 {
+ debug!("Expected color index; got {:?}", attrs);
+ None
+ } else {
+ *i += 2;
+ let idx = attrs[*i];
+ match idx {
+ 0..=255 => Some(Color::Indexed(idx as u8)),
+ _ => {
+ debug!("Invalid color index: {}", idx);
+ None
+ },
+ }
+ }
+ },
+ _ => {
+ debug!("Unexpected color attr: {}", attrs[*i + 1]);
+ None
+ },
+ }
+}
+
+/// C0 set of 7-bit control characters (from ANSI X3.4-1977).
+#[allow(non_snake_case)]
+pub mod C0 {
+ /// Null filler, terminal should ignore this character
+ pub const NUL: u8 = 0x00;
+ /// Start of Header
+ pub const SOH: u8 = 0x01;
+ /// Start of Text, implied end of header
+ pub const STX: u8 = 0x02;
+ /// End of Text, causes some terminal to respond with ACK or NAK
+ pub const ETX: u8 = 0x03;
+ /// End of Transmission
+ pub const EOT: u8 = 0x04;
+ /// Enquiry, causes terminal to send ANSWER-BACK ID
+ pub const ENQ: u8 = 0x05;
+ /// Acknowledge, usually sent by terminal in response to ETX
+ pub const ACK: u8 = 0x06;
+ /// Bell, triggers the bell, buzzer, or beeper on the terminal
+ pub const BEL: u8 = 0x07;
+ /// Backspace, can be used to define overstruck characters
+ pub const BS: u8 = 0x08;
+ /// Horizontal Tabulation, move to next predetermined position
+ pub const HT: u8 = 0x09;
+ /// Linefeed, move to same position on next line (see also NL)
+ pub const LF: u8 = 0x0A;
+ /// Vertical Tabulation, move to next predetermined line
+ pub const VT: u8 = 0x0B;
+ /// Form Feed, move to next form or page
+ pub const FF: u8 = 0x0C;
+ /// Carriage Return, move to first character of current line
+ pub const CR: u8 = 0x0D;
+ /// Shift Out, switch to G1 (other half of character set)
+ pub const SO: u8 = 0x0E;
+ /// Shift In, switch to G0 (normal half of character set)
+ pub const SI: u8 = 0x0F;
+ /// Data Link Escape, interpret next control character specially
+ pub const DLE: u8 = 0x10;
+ /// (DC1) Terminal is allowed to resume transmitting
+ pub const XON: u8 = 0x11;
+ /// Device Control 2, causes ASR-33 to activate paper-tape reader
+ pub const DC2: u8 = 0x12;
+ /// (DC2) Terminal must pause and refrain from transmitting
+ pub const XOFF: u8 = 0x13;
+ /// Device Control 4, causes ASR-33 to deactivate paper-tape reader
+ pub const DC4: u8 = 0x14;
+ /// Negative Acknowledge, used sometimes with ETX and ACK
+ pub const NAK: u8 = 0x15;
+ /// Synchronous Idle, used to maintain timing in Sync communication
+ pub const SYN: u8 = 0x16;
+ /// End of Transmission block
+ pub const ETB: u8 = 0x17;
+ /// Cancel (makes VT100 abort current escape sequence if any)
+ pub const CAN: u8 = 0x18;
+ /// End of Medium
+ pub const EM: u8 = 0x19;
+ /// Substitute (VT100 uses this to display parity errors)
+ pub const SUB: u8 = 0x1A;
+ /// Prefix to an escape sequence
+ pub const ESC: u8 = 0x1B;
+ /// File Separator
+ pub const FS: u8 = 0x1C;
+ /// Group Separator
+ pub const GS: u8 = 0x1D;
+ /// Record Separator (sent by VT132 in block-transfer mode)
+ pub const RS: u8 = 0x1E;
+ /// Unit Separator
+ pub const US: u8 = 0x1F;
+ /// Delete, should be ignored by terminal
+ pub const DEL: u8 = 0x7f;
+}
+
+/// C1 set of 8-bit control characters (from ANSI X3.64-1979)
+///
+/// 0x80 (@), 0x81 (A), 0x82 (B), 0x83 (C) are reserved
+/// 0x98 (X), 0x99 (Y) are reserved
+/// 0x9a (Z) is 'reserved', but causes DEC terminals to respond with DA codes
+#[allow(non_snake_case)]
+pub mod C1 {
+ /// Reserved
+ pub const PAD: u8 = 0x80;
+ /// Reserved
+ pub const HOP: u8 = 0x81;
+ /// Reserved
+ pub const BPH: u8 = 0x82;
+ /// Reserved
+ pub const NBH: u8 = 0x83;
+ /// Index, moves down one line same column regardless of NL
+ pub const IND: u8 = 0x84;
+ /// New line, moves done one line and to first column (CR+LF)
+ pub const NEL: u8 = 0x85;
+ /// Start of Selected Area to be sent to auxiliary output device
+ pub const SSA: u8 = 0x86;
+ /// End of Selected Area to be sent to auxiliary output device
+ pub const ESA: u8 = 0x87;
+ /// Horizontal Tabulation Set at current position
+ pub const HTS: u8 = 0x88;
+ /// Hor Tab Justify, moves string to next tab position
+ pub const HTJ: u8 = 0x89;
+ /// Vertical Tabulation Set at current line
+ pub const VTS: u8 = 0x8A;
+ /// Partial Line Down (subscript)
+ pub const PLD: u8 = 0x8B;
+ /// Partial Line Up (superscript)
+ pub const PLU: u8 = 0x8C;
+ /// Reverse Index, go up one line, reverse scroll if necessary
+ pub const RI: u8 = 0x8D;
+ /// Single Shift to G2
+ pub const SS2: u8 = 0x8E;
+ /// Single Shift to G3 (VT100 uses this for sending PF keys)
+ pub const SS3: u8 = 0x8F;
+ /// Device Control String, terminated by ST (VT125 enters graphics)
+ pub const DCS: u8 = 0x90;
+ /// Private Use 1
+ pub const PU1: u8 = 0x91;
+ /// Private Use 2
+ pub const PU2: u8 = 0x92;
+ /// Set Transmit State
+ pub const STS: u8 = 0x93;
+ /// Cancel character, ignore previous character
+ pub const CCH: u8 = 0x94;
+ /// Message Waiting, turns on an indicator on the terminal
+ pub const MW: u8 = 0x95;
+ /// Start of Protected Area
+ pub const SPA: u8 = 0x96;
+ /// End of Protected Area
+ pub const EPA: u8 = 0x97;
+ /// SOS
+ pub const SOS: u8 = 0x98;
+ /// SGCI
+ pub const SGCI: u8 = 0x99;
+ /// DECID - Identify Terminal
+ pub const DECID: u8 = 0x9a;
+ /// Control Sequence Introducer
+ pub const CSI: u8 = 0x9B;
+ /// String Terminator (VT125 exits graphics)
+ pub const ST: u8 = 0x9C;
+ /// Operating System Command (reprograms intelligent terminal)
+ pub const OSC: u8 = 0x9D;
+ /// Privacy Message (password verification), terminated by ST
+ pub const PM: u8 = 0x9E;
+ /// Application Program Command (to word processor), term by ST
+ pub const APC: u8 = 0x9F;
+}
+
+// Tests for parsing escape sequences
+//
+// Byte sequences used in these tests are recording of pty stdout.
+#[cfg(test)]
+mod tests {
+ use super::{
+ parse_number, parse_rgb_color, Attr, CharsetIndex, Color, Handler, Processor,
+ StandardCharset, TermInfo,
+ };
+ use crate::index::{Column, Line};
+ use crate::term::color::Rgb;
+ use std::io;
+
+ /// The /dev/null of `io::Write`
+ struct Void;
+
+ impl io::Write for Void {
+ fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
+ Ok(bytes.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+
+ #[derive(Default)]
+ struct AttrHandler {
+ attr: Option<Attr>,
+ }
+
+ impl Handler for AttrHandler {
+ fn terminal_attribute(&mut self, attr: Attr) {
+ self.attr = Some(attr);
+ }
+ }
+
+ impl TermInfo for AttrHandler {
+ fn lines(&self) -> Line {
+ Line(24)
+ }
+
+ fn cols(&self) -> Column {
+ Column(80)
+ }
+ }
+
+ #[test]
+ fn parse_control_attribute() {
+ static BYTES: &'static [u8] = &[0x1b, 0x5b, 0x31, 0x6d];
+
+ let mut parser = Processor::new();
+ let mut handler = AttrHandler::default();
+
+ for byte in &BYTES[..] {
+ parser.advance(&mut handler, *byte, &mut Void);
+ }
+
+ assert_eq!(handler.attr, Some(Attr::Bold));
+ }
+
+ #[test]
+ fn parse_truecolor_attr() {
+ static BYTES: &'static [u8] = &[
+ 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x32, 0x3b, 0x31, 0x32, 0x38, 0x3b, 0x36, 0x36, 0x3b,
+ 0x32, 0x35, 0x35, 0x6d,
+ ];
+
+ let mut parser = Processor::new();
+ let mut handler = AttrHandler::default();
+
+ for byte in &BYTES[..] {
+ parser.advance(&mut handler, *byte, &mut Void);
+ }
+
+ let spec = Rgb { r: 128, g: 66, b: 255 };
+
+ assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec))));
+ }
+
+ /// No exactly a test; useful for debugging
+ #[test]
+ fn parse_zsh_startup() {
+ static BYTES: &'static [u8] = &[
+ 0x1b, 0x5b, 0x31, 0x6d, 0x1b, 0x5b, 0x37, 0x6d, 0x25, 0x1b, 0x5b, 0x32, 0x37, 0x6d,
+ 0x1b, 0x5b, 0x31, 0x6d, 0x1b, 0x5b, 0x30, 0x6d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x0d, 0x20, 0x0d, 0x0d, 0x1b, 0x5b, 0x30, 0x6d, 0x1b, 0x5b, 0x32,
+ 0x37, 0x6d, 0x1b, 0x5b, 0x32, 0x34, 0x6d, 0x1b, 0x5b, 0x4a, 0x6a, 0x77, 0x69, 0x6c,
+ 0x6d, 0x40, 0x6a, 0x77, 0x69, 0x6c, 0x6d, 0x2d, 0x64, 0x65, 0x73, 0x6b, 0x20, 0x1b,
+ 0x5b, 0x30, 0x31, 0x3b, 0x33, 0x32, 0x6d, 0xe2, 0x9e, 0x9c, 0x20, 0x1b, 0x5b, 0x30,
+ 0x31, 0x3b, 0x33, 0x32, 0x6d, 0x20, 0x1b, 0x5b, 0x33, 0x36, 0x6d, 0x7e, 0x2f, 0x63,
+ 0x6f, 0x64, 0x65,
+ ];
+
+ let mut handler = AttrHandler::default();
+ let mut parser = Processor::new();
+
+ for byte in &BYTES[..] {
+ parser.advance(&mut handler, *byte, &mut Void);
+ }
+ }
+
+ struct CharsetHandler {
+ index: CharsetIndex,
+ charset: StandardCharset,
+ }
+
+ impl Default for CharsetHandler {
+ fn default() -> CharsetHandler {
+ CharsetHandler { index: CharsetIndex::G0, charset: StandardCharset::Ascii }
+ }
+ }
+
+ impl Handler for CharsetHandler {
+ fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
+ self.index = index;
+ self.charset = charset;
+ }
+
+ fn set_active_charset(&mut self, index: CharsetIndex) {
+ self.index = index;
+ }
+ }
+
+ impl TermInfo for CharsetHandler {
+ fn lines(&self) -> Line {
+ Line(200)
+ }
+
+ fn cols(&self) -> Column {
+ Column(90)
+ }
+ }
+
+ #[test]
+ fn parse_designate_g0_as_line_drawing() {
+ static BYTES: &'static [u8] = &[0x1b, b'(', b'0'];
+ let mut parser = Processor::new();
+ let mut handler = CharsetHandler::default();
+
+ for byte in &BYTES[..] {
+ parser.advance(&mut handler, *byte, &mut Void);
+ }
+
+ assert_eq!(handler.index, CharsetIndex::G0);
+ assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
+ }
+
+ #[test]
+ fn parse_designate_g1_as_line_drawing_and_invoke() {
+ static BYTES: &'static [u8] = &[0x1b, 0x29, 0x30, 0x0e];
+ let mut parser = Processor::new();
+ let mut handler = CharsetHandler::default();
+
+ for byte in &BYTES[..3] {
+ parser.advance(&mut handler, *byte, &mut Void);
+ }
+
+ assert_eq!(handler.index, CharsetIndex::G1);
+ assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
+
+ let mut handler = CharsetHandler::default();
+ parser.advance(&mut handler, BYTES[3], &mut Void);
+
+ assert_eq!(handler.index, CharsetIndex::G1);
+ }
+
+ #[test]
+ fn parse_valid_rgb_color() {
+ assert_eq!(parse_rgb_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
+ }
+
+ #[test]
+ fn parse_valid_rgb_color2() {
+ assert_eq!(parse_rgb_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
+ }
+
+ #[test]
+ fn parse_invalid_number() {
+ assert_eq!(parse_number(b"1abc"), None);
+ }
+
+ #[test]
+ fn parse_valid_number() {
+ assert_eq!(parse_number(b"123"), Some(123));
+ }
+
+ #[test]
+ fn parse_number_too_large() {
+ assert_eq!(parse_number(b"321"), None);
+ }
+}