summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2016-09-18 11:17:44 -0700
committerJoe Wilm <joe@jwilm.com>2016-09-18 11:26:00 -0700
commit13d2d66b6bf213726ec864b84db471f2eec9f742 (patch)
tree4b2b1bbe9173bfd250d5b840e5e85dc76c9ad424
parent0d6d0dc0af028ead15c188db5d5f30e39fe0e3e7 (diff)
downloadalacritty-13d2d66b6bf213726ec864b84db471f2eec9f742.tar.gz
alacritty-13d2d66b6bf213726ec864b84db471f2eec9f742.zip
Rewrite ansi parser using vte crate
Using the vte crate allows removal of the ansi parser state machine and enables us to just be concerned with actions described in the protocol. In addition to making alacritty simpler, this also improves correctness and performance.
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml1
-rw-r--r--src/ansi.rs951
-rw-r--r--src/io.rs194
-rw-r--r--src/main.rs89
-rw-r--r--src/term.rs2
6 files changed, 436 insertions, 817 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 06bdfe53..22fdfacd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 9e8f8b81..f698ccb1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 {