diff options
author | Theodore Dubois <tblodt@icloud.com> | 2019-04-28 06:24:58 -0700 |
---|---|---|
committer | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-04-28 13:24:58 +0000 |
commit | dbd8538762ef8968a493e1bf996e8693479ca783 (patch) | |
tree | 32ac2a6a5e01238a272d4ba534551d2e42903c7a /src/ansi.rs | |
parent | 9c6d12ea2c863ba76015bdedc00db13b7307725a (diff) | |
download | r-alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.gz r-alacritty-dbd8538762ef8968a493e1bf996e8693479ca783.tar.bz2 r-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 'src/ansi.rs')
-rw-r--r-- | src/ansi.rs | 1568 |
1 files changed, 0 insertions, 1568 deletions
diff --git a/src/ansi.rs b/src/ansi.rs deleted file mode 100644 index c0ebb79c..00000000 --- a/src/ansi.rs +++ /dev/null @@ -1,1568 +0,0 @@ -// 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 ¶ms[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); - } -} |