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).
This commit is contained in:
Joe Wilm 2016-05-30 20:44:37 -07:00
parent 70b0423a31
commit 30ec145109
No known key found for this signature in database
GPG Key ID: 39B57C6972F518DA
7 changed files with 717 additions and 181 deletions

View File

@ -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);
}

View File

@ -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,
}
/// 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<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());
}
}
}

View File

@ -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]
}

View File

@ -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 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();

View File

@ -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);

350
src/term.rs Normal file
View File

@ -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();
}
}
}
}

View File

@ -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());
}
}
}