From 30ec14510935d46e7454863f9a4e63e53bf7728c Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 30 May 2016 20:44:37 -0700 Subject: [PATCH] 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). --- res/text.f.glsl | 7 +- src/ansi.rs | 233 +++++++++++++++++------------ src/grid.rs | 88 +++++++++-- src/main.rs | 160 ++++++++++++-------- src/renderer/mod.rs | 9 +- src/term.rs | 350 ++++++++++++++++++++++++++++++++++++++++++++ src/tty.rs | 51 ++++++- 7 files changed, 717 insertions(+), 181 deletions(-) create mode 100644 src/term.rs diff --git a/res/text.f.glsl b/res/text.f.glsl index 3389fd7e..e817626f 100644 --- a/res/text.f.glsl +++ b/res/text.f.glsl @@ -5,10 +5,11 @@ layout(location = 0, index = 0) out vec4 color; layout(location = 0, index = 1) out vec4 alphaMask; uniform sampler2D mask; -uniform vec3 textColor; +uniform ivec3 textColor; void main() { - alphaMask = vec4(texture(mask, TexCoords).rgb, 1.); - color = vec4(textColor, 1.); + alphaMask = vec4(texture(mask, TexCoords).rgb, 1.0); + vec3 textColorF = vec3(textColor) / vec3(255.0, 255.0, 255.0); + color = vec4(textColorF, 1.0); } 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 { +pub struct Parser { /// Workspace for building a control sequence buf: [char; 1024], @@ -70,11 +71,24 @@ pub struct Parser { /// Current state state: State, +} - /// Handler +/// Terminal modes +#[derive(Debug, Eq, PartialEq)] +pub enum Mode { + SwapScreenAndSetRestoreCursor = 1049, +} + +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 { + 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 Parser { - pub fn new(handler: H) -> Parser { +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(&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(&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(&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 Parser { '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(&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 '[' [[ [] [;]] []] */ - fn csi_parse(&mut self) { + fn csi_parse(&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 Parser { }} } + 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 Parser { _ => 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 Parser { _ => unknown!(), }; - self.handler.clear_screen(mode); + handler.clear_screen(mode); }, 'K' => { let mode = match args[0] { @@ -554,27 +597,29 @@ impl Parser { _ => 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 Parser { _ => 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 Parser { self.idx = 0; } - fn control(&mut self, c: char) { + fn control(&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 Parser { }, // 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 Parser { () }, 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 Parser { () }, C1::DECID => { - self.handler.identify_terminal(); + handler.identify_terminal(); }, C1::CSI | C1::ST => { () @@ -740,7 +787,7 @@ impl Parser { /// Parse a color specifier from list of attributes -fn parse_color(attrs: &[i64], i: &mut usize) -> Option { +fn parse_color(attrs: &[i64], i: &mut usize) -> Option { if attrs.len() < 2 { return None; } @@ -765,7 +812,7 @@ fn parse_color(attrs: &[i64], i: &mut usize) -> Option { 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()); } } } diff --git a/src/grid.rs b/src/grid.rs index 86a2e45f..765a5e1a 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,31 +1,41 @@ //! Functions for computing properties of the terminal grid -use std::ops::{Index, IndexMut}; +use std::collections::VecDeque; + +use std::ops::{Index, IndexMut, Deref, DerefMut}; + +use term::Cursor; +use ::Rgb; /// Calculate the number of cells for an axis pub fn num_cells_axis(cell_width: u32, cell_sep: i32, screen_width: u32) -> u32 { - ((screen_width as i32 + cell_sep) as f64 / (cell_width as i32 - cell_sep) as f64) as u32 + println!("num_cells_axis(cell_width: {}, cell_sep: {}, screen_width: {}", + cell_width, cell_sep, screen_width); + ((screen_width as i32 - cell_sep) as f64 / (cell_width as i32 + cell_sep) as f64) as u32 } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Cell { - pub character: String, + pub c: char, + pub fg: Rgb, + pub bg: Rgb, } impl Cell { - pub fn new(c: S) -> Cell - where S: Into - { + pub fn new(c: char) -> Cell { Cell { - character: c.into(), + c: c.into(), + bg: Default::default(), + fg: Default::default(), } } } /// Represents the terminal display contents +#[derive(Clone)] pub struct Grid { /// Rows in the grid. Each row holds a list of cells corresponding to the columns in that row. - raw: Vec, + raw: VecDeque, /// Number of columns cols: usize, @@ -38,9 +48,9 @@ pub struct Grid { impl Grid { pub fn new(rows: usize, cols: usize) -> Grid { - let mut raw = Vec::with_capacity(rows); + let mut raw = VecDeque::with_capacity(rows); for _ in 0..raw.capacity() { - raw.push(Row::new(cols)); + raw.push_back(Row::new(cols)); } Grid { @@ -57,28 +67,67 @@ impl Grid { pub fn cols(&self) -> usize { self.cols } + + pub fn feed(&mut self) { + // do the borrowck dance + let row = self.raw.pop_front().unwrap(); + self.raw.push_back(row); + } + + pub fn unfeed(&mut self) { + // do the borrowck dance + let row = self.raw.pop_back().unwrap(); + self.raw.push_front(row); + } + + pub fn clear(&mut self) { + for row in self.raw.iter_mut() { + for cell in row.iter_mut() { + cell.c = ' '; + } + } + } } impl Index for Grid { type Output = Row; + #[inline] fn index<'a>(&'a self, index: usize) -> &'a Row { &self.raw[index] } } impl IndexMut for Grid { + #[inline] fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Row { &mut self.raw[index] } } +impl Index for Grid { + type Output = Cell; + + #[inline] + fn index<'a>(&'a self, cursor: Cursor) -> &'a Cell { + &self.raw[cursor.y as usize][cursor.x as usize] + } +} + +impl IndexMut for Grid { + #[inline] + fn index_mut<'a>(&'a mut self, cursor: Cursor) -> &'a mut Cell { + &mut self.raw[cursor.y as usize][cursor.x as usize] + } +} + /// A row in the grid +#[derive(Debug, Clone)] pub struct Row(Vec); impl Row { pub fn new(columns: usize) -> Row { - Row(vec![Cell::new(""); columns]) + Row(vec![Cell::new(' '); columns]) } pub fn cols(&self) -> usize { @@ -86,15 +135,30 @@ impl Row { } } +impl Deref for Row { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Row { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl Index for Row { type Output = Cell; + #[inline] fn index<'a>(&'a self, index: usize) -> &'a Cell { &self.0[index] } } impl IndexMut for Row { + #[inline] fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Cell { &mut self.0[index] } diff --git a/src/main.rs b/src/main.rs index 1f945420..f13784bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![feature(range_contains)] #![feature(inclusive_range_syntax)] #![feature(io)] +#![feature(unicode)] extern crate fontconfig; extern crate freetype; @@ -11,10 +12,6 @@ extern crate glutin; extern crate cgmath; extern crate euclid; -use std::collections::HashMap; - -use std::io::{BufReader, Read, BufRead}; - #[macro_use] mod macros; @@ -25,10 +22,27 @@ mod grid; mod meter; mod tty; mod ansi; +mod term; + +use std::collections::HashMap; +use std::io::{BufReader, Read, BufRead, Write, BufWriter}; +use std::sync::Arc; +use std::fs::File; + +use std::os::unix::io::{FromRawFd, AsRawFd}; use renderer::{Glyph, QuadRenderer}; use text::FontDesc; use grid::Grid; +use term::Term; +use meter::Meter; + +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] +pub struct Rgb { + r: u8, + g: u8, + b: u8, +} mod gl { include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); @@ -37,9 +51,9 @@ mod gl { static INIT_LIST: &'static str = "abcdefghijklmnopqrstuvwxyz\ ABCDEFGHIJKLMNOPQRSTUVWXYZ\ 01234567890\ - ~`!@#$%^&*()[]{}-_=+\\|\"/?.,<>;:"; + ~`!@#$%^&*()[]{}-_=+\\|\"'/?.,<>;:█└│├─➜"; -type GlyphCache = HashMap; +type GlyphCache = HashMap; /// Render a string in a predefined location. Used for printing render time for profiling and /// optimization. @@ -47,13 +61,12 @@ fn render_string(s: &str, renderer: &QuadRenderer, glyph_cache: &GlyphCache, cell_width: u32, - color: &renderer::Rgb) + color: &Rgb) { let (mut x, mut y) = (200f32, 20f32); for c in s.chars() { - let s: String = c.escape_default().collect(); - if let Some(glyph) = glyph_cache.get(&s[..]) { + if let Some(glyph) = glyph_cache.get(&c) { renderer.render(glyph, x, y, color); } @@ -62,7 +75,11 @@ fn render_string(s: &str, } fn main() { - let window = glutin::Window::new().unwrap(); + let window = glutin::WindowBuilder::new() + .with_title("alacritty".into()) + .build() + .unwrap(); + let (width, height) = window.get_inner_size_pixels().unwrap(); unsafe { window.make_current().unwrap(); @@ -89,54 +106,19 @@ fn main() { let num_cols = grid::num_cells_axis(cell_width, sep_x, width); let num_rows = grid::num_cells_axis(cell_height, sep_y, height); - let mut cmd = tty::new(num_rows as u8, num_cols as u8); - - ::std::thread::spawn(move || { - for byte in cmd.bytes() { - let b = byte.unwrap(); - println!("{:02x}, {:?}", b, ::std::char::from_u32(b as u32)); - } - }); + let tty = tty::new(num_rows as u8, num_cols as u8); + tty.resize(num_rows as usize, num_cols as usize, width as usize, height as usize); + let mut reader = tty.reader(); + let mut writer = tty.writer(); println!("num_cols, num_rows = {}, {}", num_cols, num_rows); let mut grid = Grid::new(num_rows as usize, num_cols as usize); - // let contents = [ - // "for (row, line) in contents.iter().enumerate() {", - // " for (i, c) in line.chars().enumerate() {", - // " grid[row][i] = grid::Cell::new(Some(c.escape_default().collect()));", - // " }", - // "}"]; - - let contents = include_str!("grid.rs"); - let mut row = 0usize; - let mut col = 0; - - for (i, c) in contents.chars().enumerate() { - if c == '\n' { - row += 1; - col = 0; - continue; - } - - if row >= (num_rows as usize) { - break; - } - - if col >= grid.cols() { - continue; - } - - grid[row][col] = grid::Cell::new(c.escape_default().collect::()); - col += 1; - } - let mut glyph_cache = HashMap::new(); for c in INIT_LIST.chars() { let glyph = Glyph::new(&rasterizer.get_glyph(&desc, font_size, c)); - let string: String = c.escape_default().collect(); - glyph_cache.insert(string, glyph); + glyph_cache.insert(c, glyph); } unsafe { @@ -145,46 +127,102 @@ fn main() { gl::Enable(gl::MULTISAMPLE); } - let renderer = QuadRenderer::new(width, height); + let (chars_tx, chars_rx) = ::std::sync::mpsc::channel(); + ::std::thread::spawn(move || { + for c in reader.chars() { + let c = c.unwrap(); + chars_tx.send(c); + } + }); + + let renderer = QuadRenderer::new(width, height); + let mut terminal = Term::new(tty, grid); + let mut meter = Meter::new(); + + let mut pty_parser = ansi::Parser::new(); - let mut meter = meter::Meter::new(); 'main_loop: loop { - for event in window.poll_events() { - match event { - glutin::Event::Closed => break 'main_loop, - _ => () + // Handle keyboard/mouse input and other window events + { + let mut writer = BufWriter::new(&writer); + for event in window.poll_events() { + match event { + glutin::Event::Closed => break 'main_loop, + glutin::Event::ReceivedCharacter(c) => { + let encoded = c.encode_utf8(); + writer.write(encoded.as_slice()); + }, + glutin::Event::KeyboardInput(state, _code, key) => { + match state { + glutin::ElementState::Pressed => { + match key { + Some(glutin::VirtualKeyCode::Up) => { + writer.write("\x1b[A".as_bytes()); + }, + Some(glutin::VirtualKeyCode::Down) => { + writer.write("\x1b[B".as_bytes()); + }, + Some(glutin::VirtualKeyCode::Left) => { + writer.write("\x1b[D".as_bytes()); + }, + Some(glutin::VirtualKeyCode::Right) => { + writer.write("\x1b[C".as_bytes()); + }, + _ => (), + } + }, + _ => (), + } + }, + _ => () + } } } + while let Ok(c) = chars_rx.try_recv() { + pty_parser.advance(&mut terminal, c); + } + unsafe { gl::ClearColor(0.0, 0.0, 0.00, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT); } { - let color = renderer::Rgb { r: 0.917, g: 0.917, b: 0.917 }; let _sampler = meter.sampler(); + // Draw the grid + let grid = terminal.grid(); for i in 0..grid.rows() { let row = &grid[i]; for j in 0..row.cols() { let cell = &row[j]; - if !cell.character.is_empty() { - if let Some(glyph) = glyph_cache.get(&cell.character[..]) { + if cell.c != ' ' { + if let Some(glyph) = glyph_cache.get(&cell.c) { let y = (cell_height as f32 + sep_y as f32) * (i as f32); let x = (cell_width as f32 + sep_x as f32) * (j as f32); let y_inverted = (height as f32) - y - (cell_height as f32); - renderer.render(glyph, x, y_inverted, &color); + renderer.render(glyph, x, y_inverted, &cell.fg); } } } } + + // Also draw the cursor + if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) { + let y = (cell_height as f32 + sep_y as f32) * (terminal.cursor_y() as f32); + let x = (cell_width as f32 + sep_x as f32) * (terminal.cursor_x() as f32); + + let y_inverted = (height as f32) - y - (cell_height as f32); + + renderer.render(glyph, x, y_inverted, &term::DEFAULT_FG); + } } let timing = format!("{:.3} usec", meter.average()); - let color = renderer::Rgb { r: 0.835, g: 0.306, b: 0.325 }; + let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; render_string(&timing[..], &renderer, &glyph_cache, cell_width, &color); window.swap_buffers().unwrap(); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b88357b3..9495d8a0 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -32,12 +32,7 @@ pub struct PackedVertex { v: f32, } -#[derive(Debug)] -pub struct Rgb { - pub r: f32, - pub g: f32, - pub b: f32, -} +use super::Rgb; impl QuadRenderer { // TODO should probably hand this a transform instead of width/height @@ -103,7 +98,7 @@ impl QuadRenderer { self.program.activate(); unsafe { // set color - gl::Uniform3f(self.program.u_color, color.r, color.g, color.b); + gl::Uniform3i(self.program.u_color, color.r as i32, color.g as i32, color.b as i32); } let rect = get_rect(glyph, x, y); diff --git a/src/term.rs b/src/term.rs new file mode 100644 index 00000000..28d7c220 --- /dev/null +++ b/src/term.rs @@ -0,0 +1,350 @@ +/// Exports the `Term` type which is a high-level API for the Grid +use std::sync::Arc; + +use ansi::{self, Attr, DebugHandler}; +use grid::Grid; +use tty; +use ::Rgb; + +/// tomorrow night bright +/// +/// because contrast +pub static COLORS: &'static [Rgb] = &[ + Rgb {r: 0x00, g: 0x00, b: 0x00}, // Black + Rgb {r: 0xd5, g: 0x4e, b: 0x53}, // Red + Rgb {r: 0xb9, g: 0xca, b: 0x4a}, // Green + Rgb {r: 0xe6, g: 0xc5, b: 0x47}, // Yellow + Rgb {r: 0x7a, g: 0xa6, b: 0xda}, // Blue + Rgb {r: 0xc3, g: 0x97, b: 0xd8}, // Magenta + Rgb {r: 0x70, g: 0xc0, b: 0xba}, // Cyan + Rgb {r: 0x42, g: 0x42, b: 0x42}, // White + Rgb {r: 0x66, g: 0x66, b: 0x66}, // Bright black + Rgb {r: 0xff, g: 0x33, b: 0x34}, // Bright red + Rgb {r: 0x9e, g: 0xc4, b: 0x00}, // Bright green + Rgb {r: 0xe7, g: 0xc5, b: 0x47}, // Bright yellow + Rgb {r: 0x7a, g: 0xa6, b: 0xda}, // Bright blue + Rgb {r: 0xb7, g: 0x7e, b: 0xe0}, // Bright magenta + Rgb {r: 0x54, g: 0xce, b: 0xd6}, // Bright cyan + Rgb {r: 0x2a, g: 0x2a, b: 0x2a}, // Bright white +]; + +pub const CURSOR_SHAPE: char = '█'; + +pub const DEFAULT_FG: Rgb = Rgb { r: 0xea, g: 0xea, b: 0xea}; +pub const DEFAULT_BG: Rgb = Rgb { r: 0, g: 0, b: 0}; +pub const TAB_SPACES: usize = 8; + +/// State for cursor +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Cursor { + pub x: u16, + pub y: u16, +} + +impl Default for Cursor { + fn default() -> Cursor { + Cursor { x: 0, y: 0 } + } +} + +impl Cursor { + pub fn goto(&mut self, x: u16, y: u16) { + self.x = x; + self.y = y; + } + + pub fn advance(&mut self, rows: i64, cols: i64) { + self.x = (self.x as i64 + cols) as u16; + self.y = (self.y as i64 + rows) as u16; + } +} + +struct Mover<'a> { + cursor: &'a mut Cursor, +} + +pub struct Term { + /// The grid + grid: Grid, + + /// Alternate grid + alt_grid: Grid, + + /// Alt is active + alt: bool, + + /// Reference to the underlying tty + tty: tty::Tty, + + /// The cursor + cursor: Cursor, + + /// Alt cursor + alt_cursor: Cursor, + + /// Active foreground color + fg: Rgb, + + /// Active background color + bg: Rgb, + + /// Tabstops + tabs: Vec +} + +impl Term { + pub fn new(tty: tty::Tty, grid: Grid) -> Term { + + let mut tabs = (0..grid.cols()).map(|i| i % TAB_SPACES == 0) + .collect::>(); + tabs[0] = false; + + let alt = grid.clone(); + + Term { + grid: grid, + alt_grid: alt, + alt: false, + cursor: Cursor::default(), + alt_cursor: Cursor::default(), + fg: DEFAULT_FG, + bg: DEFAULT_BG, + tty: tty, + tabs: tabs, + } + } + + pub fn grid(&self) -> &Grid { + &self.grid + } + + pub fn swap_alt(&mut self) { + self.alt = !self.alt; + ::std::mem::swap(&mut self.grid, &mut self.alt_grid); + ::std::mem::swap(&mut self.cursor, &mut self.alt_cursor); + + if self.alt { + self.grid.clear(); + } + } + + pub fn resize(&mut self) { + unimplemented!(); + } + + #[inline] + pub fn cursor_x(&self) -> u16 { + self.cursor.x + } + + #[inline] + pub fn cursor_y(&self) -> u16 { + self.cursor.y + } + + /// Set character in current cursor position + fn set_char(&mut self, c: char) { + let cell = &mut self.grid[self.cursor]; + cell.c = c; + cell.fg = self.fg; + cell.bg = self.bg; + } + + /// Advance to next line + fn newline_c(&mut self, count: u16) { + // TODO handle scroll + self.cursor.x = 0; + self.cursor.y += 1; + } +} + +impl ansi::Handler for Term { + /// A character to be displayed + #[inline] + fn input(&mut self, c: char) { + self.set_char(c); + self.cursor.x += 1; + } + + fn goto(&mut self, x: i64, y: i64) { + println!("goto: x={}, y={}", x, y); + self.cursor.goto(x as u16, y as u16); + } + fn goto_row(&mut self, y: i64) { println!("goto_row: {}", y); } + fn goto_col(&mut self, x: i64) { println!("goto_col: {}", x); } + fn insert_blank(&mut self, num: i64) { println!("insert_blank: {}", num); } + + fn move_up(&mut self, rows: i64) { + println!("move_up: {}", rows); + self.cursor.advance(-rows, 0); + } + + fn move_down(&mut self, rows: i64) { + println!("move_down: {}", rows); + self.cursor.advance(rows, 0); + } + + fn move_forward(&mut self, cols: i64) { + println!("move_forward: {}", cols); + self.cursor.advance(0, cols); + } + + fn move_backward(&mut self, spaces: i64) { + println!("move_backward: {}", spaces); + self.cursor.advance(0, -spaces); + } + + fn identify_terminal(&mut self) { println!("identify_terminal"); } + fn move_down_and_cr(&mut self, rows: i64) { println!("move_down_and_cr: {}", rows); } + fn move_up_and_cr(&mut self, rows: i64) { println!("move_up_and_cr: {}", rows); } + fn put_tab(&mut self, mut count: i64) { + println!("put_tab: {}", count); + + let mut x = self.cursor_x(); + while x < self.grid.cols() as u16 && count != 0 { + count -= 1; + loop { + if x == self.grid.cols() as u16 || self.tabs[x as usize] { + break; + } + x += 1; + } + } + + self.cursor.x = x; + } + + /// Backspace `count` characters + #[inline] + fn backspace(&mut self, count: i64) { + self.cursor.x -= 1; + self.set_char(' '); + } + + /// Carriage return + #[inline] + fn carriage_return(&mut self) { + self.cursor.x = 0; + } + + /// Linefeed + #[inline] + fn linefeed(&mut self) { + println!("linefeed"); + // TODO handle scroll? not clear what parts of this the pty handle + if self.cursor_y() + 1 == self.grid.rows() as u16 { + self.grid.feed(); + self.clear_line(ansi::LineClearMode::Right); + } else { + self.cursor.y += 1; + } + } + + /// Set current position as a tabstop + fn bell(&mut self) { println!("bell"); } + fn substitute(&mut self) { println!("substitute"); } + fn newline(&mut self) { println!("newline"); } + fn set_horizontal_tabstop(&mut self) { println!("set_horizontal_tabstop"); } + fn scroll_up(&mut self, rows: i64) { println!("scroll_up: {}", rows); } + fn scroll_down(&mut self, rows: i64) { println!("scroll_down: {}", rows); } + fn insert_blank_lines(&mut self, count: i64) { println!("insert_blank_lines: {}", count); } + fn delete_lines(&mut self, count: i64) { println!("delete_lines: {}", count); } + fn erase_chars(&mut self, count: i64) { println!("erase_chars: {}", count); } + fn delete_chars(&mut self, count: i64) { println!("delete_chars: {}", count); } + fn move_backward_tabs(&mut self, count: i64) { println!("move_backward_tabs: {}", count); } + fn move_forward_tabs(&mut self, count: i64) { println!("move_forward_tabs: {}", count); } + fn save_cursor_position(&mut self) { println!("save_cursor_position"); } + fn restore_cursor_position(&mut self) { println!("restore_cursor_position"); } + fn clear_line(&mut self, mode: ansi::LineClearMode) { + println!("clear_line: {:?}", mode); + match mode { + ansi::LineClearMode::Right => { + let cols = self.grid.cols(); + let row = &mut self.grid[self.cursor.y as usize]; + let start = self.cursor.x as usize; + for col in start..cols { + row[col].c = ' '; + } + }, + _ => (), + } + } + fn clear_screen(&mut self, mode: ansi::ClearMode) { + println!("clear_screen: {:?}", mode); + match mode { + ansi::ClearMode::Below => { + let start = self.cursor_y() as usize; + let end = self.grid.rows(); + for i in start..end { + let row = &mut self.grid[i]; + for cell in row.iter_mut() { + cell.c = ' '; + } + } + }, + ansi::ClearMode::All => { + self.grid.clear(); + }, + _ => { + panic!("ansi::ClearMode::Above not implemented"); + } + } + } + fn clear_tabs(&mut self, mode: ansi::TabulationClearMode) { println!("clear_tabs: {:?}", mode); } + fn reset_state(&mut self) { println!("reset_state"); } + fn reverse_index(&mut self) { + println!("reverse_index"); + // if cursor is at the top + if self.cursor.y == 0 { + self.grid.unfeed(); + } else { + // can't wait for nonlexical lifetimes.. omg borrowck + let x = self.cursor.x; + let y = self.cursor.y; + self.cursor.goto(x, y - 1); + } + } + + /// set a terminal attribute + fn terminal_attribute(&mut self, attr: Attr) { + match attr { + Attr::DefaultForeground => { + self.fg = DEFAULT_FG; + }, + Attr::DefaultBackground => { + self.bg = DEFAULT_BG; + }, + Attr::Foreground(named_color) => { + self.fg = COLORS[named_color as usize]; + }, + Attr::Background(named_color) => { + self.bg = COLORS[named_color as usize]; + }, + Attr::Reset => { + self.fg = DEFAULT_FG; + self.bg = DEFAULT_BG; + } + _ => { + println!("Term got unhandled attr: {:?}", attr); + } + } + } + + fn set_mode(&mut self, mode: ansi::Mode) { + println!("set_mode: {:?}", mode); + match mode { + ansi::Mode::SwapScreenAndSetRestoreCursor => { + self.swap_alt(); + } + } + } + + fn unset_mode(&mut self, mode: ansi::Mode) { + println!("unset_mode: {:?}", mode); + match mode { + ansi::Mode::SwapScreenAndSetRestoreCursor => { + self.swap_alt(); + } + } + } +} diff --git a/src/tty.rs b/src/tty.rs index 2f2a84b5..f9704bc5 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -189,7 +189,7 @@ fn execsh() -> ! { } /// Create a new tty and return a handle to interact with it. -pub fn new(rows: u8, cols: u8) -> File { +pub fn new(rows: u8, cols: u8) -> Tty { let (master, slave) = openpty(rows, cols); match fork() { @@ -221,12 +221,49 @@ pub fn new(rows: u8, cols: u8) -> File { libc::close(slave); } - // XXX should this really return a file? - // How should this be done? Could build a File::from_raw_fd, or maybe implement a custom - // type that can be used in a mio event loop? For now, just do the file option. - unsafe { - File::from_raw_fd(master) - } + Tty { fd: master } + } + } +} + +pub struct Tty { + fd: c_int, +} + +impl Tty { + /// Get reader for the TTY + /// + /// XXX File is a bad abstraction here; it closes the fd on drop + pub fn reader(&self) -> File { + unsafe { + File::from_raw_fd(self.fd) + } + } + + /// Get writer for the TTY + /// + /// XXX File is a bad abstraction here; it closes the fd on drop + pub fn writer(&self) -> File { + unsafe { + File::from_raw_fd(self.fd) + } + } + + pub fn resize(&self, rows: usize, cols: usize, px_x: usize, px_y: usize) { + + let win = winsize { + ws_row: rows as libc::c_ushort, + ws_col: cols as libc::c_ushort, + ws_xpixel: px_x as libc::c_ushort, + ws_ypixel: px_y as libc::c_ushort, + }; + + let res = unsafe { + libc::ioctl(self.fd, libc::TIOCSWINSZ, &win as *const _) + }; + + if res < 0 { + die!("ioctl TIOCSWINSZ failed: {}", errno()); } } }