diff options
-rw-r--r-- | Cargo.lock | 16 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/ansi.rs | 951 | ||||
-rw-r--r-- | src/io.rs | 194 | ||||
-rw-r--r-- | src/main.rs | 89 | ||||
-rw-r--r-- | src/term.rs | 2 |
6 files changed, 436 insertions, 817 deletions
@@ -14,6 +14,7 @@ dependencies = [ "serde 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_macros 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vte 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -781,6 +782,19 @@ dependencies = [ ] [[package]] +name = "utf8parse" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vte" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "utf8parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "walkdir" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -973,6 +987,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tempfile 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9270837a93bad1b1dac18fe67e786b3c960513af86231f6f4f57fddd594ff0c8" "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" "checksum user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6717129de5ac253f5642fc78a51d0c7de6f9f53d617fc94e9bae7f6e71cf5504" +"checksum utf8parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a15ea87f3194a3a454c78d79082b4f5e85f6956ddb6cb86bbfbe4892aa3c0323" +"checksum vte 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "889d519744c8d773708d246d046ad1c1f1c3e319d44aaeb941c56217afc94f0d" "checksum walkdir 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d42144c31c9909882ce76e696b306b88a5b091721251137d5d522d1ef3da7cf9" "checksum wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ced3094c157b5cc0a08d40530e1a627d9f88b9a436971338d2646439128a559e" "checksum wayland-kbd 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "73bc10e84c1da90777beffecd24742baea17564ffc2a9918af41871c748eb050" @@ -17,6 +17,7 @@ parking_lot = { version = "0.3.1", features = ["nightly"] } serde = "0.8" serde_yaml = "0.4" serde_macros = "0.8" +vte = "0.1.1" [build-dependencies] gl_generator = "0.5" diff --git a/src/ansi.rs b/src/ansi.rs index 73454073..1e8c1f5e 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -32,35 +32,200 @@ //! sequences only used by folks trapped in 1988. use std::ops::Range; +use vte; + use index::{Column, Line}; use ::Rgb; -/// A CSI Escape sequence -#[derive(Debug, Eq, PartialEq)] -pub enum Escape { - DisplayAttr(u8), +/// 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; + +/// 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 + 'a> { + state: &'a mut ProcessorState, + handler: &'a mut H +} + +impl<'a, H: Handler + TermInfo + 'a> Performer<'a, H> { + /// Create a performer + #[inline] + pub fn new<'b>(state: &'b mut ProcessorState, handler: &'b mut H) -> Performer<'b, H> { + Performer { + state: state, + handler: handler + } + } +} + +impl Processor { + pub fn new() -> Processor { + Processor { + state: ProcessorState, + parser: vte::Parser::new(), + } + } + + #[inline] + pub fn advance<H: Handler + TermInfo>(&mut self, handler: &mut H, byte: u8) { + let mut performer = Performer::new(&mut self.state, handler); + self.parser.advance(&mut performer, byte); + } +} + + /// Trait that provides properties of terminal pub trait TermInfo { fn lines(&self) -> Line; fn cols(&self) -> Column; } -pub const CSI_ATTR_MAX: usize = 16; +/// 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 { + /// A character to be displayed + fn input(&mut self, _c: char) {} -pub struct Parser { - /// Workspace for building a control sequence - buf: [char; 1024], + /// 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, usize) {} + + /// 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) + fn identify_terminal(&mut self) {} + + /// Move cursor forward `cols` + fn move_forward(&mut self, Column) {} - /// Index of control sequence + /// 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 /// - /// Alternatively, this can be viewed at the current length of used buffer - idx: usize, + /// Erase means resetting to the default state (default colors, no content, no mode flags) + fn erase_chars(&mut self, Column) {} - /// Current state - state: State, + /// 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 intead of ESCape seq) + fn unset_keypad_application_mode(&mut self) {} } /// Terminal modes @@ -225,409 +390,168 @@ pub enum Attr { DefaultBackground, } -/// 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 { - /// 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, usize) {} - - /// 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) - fn identify_terminal(&mut self) {} - - /// 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 intead of ESCape seq) - fn unset_keypad_application_mode(&mut self) {} -} - -impl Parser { - pub fn new() -> Parser { - Parser { - buf: [0 as char; 1024], - idx: 0, - state: Default::default(), - } +impl<'a, H: Handler + TermInfo + 'a> vte::Perform for Performer<'a, H> { + #[inline] + fn print(&mut self, c: char) { + self.handler.input(c); } - /// Advance the state machine. - /// - /// Maybe returns an Item which represents a state change of the terminal - pub fn advance<H>(&mut self, handler: &mut H, c: char) - where H: Handler + TermInfo - { - // println!("state: {:?}; char: {:?}", self.state, c); - // Control characters get handled immediately - if is_control(c) { - self.control(handler, c); - return; - } - - match self.state { - State::Base => { - self.advance_base(handler, c); - }, - State::Escape => { - self.escape(handler, c); - }, - State::Csi => { - self.csi(handler, c); - }, - State::EscapeOther => { - self.other(handler, 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(), + C1::NEL => self.handler.newline(), + C1::HTS => self.handler.set_horizontal_tabstop(), + C1::DECID => self.handler.identify_terminal(), + _ => (), } } - fn advance_base<H>(&mut self, handler: &mut H, c: char) - where H: Handler + TermInfo - { - handler.input(c); + #[inline] + fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: u8) { + err_println!("[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, byte={:?}", + params, intermediates, ignore, byte as char); } - fn other<H>(&mut self, _handler: &mut H, c: char) - where H: Handler + TermInfo - { - if c == 0x07 as char || c == 0x18 as char || c == 0x1a as char || - c == 0x1b as char || is_control_c1(c) - { - self.state = State::Base; - println!(""); - } - - // TODO actually use these bytes. For now, we just throw them away. - print!("{:?}", c); + #[inline] + fn put(&mut self, byte: u8) { + err_println!("[unhandled put] byte={:?}", byte); } - /// Handle character following an ESC - /// - /// TODO Handle `ST`, `'#'`, `'P'`, `'_'`, `'^'`, `']'`, `'k'`, - /// 'n', 'o', '(', ')', '*', '+', '=', '>' - fn escape<H>(&mut self, handler: &mut H, c: char) - where H: Handler + TermInfo - { - // Helper for items which complete a sequence. - macro_rules! sequence_complete { - ($fun:ident) => {{ - handler.$fun(); - self.state = State::Base; - }} - } - - match c { - '[' => { - self.state = State::Csi; - }, - 'D' => sequence_complete!(linefeed), - 'E' => sequence_complete!(newline), - 'H' => sequence_complete!(set_horizontal_tabstop), - 'M' => sequence_complete!(reverse_index), - 'Z' => sequence_complete!(identify_terminal), - 'c' => sequence_complete!(reset_state), - '7' => sequence_complete!(save_cursor_position), - '8' => sequence_complete!(restore_cursor_position), - '=' => sequence_complete!(set_keypad_application_mode), - '>' => sequence_complete!(unset_keypad_application_mode), - 'P' | '_' | '^' | ']' | 'k' | '(' => { - debug_println!("Entering EscapeOther"); - debug_print!("{:?}", c); - self.state = State::EscapeOther; - }, - _ => { - self.state = State::Base; - err_println!("Unknown ESC 0x{:02x} {:?}", c as usize, c); - } - } + #[inline] + fn unhook(&mut self, byte: u8) { + err_println!("[unhandled unhook] byte={:?}", byte); } - fn csi<H>(&mut self, handler: &mut H, c: char) - where H: Handler + TermInfo - { - self.buf[self.idx] = c; - self.idx += 1; - - if (self.idx == self.buf.len()) || is_csi_terminator(c) { - self.csi_parse(handler); - } + #[inline] + fn osc_start(&mut self) { + err_println!("[unhandled osc_start]"); } - /// Parse current CSI escape buffer - /// - /// ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ - fn csi_parse<H>(&mut self, handler: &mut H) - where H: Handler + TermInfo - { - 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]; - let mut private = false; - if raw[0] == '?' { - private = true; - raw = &raw[1..]; - } - - // Parse args - while !raw.is_empty() { - // Parse next arg in buf - let (subslice, val) = parse_next_num(raw); - raw = subslice; - - // Add arg to list - args[args_idx] = val; - args_idx += 1; - - // Max args or next char isn't arg sep - if args_idx == CSI_ATTR_MAX || raw[0] != ';' { - break; - } + #[inline] + fn osc_put(&mut self, byte: u8) { + err_println!("[unhandled osc_put] byte={:?}", byte as char); + } - // Need extra check before indexing - if raw.is_empty() { - break; - } + #[inline] + fn osc_end(&mut self, byte: u8) { + err_println!("[unhandled osc_end] byte={:?}", byte); + } - raw = &raw[1..]; - } + #[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; - macro_rules! unknown { - () => {{ - err_println!("Failed parsing CSI: {:?}", &self.buf[..self.idx]); - self.state = State::Base; - return; - }} - } macro_rules! unhandled { () => {{ - err_println!("Recognized, but unhandled CSI: {:?}", &self.buf[..self.idx]); - self.state = State::Base; + err_println!("[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}", + action, args, intermediates); return; }} } macro_rules! arg_or_default { - ($arg:expr, $default:expr) => { - // using Default::default as a generic zero - if $arg == Default::default() { $default } else { $arg } - } - } - - macro_rules! debug_csi { - () => { - err_println!("CSI: {:?}", &self.buf[..self.idx]); + (idx: $idx:expr, default: $default:expr) => { + args.get($idx).and_then(|v| { + if *v == 0 { + None + } else { + Some(*v) + } + }).unwrap_or($default) } } - if raw.is_empty() { - println!("raw is empty"); - unknown!(); - } - - match raw[0] { - '@' => handler.insert_blank(arg_or_default!(args[0] as usize, 1)), + match action { + '@' => handler.insert_blank(arg_or_default!(idx: 0, default: 1) as usize), 'A' => { - handler.move_up(Line(arg_or_default!(args[0] as usize, 1))); + handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize)); }, - 'B' | 'e' => handler.move_down(Line(arg_or_default!(args[0] as usize, 1))), + 'B' | 'e' => handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), 'c' => handler.identify_terminal(), - 'C' | 'a' => handler.move_forward(Column(arg_or_default!(args[0] as usize, 1))), - 'D' => handler.move_backward(Column(arg_or_default!(args[0] as usize, 1))), - 'E' => handler.move_down_and_cr(Line(arg_or_default!(args[0] as usize, 1))), - 'F' => handler.move_up_and_cr(Line(arg_or_default!(args[0] as usize, 1))), + '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 args[0] { + let mode = match arg_or_default!(idx: 0, default: 0) { 0 => TabulationClearMode::Current, 3 => TabulationClearMode::All, - _ => unknown!(), + _ => unhandled!(), }; handler.clear_tabs(mode); }, - 'G' | '`' => handler.goto_col(Column(arg_or_default!(args[0] as usize, 1) - 1)), + 'G' | '`' => handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1)), 'H' | 'f' => { - let y = arg_or_default!(args[0] as usize, 1); - let x = arg_or_default!(args[1] as usize, 1); + 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!(args[0], 1)), + 'I' => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)), 'J' => { - let mode = match args[0] { + let mode = match arg_or_default!(idx: 0, default: 0) { 0 => ClearMode::Below, 1 => ClearMode::Above, 2 => ClearMode::All, - _ => unknown!(), + _ => unhandled!(), }; handler.clear_screen(mode); }, 'K' => { - let mode = match args[0] { + let mode = match arg_or_default!(idx: 0, default: 0) { 0 => LineClearMode::Right, 1 => LineClearMode::Left, 2 => LineClearMode::All, - _ => unknown!(), + _ => unhandled!(), }; handler.clear_line(mode); }, - 'S' => handler.scroll_up(Line(arg_or_default!(args[0] as usize, 1))), - 'T' => handler.scroll_down(Line(arg_or_default!(args[0] as usize, 1))), - 'L' => handler.insert_blank_lines(Line(arg_or_default!(args[0] as usize, 1))), + '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' => { - let mode = Mode::from_primitive(private, args[0]); + let mode = Mode::from_primitive(private, arg_or_default!(idx: 0, default: 0)); match mode { Some(mode) => handler.unset_mode(mode), None => unhandled!(), } }, - 'M' => handler.delete_lines(Line(arg_or_default!(args[0] as usize, 1))), - 'X' => handler.erase_chars(Column(arg_or_default!(args[0] as usize, 1))), - 'P' => handler.delete_chars(Column(arg_or_default!(args[0] as usize, 1))), - 'Z' => handler.move_backward_tabs(arg_or_default!(args[0], 1)), - 'd' => handler.goto_line(Line(arg_or_default!(args[0] as usize, 1) - 1)), + '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' => { - let mode = Mode::from_primitive(private, args[0]); + let mode = Mode::from_primitive(private, arg_or_default!(idx: 0, default: 0)); match mode { Some(mode) => handler.set_mode(mode), None => unhandled!(), } }, 'm' => { - let raw_attrs = &args[..args_idx]; - // Sometimes a C-style for loop is just what you need let mut i = 0; // C-for initializer + if args.len() == 0 { + handler.terminal_attribute(Attr::Reset); + return; + } loop { - if i >= raw_attrs.len() { // C-for condition + // println!("args.len = {}; i={}", args.len(), i); + if i >= args.len() { // C-for condition break; } - let attr = match raw_attrs[i] { + let attr = match args[i] { 0 => Attr::Reset, 1 => Attr::Bold, 2 => Attr::Dim, @@ -654,7 +578,7 @@ impl Parser { 36 => Attr::Foreground(Color::Cyan), 37 => Attr::Foreground(Color::White), 38 => { - if let Some(spec) = parse_color(&raw_attrs[i..], &mut i) { + if let Some(spec) = parse_color(&args[i..], &mut i) { Attr::ForegroundSpec(spec) } else { break; @@ -670,7 +594,7 @@ impl Parser { 46 => Attr::Background(Color::Cyan), 47 => Attr::Background(Color::White), 48 => { - if let Some(spec) = parse_color(&raw_attrs[i..], &mut i) { + if let Some(spec) = parse_color(&args[i..], &mut i) { Attr::BackgroundSpec(spec) } else { break; @@ -693,7 +617,7 @@ impl Parser { 105 => Attr::Foreground(Color::BrightMagenta), 106 => Attr::Foreground(Color::BrightCyan), 107 => Attr::Foreground(Color::BrightWhite), - _ => unknown!(), + _ => unhandled!(), }; handler.terminal_attribute(attr); @@ -704,99 +628,42 @@ impl Parser { 'n' => handler.identify_terminal(), 'r' => { if private { - unknown!(); + unhandled!(); } - let top = arg_or_default!(Line(args[0] as usize), Line(1)) - 1; + 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 bottom = arg_or_default!(Line(args[1] as usize), handler.lines()); + 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(), - _ => unknown!(), + _ => unhandled!(), } - - self.state = State::Base; - } - - fn csi_reset(&mut self) { - self.idx = 0; } - fn control<H>(&mut self, handler: &mut H, c: char) - where H: Handler + TermInfo - { - match c { - C0::HT => handler.put_tab(1), - C0::BS => handler.backspace(), - C0::CR => handler.carriage_return(), - C0::LF | - C0::VT | - C0::FF => handler.linefeed(), - C0::BEL => { - // Clear ESC state is in an escape sequence. - if let State::EscapeOther = self.state { - self.state = State::Base; - } - - handler.bell(); - }, - C0::ESC => { - self.csi_reset(); - self.state = State::Escape; - return; - }, - // C0::S0 => Control::SwitchG1, - // C0::S1 => Control::SwitchG0, - C0::SUB => handler.substitute(), - C0::CAN => { - self.csi_reset(); - return; - }, - C0::ENQ | - C0::NUL | - C0::XON | - C0::XOFF | - C0::DEL => { - // Ignored - return; - }, - C1::PAD | C1::HOP | C1::BPH | C1::NBH | C1::IND => { - () - }, - C1::NEL => { - handler.newline(); - () - }, - C1::SSA | C1::ESA => { - () - }, - C1::HTS => { - handler.set_horizontal_tabstop(); - () - }, - C1::HTJ | C1::VTS | C1::PLD | C1::PLU | C1::RI | C1::SS2 | - C1::SS3 | C1::PU1 | C1::PU2 | C1::STS | C1::CCH | C1::MW | - C1::SPA | C1::EPA | C1::SOS | C1::SGCI => { - () - }, - C1::DECID => { - handler.identify_terminal(); - }, - C1::CSI | C1::ST => { - () - }, - C1::DCS | C1::OSC | C1::PM | C1::APC => { - // FIXME complete str sequence - }, - _ => return, - }; - - // TODO interrupt sequence on CAN, SUB, \a and C1 chars + #[inline] + fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: u8) { + let private = intermediates.get(0).map(|b| *b == b'?').unwrap_or(false); + + match byte { + b'D' => self.handler.linefeed(), + b'E' => self.handler.newline(), + b'H' => self.handler.set_horizontal_tabstop(), + b'M' => self.handler.reverse_index(), + b'Z' => self.handler.identify_terminal(), + b'c' => self.handler.reset_state(), + b'7' => self.handler.save_cursor_position(), + b'8' => self.handler.restore_cursor_position(), + b'=' => self.handler.set_keypad_application_mode(), + b'>' => self.handler.unset_keypad_application_mode(), + _ => err_println!("[unhandled] execute {:?}", byte as char), + } } } @@ -840,137 +707,75 @@ fn parse_color(attrs: &[i64], i: &mut usize) -> Option<Rgb> { } } -/// Utility for parsing next number from a slice of chars -fn parse_next_num(buf: &[char]) -> (&[char], i64) { - let mut idx = 0; - while idx < buf.len() { - let c = buf[idx]; - match c { - '0'...'9' => { - idx += 1; - continue; - }, - _ => break - } - } - - match idx { - 0 => (buf, 0), - _ => { - // FIXME maybe write int parser based on &[char].. just stay off the heap! - let v = buf[..idx] - .iter().cloned() - .collect::<String>() - .parse::<i64>() - .unwrap_or(-1); - (&buf[idx..], v) - } - } -} - - -/// Is c a CSI terminator? -#[inline] -fn is_csi_terminator(c: char) -> bool { - match c as u32 { - 0x40...0x7e => true, - _ => false, - } -} - -/// Is `c` a control character? -#[inline] -fn is_control(c: char) -> bool { - is_control_c0(c) || is_control_c1(c) -} - -/// Is provided char one of the C0 set of 7-bit control characters? -#[inline] -fn is_control_c0(c: char) -> bool { - match c as u32 { - 0...0x1f | 0x7f => true, - _ => false, - } -} - -/// Is provided char one of the C1 set of 8-bit control characters? -#[inline] -fn is_control_c1(c: char) -> bool { - match c as u32 { - 0x80...0x9f => true, - _ => false, - } -} - /// 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: char = 0x00 as char; + pub const NUL: u8 = 0x00; /// Start of Header - pub const SOH: char = 0x01 as char; + pub const SOH: u8 = 0x01; /// Start of Text, implied end of header - pub const STX: char = 0x02 as char; + pub const STX: u8 = 0x02; /// End of Text, causes some terminal to respond with ACK or NAK - pub const ETX: char = 0x03 as char; + pub const ETX: u8 = 0x03; /// End of Transmission - pub const EOT: char = 0x04 as char; + pub const EOT: u8 = 0x04; /// Enquiry, causes terminal to send ANSWER-BACK ID - pub const ENQ: char = 0x05 as char; + pub const ENQ: u8 = 0x05; /// Acknowledge, usually sent by terminal in response to ETX - pub const ACK: char = 0x06 as char; + pub const ACK: u8 = 0x06; /// Bell, triggers the bell, buzzer, or beeper on the terminal - pub const BEL: char = 0x07 as char; + pub const BEL: u8 = 0x07; /// Backspace, can be used to define overstruck characters - pub const BS: char = 0x08 as char; + pub const BS: u8 = 0x08; /// Horizontal Tabulation, move to next predetermined position - pub const HT: char = 0x09 as char; + pub const HT: u8 = 0x09; /// Linefeed, move to same position on next line (see also NL) - pub const LF: char = 0x0A as char; + pub const LF: u8 = 0x0A; /// Vertical Tabulation, move to next predetermined line - pub const VT: char = 0x0B as char; + pub const VT: u8 = 0x0B; /// Form Feed, move to next form or page - pub const FF: char = 0x0C as char; + pub const FF: u8 = 0x0C; /// Carriage Return, move to first character of current line - pub const CR: char = 0x0D as char; + pub const CR: u8 = 0x0D; /// Shift Out, switch to G1 (other half of character set) - pub const SO: char = 0x0E as char; + pub const SO: u8 = 0x0E; /// Shift In, switch to G0 (normal half of character set) - pub const SI: char = 0x0F as char; + pub const SI: u8 = 0x0F; /// Data Link Escape, interpret next control character specially - pub const DLE: char = 0x10 as char; + pub const DLE: u8 = 0x10; /// (DC1) Terminal is allowed to resume transmitting - pub const XON: char = 0x11 as char; + pub const XON: u8 = 0x11; /// Device Control 2, causes ASR-33 to activate paper-tape reader - pub const DC2: char = 0x12 as char; + pub const DC2: u8 = 0x12; /// (DC2) Terminal must pause and refrain from transmitting - pub const XOFF: char = 0x13 as char; + pub const XOFF: u8 = 0x13; /// Device Control 4, causes ASR-33 to deactivate paper-tape reader - pub const DC4: char = 0x14 as char; + pub const DC4: u8 = 0x14; /// Negative Acknowledge, used sometimes with ETX and ACK - pub const NAK: char = 0x15 as char; + pub const NAK: u8 = 0x15; /// Synchronous Idle, used to maintain timing in Sync communication - pub const SYN: char = 0x16 as char; + pub const SYN: u8 = 0x16; /// End of Transmission block - pub const ETB: char = 0x17 as char; + pub const ETB: u8 = 0x17; /// Cancel (makes VT100 abort current escape sequence if any) - pub const CAN: char = 0x18 as char; + pub const CAN: u8 = 0x18; /// End of Medium - pub const EM: char = 0x19 as char; + pub const EM: u8 = 0x19; /// Substitute (VT100 uses this to display parity errors) - pub const SUB: char = 0x1A as char; + pub const SUB: u8 = 0x1A; /// Prefix to an ESCape sequence - pub const ESC: char = 0x1B as char; + pub const ESC: u8 = 0x1B; /// File Separator - pub const FS: char = 0x1C as char; + pub const FS: u8 = 0x1C; /// Group Separator - pub const GS: char = 0x1D as char; + pub const GS: u8 = 0x1D; /// Record Separator (sent by VT132 in block-transfer mode) - pub const RS: char = 0x1E as char; + pub const RS: u8 = 0x1E; /// Unit Separator - pub const US: char = 0x1F as char; + pub const US: u8 = 0x1F; /// Delete, should be ignored by terminal - pub const DEL: char = 0x7f as char; + pub const DEL: u8 = 0x7f; } @@ -982,92 +787,69 @@ pub mod C0 { #[allow(non_snake_case)] pub mod C1 { /// Reserved - pub const PAD: char = 0x80 as char; + pub const PAD: u8 = 0x80; /// Reserved - pub const HOP: char = 0x81 as char; + pub const HOP: u8 = 0x81; /// Reserved - pub const BPH: char = 0x82 as char; + pub const BPH: u8 = 0x82; /// Reserved - pub const NBH: char = 0x83 as char; + pub const NBH: u8 = 0x83; /// Index, moves down one line same column regardless of NL - pub const IND: char = 0x84 as char; + pub const IND: u8 = 0x84; /// NEw Line, moves done one line and to first column (CR+LF) - pub const NEL: char = 0x85 as char; + pub const NEL: u8 = 0x85; /// Start of Selected Area to be as charsent to auxiliary output device - pub const SSA: char = 0x86 as char; + pub const SSA: u8 = 0x86; /// End of Selected Area to be sent to auxiliary output device - pub const ESA: char = 0x87 as char; + pub const ESA: u8 = 0x87; /// Horizontal Tabulation Set at current position - pub const HTS: char = 0x88 as char; + pub const HTS: u8 = 0x88; /// Hor Tab Justify, moves string to next tab position - pub const HTJ: char = 0x89 as char; + pub const HTJ: u8 = 0x89; /// Vertical Tabulation Set at current line - pub const VTS: char = 0x8A as char; + pub const VTS: u8 = 0x8A; /// Partial Line Down (subscript) - pub const PLD: char = 0x8B as char; + pub const PLD: u8 = 0x8B; /// Partial Line Up (superscript) - pub const PLU: char = 0x8C as char; + pub const PLU: u8 = 0x8C; /// Reverse Index, go up one line, reverse scroll if necessary - pub const RI: char = 0x8D as char; + pub const RI: u8 = 0x8D; /// Single Shift to G2 - pub const SS2: char = 0x8E as char; + pub const SS2: u8 = 0x8E; /// Single Shift to G3 (VT100 uses this for sending PF keys) - pub const SS3: char = 0x8F as char; + pub const SS3: u8 = 0x8F; /// Device Control String, terminated by ST (VT125 enters graphics) - pub const DCS: char = 0x90 as char; + pub const DCS: u8 = 0x90; /// Private Use 1 - pub const PU1: char = 0x91 as char; + pub const PU1: u8 = 0x91; /// Private Use 2 - pub const PU2: char = 0x92 as char; + pub const PU2: u8 = 0x92; /// Set Transmit State - pub const STS: char = 0x93 as char; + pub const STS: u8 = 0x93; /// Cancel CHaracter, ignore previous character - pub const CCH: char = 0x94 as char; + pub const CCH: u8 = 0x94; /// Message Waiting, turns on an indicator on the terminal - pub const MW: char = 0x95 as char; + pub const MW: u8 = 0x95; /// Start of Protected Area - pub const SPA: char = 0x96 as char; + pub const SPA: u8 = 0x96; /// End of Protected Area - pub const EPA: char = 0x97 as char; + pub const EPA: u8 = 0x97; /// SOS - pub const SOS: char = 0x98 as char; + pub const SOS: u8 = 0x98; /// SGCI - pub const SGCI: char = 0x99 as char; + pub const SGCI: u8 = 0x99; /// DECID - Identify Terminal - pub const DECID: char = 0x9a as char; + pub const DECID: u8 = 0x9a; /// Control Sequence Introducer (described in a seperate table) - pub const CSI: char = 0x9B as char; + pub const CSI: u8 = 0x9B; /// String Terminator (VT125 exits graphics) - pub const ST: char = 0x9C as char; + pub const ST: u8 = 0x9C; /// Operating System Command (reprograms intelligent terminal) - pub const OSC: char = 0x9D as char; + pub const OSC: u8 = 0x9D; /// Privacy Message (password verification), terminated by ST - pub const PM: char = 0x9E as char; + pub const PM: u8 = 0x9E; /// Application Program Command (to word processor), term by ST - pub const APC: char = 0x9F as char; -} - -#[derive(Debug)] -enum State { - /// Base state - /// - /// Expects control characters or characters for display - Base, - - /// Just got an escape - Escape, - - /// Parsing a CSI escape, - Csi, - - /// Parsing non csi escape - EscapeOther, -} - -impl Default for State { - fn default() -> State { - State::Base - } + pub const APC: u8 = 0x9F; } // Tests for parsing escape sequences @@ -1075,9 +857,8 @@ impl Default for State { // Byte sequences used in these tests are recording of pty stdout. #[cfg(test)] mod tests { - use io::Utf8Chars; use index::{Line, Column}; - use super::{Parser, Handler, Attr, TermInfo}; + use super::{Processor, Handler, Attr, TermInfo}; use ::Rgb; #[derive(Default)] @@ -1107,11 +888,11 @@ mod tests { 0x1b, 0x5b, 0x31, 0x6d ]; - let mut parser = Parser::new(); + let mut parser = Processor::new(); let mut handler = AttrHandler::default(); - for c in Utf8Chars::new(&BYTES[..]) { - parser.advance(&mut handler, c.unwrap()); + for byte in &BYTES[..] { + parser.advance(&mut handler, *byte); } assert_eq!(handler.attr, Some(Attr::Bold)); @@ -1124,11 +905,11 @@ mod tests { 0x38, 0x3b, 0x36, 0x36, 0x3b, 0x32, 0x35, 0x35, 0x6d ]; - let mut parser = Parser::new(); + let mut parser = Processor::new(); let mut handler = AttrHandler::default(); - for c in Utf8Chars::new(&BYTES[..]) { - parser.advance(&mut handler, c.unwrap()); + for byte in &BYTES[..] { + parser.advance(&mut handler, *byte); } let spec = Rgb { @@ -1160,10 +941,10 @@ mod tests { ]; let mut handler = AttrHandler::default(); - let mut parser = Parser::new(); + let mut parser = Processor::new(); - for c in Utf8Chars::new(&BYTES[..]) { - parser.advance(&mut handler, c.unwrap()); + for byte in &BYTES[..] { + parser.advance(&mut handler, *byte); } } } diff --git a/src/io.rs b/src/io.rs deleted file mode 100644 index 5801efaf..00000000 --- a/src/io.rs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Unmerged utf8 chars iterator vendored from std::io -//! -use std::io::{BufRead, ErrorKind, Error}; -use std::fmt; -use std::error as std_error; -use std::result; -use std::char; - -static UTF8_CHAR_WIDTH: [u8; 256] = [ -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3F -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5F -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, -1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7F -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF -0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2, -2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF -4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF -]; - -/// Given a first byte, determine how many bytes are in this UTF-8 character -#[inline] -pub fn utf8_char_width(b: u8) -> usize { - return UTF8_CHAR_WIDTH[b as usize] as usize; -} - -/// An iterator over the `char`s of a reader. -/// -/// This struct is generally created by calling [`utf8_chars()`][utf8_chars] on a reader. -/// Please see the documentation of `utf8_chars()` for more details. -/// -/// [utf8_chars]: trait.BufRead.html#method.utf8_chars -pub struct Utf8Chars<R> { - inner: R, -} - -impl<R> Utf8Chars<R> { - pub fn new(inner: R) -> Utf8Chars<R> { - Utf8Chars { inner: inner } - } -} - -/// An enumeration of possible errors that can be generated from the `Utf8Chars` -/// adapter. -#[derive(Debug)] -pub enum Utf8CharsError { - /// Variant representing that the underlying stream was read successfully - /// but contains a byte sequence ill-formed in UTF-8. - InvalidUtf8, - - /// Variant representing that the underlying stream contains the start - /// of a byte sequence well-formed in UTF-8, but ends prematurely. - /// - /// Contains number of unused bytes - IncompleteUtf8(usize), - - /// Variant representing that an I/O error occurred. - Io(Error), -} - -impl<R: BufRead> Iterator for Utf8Chars<R> { - type Item = result::Result<char, Utf8CharsError>; - - // allow(unused_assignments) because consumed += 1 is not recognized as being used - #[allow(unused_assignments)] - fn next(&mut self) -> Option<result::Result<char, Utf8CharsError>> { - macro_rules! read_byte { - (EOF => $on_eof: expr) => { - { - let byte; - loop { - match self.inner.fill_buf() { - Ok(buffer) => { - if let Some(&b) = buffer.first() { - byte = b; - break - } else { - $on_eof - } - } - Err(ref e) if e.kind() == ErrorKind::Interrupted => {} - Err(e) => return Some(Err(Utf8CharsError::Io(e))), - } - } - byte - } - } - } - - let first = read_byte!(EOF => return None); - self.inner.consume(1); - - let mut consumed = 1; - - macro_rules! continuation_byte { - ($range: pat) => { - { - match read_byte!(EOF => return Some(Err(Utf8CharsError::IncompleteUtf8(consumed)))) { - byte @ $range => { - self.inner.consume(1); - consumed += 1; - (byte & 0b0011_1111) as u32 - } - _ => return Some(Err(Utf8CharsError::InvalidUtf8)) - } - } - } - } - - // Ranges can be checked against https://tools.ietf.org/html/rfc3629#section-4 - let code_point = match utf8_char_width(first) { - 1 => return Some(Ok(first as char)), - 2 => { - let second = continuation_byte!(0x80...0xBF); - ((first & 0b0001_1111) as u32) << 6 | second - } - 3 => { - let second = match first { - 0xE0 => continuation_byte!(0xA0...0xBF), - 0xE1...0xEC => continuation_byte!(0x80...0xBF), - 0xED => continuation_byte!(0x80...0x9F), - 0xEE...0xEF => continuation_byte!(0x80...0xBF), - _ => unreachable!(), - }; - let third = continuation_byte!(0x80...0xBF); - ((first & 0b0000_1111) as u32) << 12 | second << 6 | third - } - 4 => { - let second = match first { - 0xF0 => continuation_byte!(0x90...0xBF), - 0xF0...0xF3 => continuation_byte!(0x80...0xBF), - 0xF4 => continuation_byte!(0x80...0x8F), - _ => unreachable!(), - }; - let third = continuation_byte!(0x80...0xBF); - let fourth = continuation_byte!(0x80...0xBF); - ((first & 0b0000_0111) as u32) << 18 | second << 12 | third << 6 | fourth - } - _ => return Some(Err(Utf8CharsError::InvalidUtf8)) - }; - unsafe { - Some(Ok(char::from_u32_unchecked(code_point))) - } - } -} - -impl std_error::Error for Utf8CharsError { - fn description(&self) -> &str { - match *self { - Utf8CharsError::InvalidUtf8 => "invalid UTF-8 byte sequence", - Utf8CharsError::IncompleteUtf8(_) => { - "stream ended in the middle of an UTF-8 byte sequence" - } - Utf8CharsError::Io(ref e) => std_error::Error::description(e), - } - } - fn cause(&self) -> Option<&std_error::Error> { - match *self { - Utf8CharsError::InvalidUtf8 | Utf8CharsError::IncompleteUtf8(_) => None, - Utf8CharsError::Io(ref e) => e.cause(), - } - } -} - -impl fmt::Display for Utf8CharsError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Utf8CharsError::InvalidUtf8 => { - "invalid UTF-8 byte sequence".fmt(f) - } - Utf8CharsError::IncompleteUtf8(_) => { - "stream ended in the middle of an UTF-8 byte sequence".fmt(f) - } - Utf8CharsError::Io(ref e) => e.fmt(f), - } - } -} diff --git a/src/main.rs b/src/main.rs index c0685204..a803d05c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ extern crate notify; extern crate parking_lot; extern crate serde; extern crate serde_yaml; +extern crate vte; #[macro_use] extern crate bitflags; @@ -49,7 +50,6 @@ mod tty; pub mod ansi; mod term; mod util; -mod io; mod sync; use std::sync::{mpsc, Arc}; @@ -58,7 +58,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use parking_lot::{Condvar, Mutex, MutexGuard}; use config::Config; -use io::{Utf8Chars, Utf8CharsError}; use meter::Meter; use renderer::{QuadRenderer, GlyphCache}; use sync::PriorityMutex; @@ -69,6 +68,24 @@ use util::thread; /// Channel used by resize handling on mac static mut resize_sender: Option<mpsc::Sender<(u32, u32)>> = None; +#[derive(Clone)] +struct Flag(Arc<AtomicBool>); +impl Flag { + pub fn new(initial_value: bool) -> Flag { + Flag(Arc::new(AtomicBool::new(initial_value))) + } + + #[inline] + pub fn get(&self) -> bool { + self.0.load(Ordering::Acquire) + } + + #[inline] + pub fn set(&self, value: bool) { + self.0.store(value, Ordering::Release) + } +} + /// Resize handling for Mac fn window_resize_handler(width: u32, height: u32) { unsafe { @@ -163,18 +180,27 @@ fn main() { resize_sender = Some(tx.clone()); } + let signal_flag = Flag::new(false); + let terminal = Arc::new(PriorityMutex::new(terminal)); let window = Arc::new(window); - let pty_reader = PtyReader::spawn(terminal.clone(), reader, window.create_window_proxy()); + let pty_reader = PtyReader::spawn( + terminal.clone(), + reader, + window.create_window_proxy(), + signal_flag.clone() + ); // Wraps a renderer and gives simple draw() api. - let mut display = Display::new(window.clone(), - terminal.clone(), - renderer, - glyph_cache, - render_timer, - rx); + let mut display = Display::new( + window.clone(), + terminal.clone(), + renderer, + glyph_cache, + render_timer, + rx + ); // Event processor let mut processor = event::Processor::new(&mut writer, terminal.clone(), tx); @@ -184,6 +210,8 @@ fn main() { // Wait for something to happen processor.process_events(&window); + signal_flag.set(false); + // Maybe draw the terminal let terminal = terminal.lock_high(); if terminal.dirty { @@ -205,45 +233,32 @@ struct PtyReader; impl PtyReader { pub fn spawn<R>(terminal: Arc<PriorityMutex<Term>>, mut pty: R, - proxy: ::glutin::WindowProxy) + proxy: ::glutin::WindowProxy, + signal_flag: Flag) -> std::thread::JoinHandle<()> where R: std::io::Read + Send + 'static { thread::spawn_named("pty reader", move || { let mut buf = [0u8; 4096]; - let mut start = 0; - let mut pty_parser = ansi::Parser::new(); + let mut pty_parser = ansi::Processor::new(); loop { - if let Ok(got) = pty.read(&mut buf[start..]) { - let mut remain = 0; - - // if `start` is nonzero, then actual bytes in buffer is > `got` by `start` bytes. - let end = start + got; - { - let mut terminal = terminal.lock_low(); - terminal.dirty = true; - for c in Utf8Chars::new(&buf[..end]) { - match c { - Ok(c) => pty_parser.advance(&mut *terminal, c), - Err(err) => match err { - Utf8CharsError::IncompleteUtf8(unused) => { - remain = unused; - break; - }, - _ => panic!("{}", err), - } - } - } + if let Ok(got) = pty.read(&mut buf[..]) { + let mut terminal = terminal.lock_high(); + + for byte in &buf[..got] { + pty_parser.advance(&mut *terminal, *byte); } - proxy.wakeup_event_loop(); + terminal.dirty = true; - // Move any leftover bytes to front of buffer - for i in 0..remain { - buf[i] = buf[end - (remain - i)]; + // Only wake up the event loop if it hasn't already been signaled. This is a + // really important optimization because waking up the event loop redundantly + // burns *a lot* of cycles. + if !signal_flag.get() { + proxy.wakeup_event_loop(); + signal_flag.set(true); } - start = remain; } else { break; } diff --git a/src/term.rs b/src/term.rs index 0cb54240..3dfab06c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -457,7 +457,7 @@ impl ansi::Handler for Term { /// A character to be displayed #[inline] fn input(&mut self, c: char) { - debug_print!("{}", c); + debug_print!("{}; attrs = {:?}", c, self.attr); if self.cursor.col == self.grid.num_cols() { debug_println!("wrapping"); if (self.cursor.line + 1) >= self.scroll_region.end { |