diff options
author | Joe Wilm <joe@jwilm.com> | 2016-11-19 16:16:20 -0800 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-11-19 21:34:11 -0800 |
commit | 66dbd29cd194a4c84f796f32827429895c2a3bba (patch) | |
tree | 5f5322cdae53d4fc295fe85f519192e9d38aab03 /src | |
parent | d97996e19de6856c23c51d05ec10f10db41e309d (diff) | |
download | alacritty-66dbd29cd194a4c84f796f32827429895c2a3bba.tar.gz alacritty-66dbd29cd194a4c84f796f32827429895c2a3bba.zip |
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`.
Diffstat (limited to 'src')
-rw-r--r-- | src/ansi.rs | 2 | ||||
-rw-r--r-- | src/config.rs | 54 | ||||
-rw-r--r-- | src/event.rs | 32 | ||||
-rw-r--r-- | src/event_loop.rs | 24 | ||||
-rw-r--r-- | src/grid.rs | 4 | ||||
-rw-r--r-- | src/index.rs | 6 | ||||
-rw-r--r-- | src/input.rs | 3 | ||||
-rw-r--r-- | src/lib.rs | 98 | ||||
-rw-r--r-- | src/main.rs | 144 | ||||
-rw-r--r-- | src/meter.rs | 4 | ||||
-rw-r--r-- | src/renderer/mod.rs | 2 | ||||
-rw-r--r-- | src/term.rs | 73 | ||||
-rw-r--r-- | src/tty.rs | 18 |
13 files changed, 309 insertions, 155 deletions
diff --git a/src/ansi.rs b/src/ansi.rs index f6d5de37..eaa34430 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -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, diff --git a/src/config.rs b/src/config.rs index b315fb86..704c4237 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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; - - struct StringVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<__D> ::serde::de::Visitor for StringVisitor<__D> - 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 +{ + struct RgbVisitor; + + impl ::serde::de::Visitor for RgbVisitor { + type Value = Rgb; + + 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 { diff --git a/src/event.rs b/src/event.rs index 430671cb..5ec156c3 100644 --- a/src/event.rs +++ b/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 diff --git a/src/event_loop.rs b/src/event_loop.rs index 7c00dd36..632ad28e 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -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; } diff --git a/src/grid.rs b/src/grid.rs index 7acfeaa3..1bcc91ce 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -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> { diff --git a/src/index.rs b/src/index.rs index 946e01fa..dc98be1e 100644 --- a/src/index.rs +++ b/src/index.rs @@ -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 { diff --git a/src/input.rs b/src/input.rs index f99c7a45..9d70e772 100644 --- a/src/input.rs +++ b/src/input.rs @@ -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. /// diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..8052ce07 --- /dev/null +++ b/src/lib.rs @@ -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) + } +} diff --git a/src/main.rs b/src/main.rs index ad8cfed9..fbf1eaca 100644 --- a/src/main.rs +++ b/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); }); diff --git a/src/meter.rs b/src/meter.rs index 470f613c..b73c1004 100644 --- a/src/meter.rs +++ b/src/meter.rs @@ -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}; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 333604f6..dfaf6b30 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -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 diff --git a/src/term.rs b/src/term.rs index 6e6f7d14..b4ac14af 100644 --- a/src/term.rs +++ b/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); + } +} @@ -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, |