diff options
-rw-r--r-- | res/text.f.glsl | 7 | ||||
-rw-r--r-- | src/ansi.rs | 233 | ||||
-rw-r--r-- | src/grid.rs | 88 | ||||
-rw-r--r-- | src/main.rs | 158 | ||||
-rw-r--r-- | src/renderer/mod.rs | 9 | ||||
-rw-r--r-- | src/term.rs | 350 | ||||
-rw-r--r-- | src/tty.rs | 51 |
7 files changed, 716 insertions, 180 deletions
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<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()); } } } 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<S>(c: S) -> Cell - where S: Into<String> - { + 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<Row>, + raw: VecDeque<Row>, /// 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<usize> for Grid { type Output = Row; + #[inline] fn index<'a>(&'a self, index: usize) -> &'a Row { &self.raw[index] } } impl IndexMut<usize> for Grid { + #[inline] fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Row { &mut self.raw[index] } } +impl Index<Cursor> 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<Cursor> 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<Cell>); 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<Cell>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Row { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl Index<usize> for Row { type Output = Cell; + #[inline] fn index<'a>(&'a self, index: usize) -> &'a Cell { &self.0[index] } } impl IndexMut<usize> 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<String, renderer::Glyph>; +type GlyphCache = HashMap<char, renderer::Glyph>; /// 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::<String>()); - 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 (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<bool> +} + +impl Term { + pub fn new(tty: tty::Tty, grid: Grid) -> Term { + + let mut tabs = (0..grid.cols()).map(|i| i % TAB_SPACES == 0) + .collect::<Vec<bool>>(); + 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(); + } + } + } +} @@ -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()); } } } |