Add support for recording/running ref tests

Ref tests use a recording of the terminal protocol and a serialization
of the grid state to check that the parsing and action handling systems
produce the correct result. Ref tests may be recorded by running
alacritty with `--ref-test` and closing the terminal by using the window
"X" button. At that point, the recording is fully written to disk, and a
serialization of important state is recorded. Those files should be
moved to an appropriate folder in the `tests/ref/` tree, and the
`ref_test!` macro invocation should be updated accordingly.

A couple of changes were necessary to make this work:

* Ref tests shouldn't create a pty; the pty was refactored out of the
  `Term` type.
* Repeatable lines/cols were needed; on startup, the terminal is resized
* by default to 80x24 though that may be changed by passing
  `--dimensions w h`.
* Calculating window size based on desired rows/columns and font metrics
  required making load_font callable multiple times.
* Refactor types into library crate so they may be imported in an
  integration test.
* A whole bunch of types needed symmetric serialization and
  deserialization. Mostly this was just adding derives, but the custom
  deserialization of Rgb had to change to a deserialize_with function.

This initially adds one ref test as a sanity check, and more will be
added in subsequent commits. This initial ref tests just starts the
terminal and runs `ll`.
This commit is contained in:
Joe Wilm 2016-11-19 16:16:20 -08:00
parent d97996e19d
commit 66dbd29cd1
21 changed files with 448 additions and 163 deletions

1
Cargo.lock generated
View File

