diff options
author | Joe Wilm <joe@jwilm.com> | 2016-05-30 20:44:37 -0700 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-06-02 19:42:28 -0700 |
commit | 30ec14510935d46e7454863f9a4e63e53bf7728c (patch) | |
tree | 9501fe70ecf582e57903fbc061d3e6a0928f3f33 /src/ansi.rs | |
parent | 70b0423a31016798592fc0e96ce316cb3f1e9d46 (diff) | |
download | alacritty-30ec14510935d46e7454863f9a4e63e53bf7728c.tar.gz alacritty-30ec14510935d46e7454863f9a4e63e53bf7728c.zip |
Initial support for Terminal Emulation (woo!)
This patch introduces basic support for terminal emulation. Basic means
commands that don't use paging and are not full screen applications like
vim or tmux. Some paging applications are working properly, such as as
`git log`. Other pagers work reasonably well as long as the help menu is
not accessed.
There is now a central Rgb color type which is shared by the renderer,
terminal emulation, and the pty parser.
The parser no longer owns a Handler. Instead, a mutable reference to a
Handler is provided whenever advancing the parser. This resolved some
potential ownership issues (eg parser owning the `Term` type would've
been unworkable).
Diffstat (limited to 'src/ansi.rs')
-rw-r--r-- | src/ansi.rs | 233 |
1 files changed, 142 insertions, 91 deletions
diff --git a/src/ansi.rs b/src/ansi.rs index 23baa9ad..389ab94f 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -18,6 +18,7 @@ //! sequences only used by folks trapped in 1988. use std::io::{Cursor, Read, Write, Chars}; +use ::Rgb; /// A CSI Escape sequence #[derive(Debug, Eq, PartialEq)] @@ -59,7 +60,7 @@ pub enum Item { pub const CSI_ATTR_MAX: usize = 16; -pub struct Parser<H> { +pub struct Parser { /// Workspace for building a control sequence buf: [char; 1024], @@ -70,11 +71,24 @@ pub struct Parser<H> { /// Current state state: State, +} + +/// Terminal modes +#[derive(Debug, Eq, PartialEq)] +pub enum Mode { + SwapScreenAndSetRestoreCursor = 1049, +} - /// Handler +impl Mode { + /// Create mode from a primitive /// - /// Receives data from the parser - pub handler: H, + /// TODO lots of unhandled values.. + pub fn from_primitive(num: i64) -> Option<Mode> { + Some(match num { + 1049 => Mode::SwapScreenAndSetRestoreCursor, + _ => return None + }) + } } /// Mode for clearing line @@ -152,18 +166,6 @@ pub enum Color { BrightWhite, } -/// 16-million color specifier -/// TODO -#[derive(Debug, Eq, PartialEq)] -pub struct ColorSpec { - /// Red - pub r: u8, - /// Green - pub g: u8, - /// blue - pub b: u8, -} - /// Terminal character attributes #[derive(Debug, Eq, PartialEq)] pub enum Attr { @@ -204,11 +206,11 @@ pub enum Attr { /// Set indexed foreground color Foreground(Color), /// Set specific foreground color - ForegroundSpec(ColorSpec), + ForegroundSpec(Rgb), /// Set indexed background color Background(Color), /// Set specific background color - BackgroundSpec(ColorSpec), + BackgroundSpec(Rgb), /// Set default foreground DefaultForeground, /// Set default background @@ -336,6 +338,12 @@ pub trait Handler { /// set a terminal attribute fn terminal_attribute(&mut self, attr: Attr) {} + + /// Set mode + fn set_mode(&mut self, Mode) {} + + /// Unset mode + fn unset_mode(&mut self, Mode) {} } /// An implementation of handler that just prints everything it gets @@ -378,55 +386,62 @@ impl Handler for DebugHandler { fn reset_state(&mut self) { println!("reset_state"); } fn reverse_index(&mut self) { println!("reverse_index"); } fn terminal_attribute(&mut self, attr: Attr) { println!("terminal_attribute: {:?}", attr); } + fn set_mode(&mut self, mode: Mode) { println!("set_mode: {:?}", mode); } + fn unset_mode(&mut self, mode: Mode) { println!("unset_mode: {:?}", mode); } } -impl<H: Handler> Parser<H> { - pub fn new(handler: H) -> Parser<H> { +impl Parser { + pub fn new() -> Parser { Parser { buf: [0 as char; 1024], idx: 0, state: Default::default(), - handler: handler, } } /// Advance the state machine. /// /// Maybe returns an Item which represents a state change of the terminal - pub fn advance(&mut self, c: char) { + pub fn advance<H>(&mut self, handler: &mut H, c: char) + where H: Handler + { // println!("state: {:?}; char: {:?}", self.state, c); // Control characters get handled immediately if is_control(c) { - self.control(c); + self.control(handler, c); return; } match self.state { State::Base => { - self.advance_base(c); + self.advance_base(handler, c); }, State::Escape => { - self.escape(c); + self.escape(handler, c); }, State::Csi => { - self.csi(c); + self.csi(handler, c); } } } - fn advance_base(&mut self, c: char) { - self.handler.input(c); + fn advance_base<H>(&mut self, handler: &mut H, c: char) + where H: Handler + { + handler.input(c); } /// Handle character following an ESC /// /// TODO Handle `ST`, `'#'`, `'P'`, `'_'`, `'^'`, `']'`, `'k'`, /// 'n', 'o', '(', ')', '*', '+', '=', '>' - fn escape(&mut self, c: char) { + fn escape<H>(&mut self, handler: &mut H, c: char) + where H: Handler + { // Helper for items which complete a sequence. macro_rules! sequence_complete { ($fun:ident) => {{ - self.handler.$fun(); + handler.$fun(); self.state = State::Base; }} } @@ -444,30 +459,38 @@ impl<H: Handler> Parser<H> { '7' => sequence_complete!(save_cursor_position), '8' => sequence_complete!(restore_cursor_position), _ => { + self.state = State::Base; err_println!("Unknown ESC 0x{:02x} {:?}", c as usize, c); } } } - fn csi(&mut self, c: char) { + fn csi<H>(&mut self, handler: &mut H, c: char) + where H: Handler + { self.buf[self.idx] = c; self.idx += 1; if (self.idx == self.buf.len()) || is_csi_terminator(c) { - self.csi_parse(); + self.csi_parse(handler); } } /// Parse current CSI escape buffer /// /// ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ - fn csi_parse(&mut self) { + fn csi_parse<H>(&mut self, handler: &mut H) + where H: Handler + { let mut idx = 0; let mut args = [0i64; CSI_ATTR_MAX]; let mut args_idx = 0; // Get a slice which is the used subset of self.buf let mut raw = &self.buf[..self.idx]; + if raw[0] == '?' { + raw = &raw[1..]; + } // Parse args while !raw.is_empty() { @@ -500,26 +523,46 @@ impl<H: Handler> Parser<H> { }} } + macro_rules! unhandled { + () => {{ + err_println!("Recognized, but unhandled CSI: {:?}", &self.buf[..self.idx]); + self.state = State::Base; + return; + }} + } + macro_rules! arg_or_default { ($arg:expr, $default:expr) => { if $arg == 0 { $default } else { $arg } } } + macro_rules! debug_csi { + () => { + err_println!("CSI: {:?}", &self.buf[..self.idx]); + } + } + if raw.is_empty() { println!("raw is empty"); unknown!(); } match raw[0] { - '@' => self.handler.insert_blank(arg_or_default!(args[0], 1)), - 'A' => self.handler.move_up(arg_or_default!(args[0], 1)), - 'B' | 'e' => self.handler.move_down(arg_or_default!(args[0], 1)), - 'c' => self.handler.identify_terminal(), - 'C' | 'a' => self.handler.move_forward(arg_or_default!(args[0], 1)), - 'D' => self.handler.move_backward(arg_or_default!(args[0], 1)), - 'E' => self.handler.move_down_and_cr(arg_or_default!(args[0], 1)), - 'F' => self.handler.move_up_and_cr(arg_or_default!(args[0], 1)), + '@' => handler.insert_blank(arg_or_default!(args[0], 1)), + 'A' => { + debug_csi!(); + handler.move_up(arg_or_default!(args[0], 1)); + }, + 'B' | 'e' => handler.move_down(arg_or_default!(args[0], 1)), + 'c' => handler.identify_terminal(), + 'C' | 'a' => { + debug_csi!(); + handler.move_forward(arg_or_default!(args[0], 1)) + }, + 'D' => handler.move_backward(arg_or_default!(args[0], 1)), + 'E' => handler.move_down_and_cr(arg_or_default!(args[0], 1)), + 'F' => handler.move_up_and_cr(arg_or_default!(args[0], 1)), 'g' => { let mode = match args[0] { 0 => TabulationClearMode::Current, @@ -527,15 +570,15 @@ impl<H: Handler> Parser<H> { _ => unknown!(), }; - self.handler.clear_tabs(mode); + handler.clear_tabs(mode); }, - 'G' | '`' => self.handler.goto_col(arg_or_default!(args[0], 1)), + 'G' | '`' => handler.goto_col(arg_or_default!(args[0], 1)), 'H' | 'f' => { - let x = arg_or_default!(args[0], 1); - let y = arg_or_default!(args[1], 1); - self.handler.goto(x, y); + let y = arg_or_default!(args[0], 1); + let x = arg_or_default!(args[1], 1); + handler.goto(x - 1, y - 1); }, - 'I' => self.handler.move_forward_tabs(arg_or_default!(args[0], 1)), + 'I' => handler.move_forward_tabs(arg_or_default!(args[0], 1)), 'J' => { let mode = match args[0] { 0 => ClearMode::Below, @@ -544,7 +587,7 @@ impl<H: Handler> Parser<H> { _ => unknown!(), }; - self.handler.clear_screen(mode); + handler.clear_screen(mode); }, 'K' => { let mode = match args[0] { @@ -554,27 +597,29 @@ impl<H: Handler> Parser<H> { _ => unknown!(), }; - self.handler.clear_line(mode); + handler.clear_line(mode); }, - 'S' => self.handler.scroll_up(arg_or_default!(args[0], 1)), - 'T' => self.handler.scroll_down(arg_or_default!(args[0], 1)), - 'L' => self.handler.insert_blank_lines(arg_or_default!(args[0], 1)), + 'S' => handler.scroll_up(arg_or_default!(args[0], 1)), + 'T' => handler.scroll_down(arg_or_default!(args[0], 1)), + 'L' => handler.insert_blank_lines(arg_or_default!(args[0], 1)), 'l' => { - // TODO ResetMode - // - // This one seems like a lot of (important) work; going to come back to it. - unknown!(); + let mode = Mode::from_primitive(args[0]); + match mode { + Some(mode) => handler.set_mode(mode), + None => unhandled!(), + } }, - 'M' => self.handler.delete_lines(arg_or_default!(args[0], 1)), - 'X' => self.handler.erase_chars(arg_or_default!(args[0], 1)), - 'P' => self.handler.delete_chars(arg_or_default!(args[0], 1)), - 'Z' => self.handler.move_backward_tabs(arg_or_default!(args[0], 1)), - 'd' => self.handler.goto_row(arg_or_default!(args[0], 1)), + 'M' => handler.delete_lines(arg_or_default!(args[0], 1)), + 'X' => handler.erase_chars(arg_or_default!(args[0], 1)), + 'P' => handler.delete_chars(arg_or_default!(args[0], 1)), + 'Z' => handler.move_backward_tabs(arg_or_default!(args[0], 1)), + 'd' => handler.goto_row(arg_or_default!(args[0], 1)), 'h' => { - // TODO SetMode - // - // Ditto for 'l' - unknown!(); + let mode = Mode::from_primitive(args[0]); + match mode { + Some(mode) => handler.unset_mode(mode), + None => unhandled!(), + } }, 'm' => { let raw_attrs = &args[..args_idx]; @@ -655,15 +700,15 @@ impl<H: Handler> Parser<H> { _ => unknown!(), }; - self.handler.terminal_attribute(attr); + handler.terminal_attribute(attr); i += 1; // C-for expr } } - 'n' => self.handler.identify_terminal(), + 'n' => handler.identify_terminal(), 'r' => unknown!(), // set scrolling region - 's' => self.handler.save_cursor_position(), - 'u' => self.handler.restore_cursor_position(), + 's' => handler.save_cursor_position(), + 'u' => handler.restore_cursor_position(), _ => unknown!(), } @@ -674,15 +719,17 @@ impl<H: Handler> Parser<H> { self.idx = 0; } - fn control(&mut self, c: char) { + fn control<H>(&mut self, handler: &mut H, c: char) + where H: Handler + { match c { - C0::HT => self.handler.put_tab(1), - C0::BS => self.handler.backspace(1), - C0::CR => self.handler.carriage_return(), + C0::HT => handler.put_tab(1), + C0::BS => handler.backspace(1), + C0::CR => handler.carriage_return(), C0::LF | C0::VT | - C0::FF => self.handler.linefeed(), - C0::BEL => self.handler.bell(), + C0::FF => handler.linefeed(), + C0::BEL => handler.bell(), C0::ESC => { self.csi_reset(); self.state = State::Escape; @@ -690,7 +737,7 @@ impl<H: Handler> Parser<H> { }, // C0::S0 => Control::SwitchG1, // C0::S1 => Control::SwitchG0, - C0::SUB => self.handler.substitute(), + C0::SUB => handler.substitute(), C0::CAN => { self.csi_reset(); return; @@ -707,14 +754,14 @@ impl<H: Handler> Parser<H> { () }, C1::NEL => { - self.handler.newline(); + handler.newline(); () }, C1::SSA | C1::ESA => { () }, C1::HTS => { - self.handler.set_horizontal_tabstop(); + handler.set_horizontal_tabstop(); () }, C1::HTJ | C1::VTS | C1::PLD | C1::PLU | C1::RI | C1::SS2 | @@ -723,7 +770,7 @@ impl<H: Handler> Parser<H> { () }, C1::DECID => { - self.handler.identify_terminal(); + handler.identify_terminal(); }, C1::CSI | C1::ST => { () @@ -740,7 +787,7 @@ impl<H: Handler> Parser<H> { /// Parse a color specifier from list of attributes -fn parse_color(attrs: &[i64], i: &mut usize) -> Option<ColorSpec> { +fn parse_color(attrs: &[i64], i: &mut usize) -> Option<Rgb> { if attrs.len() < 2 { return None; } @@ -765,7 +812,7 @@ fn parse_color(attrs: &[i64], i: &mut usize) -> Option<ColorSpec> { return None; } - Some(ColorSpec { + Some(Rgb { r: r as u8, g: g as u8, b: b as u8 @@ -1011,7 +1058,8 @@ impl Default for State { #[cfg(test)] mod tests { use std::io::{Cursor, Read}; - use super::{Parser, Escape, Handler, Attr, ColorSpec, DebugHandler}; + use super::{Parser, Escape, Handler, Attr, Rgb, DebugHandler}; + use ::Rgb; #[test] fn parse_control_attribute() { @@ -1031,13 +1079,14 @@ mod tests { ]; let cursor = Cursor::new(BYTES); - let mut parser = Parser::new(TestHandler::default()); + let mut parser = Parser::new(); + let mut handler = TestHandler::default(); for c in cursor.chars() { - parser.advance(c.unwrap()); + parser.advance(&mut handler, c.unwrap()); } - assert_eq!(parser.handler.attr, Some(Attr::Bold)); + assert_eq!(handler.attr, Some(Attr::Bold)); } #[test] @@ -1059,19 +1108,20 @@ mod tests { ]; let mut cursor = Cursor::new(BYTES); - let mut parser = Parser::new(TestHandler::default()); + let mut parser = Parser::new(); + let mut handler = TestHandler::default(); for c in cursor.chars() { - parser.advance(c.unwrap()); + parser.advance(&mut handler, c.unwrap()); } - let spec = ColorSpec { + let spec = Rgb { r: 128, g: 66, b: 255 }; - assert_eq!(parser.handler.attr, Some(Attr::ForegroundSpec(spec))); + assert_eq!(handler.attr, Some(Attr::ForegroundSpec(spec))); } /// No exactly a test; useful for debugging @@ -1094,10 +1144,11 @@ mod tests { ]; let mut cursor = Cursor::new(BYTES); - let mut parser = Parser::new(DebugHandler); + let mut handler = DebugHandler; + let mut parser = Parser::new(); for c in cursor.chars() { - parser.advance(c.unwrap()); + parser.advance(&mut handler, c.unwrap()); } } } |