summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--res/text.f.glsl7
-rw-r--r--src/ansi.rs233
-rw-r--r--src/grid.rs88
-rw-r--r--src/main.rs158
-rw-r--r--src/renderer/mod.rs9
-rw-r--r--src/term.rs350
-rw-r--r--src/tty.rs51
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();
+ }
+ }
+ }
+}
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());
}
}
}