@ -15,6 +15,7 @@ dependencies = [
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vte 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -5,6 +5,11 @@ authors = ["Joe Wilm <joe@jwilm.com>"]
license = "Apache-2.0"
build = "build.rs"
[[bin]]
doc = false
path = "src/main.rs"
name = "alacritty"
[dependencies]
libc = "*"
cgmath = "0.7"
@ -18,6 +23,7 @@ serde_yaml = "0.5"
serde_derive = "0.8"
vte = "0.1.2"
mio = "0.6"
serde_json = "*"
copypasta = { path = "./copypasta" }
[features]
@ -32,5 +38,6 @@ gl_generator = "0.5"
git = "https://github.com/jwilm/glutin"
rev = "78838c1e1497dc8a1b1c8f69da7a6f3cd7da15c1"
[profile.release]
debug = true

View File

@ -75,6 +75,7 @@ pub struct Descriptor {
/// Given a fontdesc, can rasterize fonts.
pub struct Rasterizer {
fonts: HashMap<FontKey, Font>,
keys: HashMap<(FontDesc, Size), FontKey>,
device_pixel_ratio: f32,
}
@ -83,6 +84,7 @@ impl Rasterizer {
println!("device_pixel_ratio: {}", device_pixel_ratio);
Rasterizer {
fonts: HashMap::new(),
keys: HashMap::new(),
device_pixel_ratio: device_pixel_ratio,
}
}
@ -100,12 +102,19 @@ impl Rasterizer {
}
pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option<FontKey> {
self.get_font(desc, size)
.map(|font| {
let key = FontKey::next();
self.fonts.insert(key, font);
self.keys
.get(&(desc.to_owned(), size))
.map(|k| *k)
.or_else(|| {
self.get_font(desc, size)
.map(|font| {
let key = FontKey::next();
key
self.fonts.insert(key, font);
self.keys.insert((desc.clone(), size), key);
key
})
})
}

View File

@ -29,6 +29,7 @@ pub struct Rasterizer {
faces: HashMap<FontKey, Face<'static>>,
library: Library,
system_fonts: HashMap<String, Family>,
keys: HashMap<FontDesc, FontKey>,
dpi_x: u32,
dpi_y: u32,
dpr: f32,
@ -51,6 +52,7 @@ impl Rasterizer {
Rasterizer {
system_fonts: get_font_families(),
faces: HashMap::new(),
keys: HashMap::new(),
library: library,
dpi_x: dpi_x as u32,
dpi_y: dpi_y as u32,
@ -77,11 +79,16 @@ impl Rasterizer {
}
pub fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Option<FontKey> {
self.get_face(desc)
.map(|face| {
let key = FontKey::next();
self.faces.insert(key, face);
key
self.keys
.get(&desc.to_owned())
.map(|k| *k)
.or_else(|| {
self.get_face(desc)
.map(|face| {
let key = FontKey::next();
self.faces.insert(key, face);
key
})
})
}

View File

@ -303,7 +303,7 @@ pub enum TabulationClearMode {
///
/// The order here matters since the enum should be castable to a `usize` for
/// indexing a color list.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Color {
/// Black
Black = 0,

View File

@ -463,7 +463,9 @@ pub struct Colors {
#[derive(Debug, Deserialize)]
pub struct PrimaryColors {
#[serde(deserialize_with = "rgb_from_hex")]
background: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
foreground: Rgb,
}
@ -501,45 +503,45 @@ impl Default for Colors {
/// The normal or bright colors section of config
#[derive(Debug, Deserialize)]
pub struct AnsiColors {
#[serde(deserialize_with = "rgb_from_hex")]
black: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
red: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
green: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
yellow: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
blue: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
magenta: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
cyan: Rgb,
#[serde(deserialize_with = "rgb_from_hex")]
white: Rgb,
}
impl serde::de::Deserialize for Rgb {
fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error>
where D: serde::de::Deserializer
{
use std::marker::PhantomData;
/// Deserialize an Rgb from a hex string
///
/// This is *not* the deserialize impl for Rgb since we want a symmetric
/// serialize/deserialize impl for ref tests.
fn rgb_from_hex<D>(deserializer: &mut D) -> ::std::result::Result<Rgb, D::Error>
where D: de::Deserializer
{
struct RgbVisitor;
struct StringVisitor<__D> {
_marker: PhantomData<__D>,
}
impl ::serde::de::Visitor for RgbVisitor {
type Value = Rgb;
impl<__D> ::serde::de::Visitor for StringVisitor<__D>
where __D: ::serde::de::Deserializer
fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<Rgb, E>
where E: ::serde::de::Error
{
type Value = String;
fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<Self::Value, E>
where E: ::serde::de::Error
{
Ok(value.to_owned())
}
Rgb::from_str(&value[..])
.map_err(|_| E::custom("failed to parse rgb; expect 0xrrggbb"))
}
deserializer
.deserialize_f64(StringVisitor::<D>{ _marker: PhantomData })
.and_then(|v| {
Rgb::from_str(&v[..])
.map_err(|_| D::Error::custom("failed to parse rgb; expect 0xrrggbb"))
})
}
deserializer.deserialize_str(RgbVisitor)
}
impl Rgb {

View File

@ -1,12 +1,14 @@
//! Process window events
use std::fs::File;
use std::io::Write;
use std::sync::{Arc, mpsc};
use serde_json as json;
use glutin;
use input;
use sync::FairMutex;
use term::Term;
use util::encode_char;
use config::Config;
/// The event processor
@ -15,6 +17,7 @@ pub struct Processor<N> {
input_processor: input::Processor,
terminal: Arc<FairMutex<Term>>,
resize_tx: mpsc::Sender<(u32, u32)>,
ref_test: bool,
}
impl<N: input::Notify> Processor<N> {
@ -27,18 +30,43 @@ impl<N: input::Notify> Processor<N> {
terminal: Arc<FairMutex<Term>>,
resize_tx: mpsc::Sender<(u32, u32)>,
config: &Config,
ref_test: bool,
) -> Processor<N> {
Processor {
notifier: notifier,
terminal: terminal,
input_processor: input::Processor::new(config),
resize_tx: resize_tx,
ref_test: ref_test,
}
}
fn handle_event(&mut self, event: glutin::Event) {
match event {
glutin::Event::Closed => panic!("window closed"), // TODO ...
glutin::Event::Closed => {
if self.ref_test {
// dump grid state
let terminal = self.terminal.lock();
let grid = terminal.grid();
let serialized_grid = json::to_string(&grid)
.expect("serialize grid");
let serialized_size = json::to_string(terminal.size_info())
.expect("serialize size");
File::create("./grid.json")
.and_then(|mut f| f.write_all(serialized_grid.as_bytes()))
.expect("write grid.json");
File::create("./size.json")
.and_then(|mut f| f.write_all(serialized_size.as_bytes()))
.expect("write size.json");
}
// FIXME
panic!("window closed");
},
glutin::Event::Resized(w, h) => {
self.resize_tx.send((w, h)).expect("send new size");
// Acquire term lock

View File

@ -1,7 +1,8 @@
//! The main event loop which performs I/O on the pseudoterminal
use std::borrow::Cow;
use std::collections::VecDeque;
use std::io::{self, ErrorKind};
use std::io::{self, ErrorKind, Write};
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
@ -34,6 +35,7 @@ pub struct EventLoop<Io> {
terminal: Arc<FairMutex<Term>>,
proxy: ::glutin::WindowProxy,
signal_flag: Flag,
ref_test: bool,
}
/// Helper type which tracks how much of a buffer has been written.
@ -130,6 +132,7 @@ impl<Io> EventLoop<Io>
proxy: ::glutin::WindowProxy,
signal_flag: Flag,
pty: Io,
ref_test: bool,
) -> EventLoop<Io> {
let (tx, rx) = ::mio::channel::channel();
EventLoop {
@ -139,7 +142,8 @@ impl<Io> EventLoop<Io>
rx: rx,
terminal: terminal,
proxy: proxy,
signal_flag: signal_flag
signal_flag: signal_flag,
ref_test: ref_test,
}
}
@ -174,11 +178,15 @@ impl<Io> EventLoop<Io>
}
#[inline]
fn pty_read(&mut self, state: &mut State, buf: &mut [u8]) {
fn pty_read<W: Write>(&mut self, state: &mut State, buf: &mut [u8], mut writer: Option<&mut W>) {
loop {
match self.pty.read(&mut buf[..]) {
Ok(0) => break,
Ok(got) => {
writer = writer.map(|w| {
w.write_all(&buf[..got]).unwrap(); w
});
let mut terminal = self.terminal.lock();
for byte in &buf[..got] {
state.parser.advance(&mut *terminal, *byte);
@ -252,6 +260,14 @@ impl<Io> EventLoop<Io>
let mut events = Events::with_capacity(1024);
let mut pipe = if self.ref_test {
let file = File::create("./alacritty.recording")
.expect("create alacritty recording");
Some(file)
} else {
None
};
'event_loop: loop {
self.poll.poll(&mut events, None).expect("poll ok");
@ -262,7 +278,7 @@ impl<Io> EventLoop<Io>
let kind = event.kind();
if kind.is_readable() {
self.pty_read(&mut state, &mut buf);
self.pty_read(&mut state, &mut buf, pipe.as_mut());
if ::tty::process_should_exit() {
break 'event_loop;
}

View File

@ -29,7 +29,7 @@ use std::slice::{self, Iter, IterMut};
use index::{self, Cursor};
/// Represents the terminal display contents
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct Grid<T> {
/// Lines in the grid. Each row holds a list of cells corresponding to the
/// columns in that row.
@ -221,7 +221,7 @@ impl<'cursor, T> IndexMut<&'cursor Cursor> for Grid<T> {
}
/// A row in the grid
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Row<T>(Vec<T>);
impl<T: Clone> Row<T> {

View File

@ -21,7 +21,7 @@ use std::mem;
use std::ops::{self, Deref, Add};
/// Index in the grid using row, column notation
#[derive(Debug, Clone, Default, Eq, PartialEq)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct Cursor {
pub line: Line,
pub col: Column,
@ -30,7 +30,7 @@ pub struct Cursor {
/// A line
///
/// Newtype to avoid passing values incorrectly
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Line(pub usize);
impl fmt::Display for Line {
@ -42,7 +42,7 @@ impl fmt::Display for Line {
/// A column
///
/// Newtype to avoid passing values incorrectly
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Column(pub usize);
impl fmt::Display for Column {

View File

@ -31,8 +31,7 @@ use glutin::{Mods, mods};
use config::Config;
use event_loop;
use term::mode::{self, TermMode};
use util::encode_char;
use term::mode::{TermMode};
/// Processes input from glutin.
///

98
src/lib.rs Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Alacritty - The GPU Enhanced Terminal
#![feature(question_mark)]
#![feature(range_contains)]
#![feature(inclusive_range_syntax)]
#![feature(drop_types_in_const)]
#![feature(unicode)]
#![feature(step_trait)]
#![feature(core_intrinsics)]
#![allow(stable_features)] // lying about question_mark because 1.14.0 isn't released!
#![feature(proc_macro)]
#[macro_use]
extern crate serde_derive;
extern crate cgmath;
extern crate copypasta;
extern crate errno;
extern crate font;
extern crate glutin;
extern crate libc;
extern crate mio;
extern crate notify;
extern crate parking_lot;
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;
extern crate vte;
#[macro_use]
extern crate bitflags;
#[macro_use]
pub mod macros;
pub mod event;
pub mod event_loop;
pub mod index;
pub mod input;
pub mod meter;
pub mod renderer;
pub mod sync;
pub mod term;
pub mod tty;
pub mod util;
pub mod ansi;
pub mod config;
pub mod grid;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
pub use grid::Grid;
pub use term::Term;
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize, Deserialize)]
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
pub mod gl {
#![allow(non_upper_case_globals)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
#[derive(Clone)]
pub struct Flag(pub Arc<AtomicBool>);
impl Flag {
pub fn new(initial_value: bool) -> Flag {
Flag(Arc::new(AtomicBool::new(initial_value)))
}
#[inline]
pub fn get(&self) -> bool {
self.0.load(Ordering::Acquire)
}
#[inline]
pub fn set(&self, value: bool) {
self.0.store(value, Ordering::Release)
}
}

View File

@ -14,12 +14,8 @@
//
//! Alacritty - The GPU Enhanced Terminal
#![feature(question_mark)]
#![feature(range_contains)]
#![feature(inclusive_range_syntax)]
#![feature(drop_types_in_const)]
#![feature(unicode)]
#![feature(step_trait)]
#![feature(core_intrinsics)]
#![allow(stable_features)] // lying about question_mark because 1.14.0 isn't released!
#![feature(proc_macro)]
@ -27,6 +23,8 @@
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate alacritty;
extern crate cgmath;
extern crate copypasta;
extern crate errno;
@ -37,64 +35,34 @@ extern crate mio;
extern crate notify;
extern crate parking_lot;
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;
extern crate vte;
#[macro_use]
extern crate bitflags;
#[macro_use]
mod macros;
mod event;
mod event_loop;
mod index;
mod input;
mod meter;
mod renderer;
mod sync;
mod term;
mod tty;
mod util;
pub mod ansi;
pub mod config;
pub mod grid;
use std::sync::{mpsc, Arc};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::Ordering;
use parking_lot::{MutexGuard};
use event_loop::EventLoop;
use config::Config;
use meter::Meter;
use renderer::{QuadRenderer, GlyphCache};
use sync::FairMutex;
use term::Term;
use tty::process_should_exit;
use alacritty::Flag;
use alacritty::Rgb;
use alacritty::config::{self, Config};
use alacritty::event;
use alacritty::gl;
use alacritty::input;
use alacritty::meter::Meter;
use alacritty::renderer::{QuadRenderer, GlyphCache};
use alacritty::sync::FairMutex;
use alacritty::term::{self, Term};
use alacritty::tty::{self, Pty, process_should_exit};
use alacritty::event_loop::EventLoop;
/// Channel used by resize handling on mac
static mut RESIZE_CALLBACK: Option<Box<Fn(u32, u32)>> = None;
#[derive(Clone)]
pub struct Flag(Arc<AtomicBool>);
impl Flag {
pub fn new(initial_value: bool) -> Flag {
Flag(Arc::new(AtomicBool::new(initial_value)))
}
#[inline]
pub fn get(&self) -> bool {
self.0.load(Ordering::Acquire)
}
#[inline]
pub fn set(&self, value: bool) {
self.0.store(value, Ordering::Release)
}
}
/// Resize handling for Mac
fn window_resize_handler(width: u32, height: u32) {
unsafe {
@ -102,18 +70,6 @@ fn window_resize_handler(width: u32, height: u32) {
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub struct Rgb {
r: u8,
g: u8,
b: u8,
}
mod gl {
#![allow(non_upper_case_globals)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
fn main() {
// Load configuration
let (config, config_path) = match Config::load() {
@ -126,6 +82,27 @@ fn main() {
Ok((config, path)) => (config, Some(path)),
};
let mut ref_test = false;
let mut columns = 80;
let mut lines = 24;
let mut args_iter = ::std::env::args();
while let Some(arg) = args_iter.next() {
match &arg[..] {
// Generate ref test
"--ref-test" => ref_test = true,
// Set dimensions
"-d" | "--dimensions" => {
args_iter.next()
.map(|w| w.parse().map(|w| columns = w));
args_iter.next()
.map(|h| h.parse().map(|h| lines = h));
},
// ignore unexpected
_ => (),
}
}
let font = config.font();
let dpi = config.dpi();
let render_timer = config.render_timer();
@ -145,7 +122,7 @@ fn main() {
let _ = unsafe { window.make_current() };
unsafe {
gl::Viewport(0, 0, width as i32, height as i32);
// gl::Viewport(0, 0, width as i32, height as i32);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
gl::Enable(gl::MULTISAMPLE);
@ -176,15 +153,30 @@ fn main() {
let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32;
let cell_height = (metrics.line_height + font.offset().y() as f64) as u32;
// Resize window to be 80 col x 24 lines
let width = cell_width * columns + 4;
let height = cell_height * lines + 4;
println!("set_inner_size: {} x {}", width, height);
// Is this in points?
let width_pts = (width as f32 / dpr) as u32;
let height_pts = (height as f32 / dpr) as u32;
println!("set_inner_size: {} x {}; pts: {} x {}", width, height, width_pts, height_pts);
window.set_inner_size(width_pts, height_pts);
renderer.resize(width as _, height as _);
println!("Cell Size: ({} x {})", cell_width, cell_height);
let terminal = Term::new(
width as f32,
height as f32,
cell_width as f32,
cell_height as f32
);
let pty_io = terminal.tty().reader();
let size = term::SizeInfo {
width: width as f32,
height: height as f32,
cell_width: cell_width as f32,
cell_height: cell_height as f32
};
let terminal = Term::new(size);
let pty = tty::new(size.lines(), size.cols());
pty.resize(size.lines(), size.cols(), size.width as usize, size.height as usize);
let pty_io = pty.reader();
let (tx, rx) = mpsc::channel();
@ -215,6 +207,7 @@ fn main() {
window.create_window_proxy(),
signal_flag.clone(),
pty_io,
ref_test,
);
let loop_tx = event_loop.channel();
@ -226,7 +219,8 @@ fn main() {
renderer,
glyph_cache,
render_timer,
rx
rx,
pty
);
// Event processor
@ -234,7 +228,8 @@ fn main() {
input::LoopNotifier(loop_tx),
terminal.clone(),
tx,
&config
&config,
ref_test,
);
let (config_tx, config_rx) = mpsc::channel();
@ -302,6 +297,7 @@ struct Display {
render_timer: bool,
rx: mpsc::Receiver<(u32, u32)>,
meter: Meter,
pty: Pty,
}
impl Display {
@ -314,7 +310,8 @@ impl Display {
renderer: QuadRenderer,
glyph_cache: GlyphCache,
render_timer: bool,
rx: mpsc::Receiver<(u32, u32)>)
rx: mpsc::Receiver<(u32, u32)>,
pty: Pty)
-> Display
{
Display {
@ -324,6 +321,7 @@ impl Display {
render_timer: render_timer,
rx: rx,
meter: Meter::new(),
pty: pty,
}
}
@ -350,6 +348,8 @@ impl Display {
// available
if let Some((w, h)) = new_size.take() {
terminal.resize(w as f32, h as f32);
let size = terminal.size_info();
self.pty.resize(size.lines(), size.cols(), w as _, h as _);
self.renderer.resize(w as i32, h as i32);
}
@ -369,7 +369,7 @@ impl Display {
// Draw render timer
if self.render_timer {
let timing = format!("{:.3} usec", self.meter.average());
let color = ::term::cell::Color::Rgb(Rgb { r: 0xd5, g: 0x4e, b: 0x53 });
let color = alacritty::term::cell::Color::Rgb(Rgb { r: 0xd5, g: 0x4e, b: 0x53 });
self.renderer.with_api(terminal.size_info(), |mut api| {
api.render_string(&timing[..], glyph_cache, &color);
});

View File

@ -20,7 +20,7 @@
//!
//! ```rust
//! // create a meter
//! let mut meter = Meter::new();
//! let mut meter = alacritty::meter::Meter::new();
//!
//! // Sample something.
//! {
@ -29,7 +29,7 @@
//!
//! // Get the moving average. The meter tracks a fixed number of samles, and the average won't mean
//! // much until it's filled up at least once.
//! printf!("Average time: {}", meter.average());
//! println!("Average time: {}", meter.average());
use std::time::{Instant, Duration};

View File

@ -1100,6 +1100,7 @@ impl From<io::Error> for ShaderCreationError {
///
/// The strategy for filling an atlas looks roughly like this:
///
/// ```ignore
/// (width, height)
/// ┌─────┬─────┬─────┬─────┬─────┐
/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
@ -1112,6 +1113,7 @@ impl From<io::Error> for ShaderCreationError {
/// │ │ │ │ │ <- Row considered full when next glyph doesn't
/// └─────┴─────┴─────┴───────────┘ fit in the row.
/// (0, 0) x->
/// ```
#[derive(Debug)]
struct Atlas {
/// Texture id for this atlas

View File

@ -20,7 +20,6 @@ use std::ptr;
use ansi::{self, Attr, Handler};
use grid::{Grid, ClearRegion};
use index::{Cursor, Column, Line};
use tty;
use ansi::Color;
/// RAII type which manages grid state for render
@ -80,6 +79,7 @@ pub mod cell {
use ::Rgb;
bitflags! {
#[derive(Serialize, Deserialize)]
pub flags Flags: u32 {
const INVERSE = 0b00000001,
const BOLD = 0b00000010,
@ -88,13 +88,13 @@ pub mod cell {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Color {
Rgb(Rgb),
Ansi(::ansi::Color),
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Cell {
pub c: char,
pub fg: Color,
@ -191,9 +191,6 @@ pub struct Term {
/// Alt is active
alt: bool,
/// Reference to the underlying tty
tty: tty::Tty,
/// The cursor
cursor: Cursor,
@ -222,7 +219,7 @@ pub struct Term {
}
/// Terminal size info
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct SizeInfo {
/// Terminal window width
pub width: f32,
@ -251,18 +248,8 @@ impl SizeInfo {
impl Term {
pub fn new(
width: f32,
height: f32,
cell_width: f32,
cell_height: f32
size: SizeInfo
) -> Term {
let size = SizeInfo {
width: width as f32,
height: height as f32,
cell_width: cell_width as f32,
cell_height: cell_height as f32,
};
let template = Cell::new(
' ',
cell::Color::Ansi(Color::Foreground),
@ -276,9 +263,6 @@ impl Term {
let grid = Grid::new(num_lines, num_cols, &template);
let tty = tty::new(*num_lines as u8, *num_cols as u8);
tty.resize(*num_lines as usize, *num_cols as usize, size.width as usize, size.height as usize);
let mut tabs = (Column(0)..grid.num_cols())
.map(|i| (*i as usize) % TAB_SPACES == 0)
.collect::<Vec<bool>>();
@ -295,7 +279,6 @@ impl Term {
alt: false,
cursor: Cursor::default(),
alt_cursor: Cursor::default(),
tty: tty,
tabs: tabs,
mode: Default::default(),
scroll_region: scroll_region,
@ -305,6 +288,10 @@ impl Term {
}
}
pub fn grid(&self) -> &Grid<Cell> {
&self.grid
}
pub fn render_grid<'a>(&'a mut self) -> RenderGrid<'a> {
RenderGrid::new(&mut self.grid, &self.cursor, self.mode)
}
@ -364,18 +351,6 @@ impl Term {
// Reset scrolling region to new size
self.scroll_region = Line(0)..self.grid.num_lines();
// Inform tty of new dimensions
self.tty.resize(*num_lines as _,
*num_cols as _,
self.size_info.width as usize,
self.size_info.height as usize);
}
#[inline]
pub fn tty(&self) -> &tty::Tty {
&self.tty
}
#[inline]
@ -876,3 +851,33 @@ impl ansi::Handler for Term {
self.mode.remove(mode::APP_KEYPAD);
}
}
#[cfg(test)]
mod tests {
extern crate serde_json;
use ansi::Color;
use grid::Grid;
use index::{Line, Column};
use term::{cell, Cell};
/// Check that the grid can be serialized back and forth losslessly
///
/// This test is in the term module as opposed to the grid since we want to
/// test this property with a T=Cell.
#[test]
fn grid_serde() {
let template = Cell::new(
' ',
cell::Color::Ansi(Color::Foreground),
cell::Color::Ansi(Color::Background)
);
let grid = Grid::new(Line(24), Column(80), &template);
let serialized = serde_json::to_string(&grid).expect("ser");
let deserialized = serde_json::from_str::<Grid<Cell>>(&serialized)
.expect("de");
assert_eq!(deserialized, grid);
}
}

View File

@ -23,6 +23,8 @@ use std::ptr;
use libc::{self, winsize, c_int, pid_t, WNOHANG, WIFEXITED, WEXITSTATUS, SIGCHLD};
use index::{Line, Column};
/// Process ID of child process
///
/// Necessary to put this in static storage for `sigchld` to have access
@ -236,8 +238,8 @@ fn execsh() -> ! {
}
/// Create a new tty and return a handle to interact with it.
pub fn new(rows: u8, cols: u8) -> Tty {
let (master, slave) = openpty(rows, cols);
pub fn new(lines: Line, cols: Column) -> Pty {
let (master, slave) = openpty(lines.0 as _, cols.0 as _);
match fork() {
Relation::Child => {
@ -280,16 +282,16 @@ pub fn new(rows: u8, cols: u8) -> Tty {
set_nonblocking(master);
}
Tty { fd: master }
Pty { fd: master }
}
}
}
pub struct Tty {
pub struct Pty {
fd: c_int,
}
impl Tty {
impl Pty {
/// Get reader for the TTY
///
/// XXX File is a bad abstraction here; it closes the fd on drop
@ -299,9 +301,11 @@ impl Tty {
}
}
pub fn resize(&self, rows: usize, cols: usize, px_x: usize, px_y: usize) {
pub fn resize(&self, lines: Line, cols: Column, px_x: usize, px_y: usize) {
let lines = lines.0;
let cols = cols.0;
let win = winsize {
ws_row: rows as libc::c_ushort,
ws_row: lines 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,

78
tests/ref.rs Normal file
View File

@ -0,0 +1,78 @@
extern crate alacritty;
extern crate serde_json;
/// ref tests
mod reference {
use std::fs::File;
use std::io::Read;
use std::path::Path;
use serde_json as json;
use alacritty::Grid;
use alacritty::Term;
use alacritty::term::Cell;
use alacritty::term::SizeInfo;
use alacritty::ansi;
macro_rules! ref_file {
($ref_name:ident, $file:expr) => {
concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/ref/", stringify!($ref_name), "/", $file
)
}
}
fn read_u8<P>(path: P) -> Vec<u8>
where P: AsRef<Path>
{
let mut res = Vec::new();
File::open(path.as_ref()).unwrap()
.read_to_end(&mut res).unwrap();
res
}
fn read_string<P>(path: P) -> String
where P: AsRef<Path>
{
let mut res = String::new();
File::open(path.as_ref()).unwrap()
.read_to_string(&mut res).unwrap();
res
}
macro_rules! ref_test {
($name:ident) => {
#[test]
fn $name() {
let recording = read_u8(ref_file!($name, "alacritty.recording"));
let serialized_size = read_string(ref_file!($name, "size.json"));
let serialized_grid = read_string(ref_file!($name, "grid.json"));
let size: SizeInfo = json::from_str(&serialized_size[..]).unwrap();
let grid: Grid<Cell> = json::from_str(&serialized_grid[..]).unwrap();
let mut terminal = Term::new(size);
let mut parser = ansi::Processor::new();
for byte in recording {
parser.advance(&mut terminal, byte);
}
assert_eq!(grid, *terminal.grid());
}
};
($( $name:ident ),*) => {
$(
ref_test! { $name }
),*
}
}
// Ref tests!
ref_test! { ll }
}

View File

@ -0,0 +1,27 @@
% jwilm@kurast.local ➜  ~/code/alacritty  [?1h=[?2004hlll[?1l>[?2004l
total 16440
drwxr-xr-x 3 jwilm staff 102B Nov 2 10:54 Alacritty.app
-rw-r--r-- 1 jwilm staff 53K Nov 19 14:27 Cargo.lock
-rw-r--r-- 1 jwilm staff 746B Nov 19 14:24 Cargo.toml
-rw-r--r-- 1 jwilm staff 11K Jun 30 10:44 LICENSE-APACHE
-rw-r--r-- 1 jwilm staff 1.6K Nov 2 10:52 Makefile
-rw-r--r-- 1 jwilm staff 49B Jun 9 18:56 TASKS.md
-rwxr-xr-x 1 jwilm staff 1.7M Sep 26 10:49 alacritty-pre-eloop
-rw-r--r-- 1 jwilm staff 255B Nov 19 14:31 alacritty.recording
-rw-r--r-- 1 jwilm staff 6.5K Nov 17 17:18 alacritty.yml
-rw-r--r-- 1 jwilm staff 1.1K Jun 30 10:44 build.rs
drwxr-xr-x 6 jwilm staff 204B Oct 10 10:46 copypasta
drwxr-xr-x 3 jwilm staff 102B Jun 9 18:56 docs
-rwxr-xr-x 1 jwilm staff 2.2M Nov 11 17:53 exitter
drwxr-xr-x 5 jwilm staff 170B Jun 28 14:50 font
-rwxr-xr-x 1 jwilm staff 2.2M Nov 14 13:27 hardcoded_bindings_alacritty
drwxr-xr-x 6 jwilm staff 204B Nov 2 10:54 macos
drwxr-xr-x 4 jwilm staff 136B Oct 27 17:59 res
-rw-r--r-- 1 jwilm staff 19B Nov 11 16:55 rustc-version
drwxr-xr-x 5 jwilm staff 170B Oct 10 10:46 scripts
drwxr-xr-x 17 jwilm staff 578B Nov 19 14:30 src
drwxr-xr-x 5 jwilm staff 170B Jun 28 15:49 target
-rw-r--r-- 1 jwilm staff 8.1K Nov 17 11:13 thing.log
-rw-r--r-- 1 jwilm staff 3.5K Sep 1 11:27 tmux-client-23038.log
-rwxr-xr-x 1 jwilm staff 1.8M Sep 22 12:03 with_parallel
% jwilm@kurast.local ➜  ~/code/alacritty  [?1h=[?2004h

1
tests/ref/ll/grid.json Normal file

File diff suppressed because one or more lines are too long

1
tests/ref/ll/size.json Normal file
View File

@ -0,0 +1 @@
{"width":1124.0,"height":628.0,"cell_width":14.0,"cell_height":26.0}