From 30ec14510935d46e7454863f9a4e63e53bf7728c Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 30 May 2016 20:44:37 -0700 Subject: 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). --- src/main.rs | 158 +++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 60 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 1f945420..f13784bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![feature(range_contains)] #![feature(inclusive_range_syntax)] #![feature(io)] +#![feature(unicode)] extern crate fontconfig; extern crate freetype; @@ -11,10 +12,6 @@ extern crate glutin; extern crate cgmath; extern crate euclid; -use std::collections::HashMap; - -use std::io::{BufReader, Read, BufRead}; - #[macro_use] mod macros; @@ -25,10 +22,27 @@ mod grid; mod meter; mod tty; mod ansi; +mod term; + +use std::collections::HashMap; +use std::io::{BufReader, Read, BufRead, Write, BufWriter}; +use std::sync::Arc; +use std::fs::File; + +use std::os::unix::io::{FromRawFd, AsRawFd}; use renderer::{Glyph, QuadRenderer}; use text::FontDesc; use grid::Grid; +use term::Term; +use meter::Meter; + +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] +pub struct Rgb { + r: u8, + g: u8, + b: u8, +} mod gl { include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); @@ -37,9 +51,9 @@ mod gl { static INIT_LIST: &'static str = "abcdefghijklmnopqrstuvwxyz\ ABCDEFGHIJKLMNOPQRSTUVWXYZ\ 01234567890\ - ~`!@#$%^&*()[]{}-_=+\\|\"/?.,<>;:"; + ~`!@#$%^&*()[]{}-_=+\\|\"'/?.,<>;:█└│├─➜"; -type GlyphCache = HashMap; +type GlyphCache = HashMap; /// Render a string in a predefined location. Used for printing render time for profiling and /// optimization. @@ -47,13 +61,12 @@ fn render_string(s: &str, renderer: &QuadRenderer, glyph_cache: &GlyphCache, cell_width: u32, - color: &renderer::Rgb) + color: &Rgb) { let (mut x, mut y) = (200f32, 20f32); for c in s.chars() { - let s: String = c.escape_default().collect(); - if let Some(glyph) = glyph_cache.get(&s[..]) { + if let Some(glyph) = glyph_cache.get(&c) { renderer.render(glyph, x, y, color); } @@ -62,7 +75,11 @@ fn render_string(s: &str, } fn main() { - let window = glutin::Window::new().unwrap(); + let window = glutin::WindowBuilder::new() + .with_title("alacritty".into()) + .build() + .unwrap(); + let (width, height) = window.get_inner_size_pixels().unwrap(); unsafe { window.make_current().unwrap(); @@ -89,54 +106,19 @@ fn main() { let num_cols = grid::num_cells_axis(cell_width, sep_x, width); let num_rows = grid::num_cells_axis(cell_height, sep_y, height); - let mut cmd = tty::new(num_rows as u8, num_cols as u8); - - ::std::thread::spawn(move || { - for byte in cmd.bytes() { - let b = byte.unwrap(); - println!("{:02x}, {:?}", b, ::std::char::from_u32(b as u32)); - } - }); + let tty = tty::new(num_rows as u8, num_cols as u8); + tty.resize(num_rows as usize, num_cols as usize, width as usize, height as usize); + let mut reader = tty.reader(); + let mut writer = tty.writer(); println!("num_cols, num_rows = {}, {}", num_cols, num_rows); let mut grid = Grid::new(num_rows as usize, num_cols as usize); - // let contents = [ - // "for (row, line) in contents.iter().enumerate() {", - // " for (i, c) in line.chars().enumerate() {", - // " grid[row][i] = grid::Cell::new(Some(c.escape_default().collect()));", - // " }", - // "}"]; - - let contents = include_str!("grid.rs"); - let mut row = 0usize; - let mut col = 0; - - for (i, c) in contents.chars().enumerate() { - if c == '\n' { - row += 1; - col = 0; - continue; - } - - if row >= (num_rows as usize) { - break; - } - - if col >= grid.cols() { - continue; - } - - grid[row][col] = grid::Cell::new(c.escape_default().collect::()); - col += 1; - } - let mut glyph_cache = HashMap::new(); for c in INIT_LIST.chars() { let glyph = Glyph::new(&rasterizer.get_glyph(&desc, font_size, c)); - let string: String = c.escape_default().collect(); - glyph_cache.insert(string, glyph); + glyph_cache.insert(c, glyph); } unsafe { @@ -145,46 +127,102 @@ fn main() { gl::Enable(gl::MULTISAMPLE); } + let (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(); -- cgit