mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
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:
parent
d97996e19d
commit
66dbd29cd1
21 changed files with 448 additions and 163 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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)",
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,13 +102,20 @@ impl Rasterizer {
|
|||
}
|
||||
|
||||
pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option<FontKey> {
|
||||
self.keys
|
||||
.get(&(desc.to_owned(), size))
|
||||
.map(|k| *k)
|
||||
.or_else(|| {
|
||||
self.get_font(desc, size)
|
||||
.map(|font| {
|
||||
let key = FontKey::next();
|
||||
|
||||
self.fonts.insert(key, font);
|
||||
self.keys.insert((desc.clone(), size), key);
|
||||
|
||||
key
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_font(&mut self, desc: &FontDesc, size: Size) -> Option<Font> {
|
||||
|
|
|
@ -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,12 +79,17 @@ impl Rasterizer {
|
|||
}
|
||||
|
||||
pub fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Option<FontKey> {
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_face(&mut self, desc: &FontDesc) -> Option<Face<'static>> {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
/// 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
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
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
|
||||
{
|
||||
type Value = String;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<Self::Value, E>
|
||||
fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<Rgb, 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 {
|
||||
|
|
32
src/event.rs
32
src/event.rs
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
98
src/lib.rs
Normal 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)
|
||||
}
|
||||
}
|
144
src/main.rs
144
src/main.rs
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
73
src/term.rs
73
src/term.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
18
src/tty.rs
18
src/tty.rs
|
@ -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
78
tests/ref.rs
Normal 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 }
|
||||
}
|
27
tests/ref/ll/alacritty.recording
Normal file
27
tests/ref/ll/alacritty.recording
Normal file
|
@ -0,0 +1,27 @@
|
|||
[1m[7m%[27m[1m[0m
[0m[27m[24m[Jjwilm@kurast.local [01;32m➜ [01;32m [36m~/code/alacritty [00m [K[?1h=[?2004h[35ml[39m[35ml[35ml[39m[?1l>[?2004l
|
||||
total 16440
|
||||
drwxr-xr-x 3 jwilm staff 102B Nov 2 10:54 [1m[36mAlacritty.app[39;49m[0m
|
||||
-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 [31malacritty-pre-eloop[39;49m[0m
|
||||
-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 [1m[36mcopypasta[39;49m[0m
|
||||
drwxr-xr-x 3 jwilm staff 102B Jun 9 18:56 [1m[36mdocs[39;49m[0m
|
||||
-rwxr-xr-x 1 jwilm staff 2.2M Nov 11 17:53 [31mexitter[39;49m[0m
|
||||
drwxr-xr-x 5 jwilm staff 170B Jun 28 14:50 [1m[36mfont[39;49m[0m
|
||||
-rwxr-xr-x 1 jwilm staff 2.2M Nov 14 13:27 [31mhardcoded_bindings_alacritty[39;49m[0m
|
||||
drwxr-xr-x 6 jwilm staff 204B Nov 2 10:54 [1m[36mmacos[39;49m[0m
|
||||
drwxr-xr-x 4 jwilm staff 136B Oct 27 17:59 [1m[36mres[39;49m[0m
|
||||
-rw-r--r-- 1 jwilm staff 19B Nov 11 16:55 rustc-version
|
||||
drwxr-xr-x 5 jwilm staff 170B Oct 10 10:46 [1m[36mscripts[39;49m[0m
|
||||
drwxr-xr-x 17 jwilm staff 578B Nov 19 14:30 [1m[36msrc[39;49m[0m
|
||||
drwxr-xr-x 5 jwilm staff 170B Jun 28 15:49 [1m[36mtarget[39;49m[0m
|
||||
-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 [31mwith_parallel[39;49m[0m
|
||||
[1m[7m%[27m[1m[0m
[0m[27m[24m[Jjwilm@kurast.local [01;32m➜ [01;32m [36m~/code/alacritty [00m [K[?1h=[?2004h
|
1
tests/ref/ll/grid.json
Normal file
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
1
tests/ref/ll/size.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"width":1124.0,"height":628.0,"cell_width":14.0,"cell_height":26.0}
|
Loading…
Reference in a new issue