aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 {