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