diff options
-rw-r--r-- | src/grid.rs | 620 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/renderer/mod.rs | 27 | ||||
-rw-r--r-- | src/term.rs | 350 |
4 files changed, 685 insertions, 314 deletions
diff --git a/src/grid.rs b/src/grid.rs index 042aabb7..37c9dee6 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -11,160 +11,362 @@ // 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. -// -//! Functions for computing properties of the terminal grid -use std::ops::{Index, IndexMut, Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull}; +//! A generic 2d grid implementation optimized for use in a terminal. +//! +//! The current implementation uses a vector of vectors to store cell data. +//! Reimplementing the store as a single contiguous vector may be desirable in +//! the future. Rotation and indexing would need to be reconsidered at that +//! time; rotation currently reorganize Vecs in the lines Vec, and indexing with +//! ranges is currently supported. + +use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; use std::cmp::Ordering; use std::slice::{self, Iter, IterMut}; use std::iter::IntoIterator; +use std::borrow::ToOwned; use util::Rotate; -use term::{Cursor, DEFAULT_FG, DEFAULT_BG}; -use ::Rgb; - -#[derive(Clone, Debug)] -pub struct Cell { - pub c: char, - pub fg: Rgb, - pub bg: Rgb, - pub flags: CellFlags, -} +/// Indexing types and implementations for Grid and Line +pub mod index { + use std::fmt; + use std::iter::Step; + use std::num::{One, Zero}; + use std::ops::{self, Deref, Add}; -bitflags! { - pub flags CellFlags: u32 { - const INVERSE = 0b00000001, - const BOLD = 0b00000010, - const ITALIC = 0b00000100, - const UNDERLINE = 0b00001000, + /// Index in the grid using row, column notation + #[derive(Debug, Clone, Default, Eq, PartialEq)] + pub struct Cursor { + pub line: Line, + pub col: Column, } -} -impl Cell { - pub fn new(c: char) -> Cell { - Cell { - c: c.into(), - bg: Default::default(), - fg: Default::default(), - flags: CellFlags::empty(), + /// A line + /// + /// Newtype to avoid passing values incorrectly + #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] + pub struct Line(pub usize); + + impl fmt::Display for Line { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Line({})", self.0) } } - pub fn reset(&mut self) { - self.c = ' '; - self.flags = CellFlags::empty(); + /// A column + /// + /// Newtype to avoid passing values incorrectly + #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] + pub struct Column(pub usize); - // FIXME shouldn't know about term - self.bg = DEFAULT_BG; - self.fg = DEFAULT_FG; + impl fmt::Display for Column { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Column({})", self.0) + } } -} -/// Represents the terminal display contents -#[derive(Clone)] -pub struct Grid { - /// Rows in the grid. Each row holds a list of cells corresponding to the columns in that row. - raw: Vec<Row>, + /// Copyright 2015 The Rust Project Developers. See the COPYRIGHT + /// file at the top-level directory of this distribution and at + /// http://rust-lang.org/COPYRIGHT. + /// + /// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or + /// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license + /// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your + /// option. This file may not be copied, modified, or distributed + /// except according to those terms. + /// + /// implements binary operators "&T op U", "T op &U", "&T op &U" + /// based on "T op U" where T and U are expected to be `Copy`able + macro_rules! forward_ref_binop { + (impl $imp:ident, $method:ident for $t:ty, $u:ty) => { + impl<'a> $imp<$u> for &'a $t { + type Output = <$t as $imp<$u>>::Output; + + #[inline] + fn $method(self, other: $u) -> <$t as $imp<$u>>::Output { + $imp::$method(*self, other) + } + } - /// Number of columns - cols: usize, + impl<'a> $imp<&'a $u> for $t { + type Output = <$t as $imp<$u>>::Output; - /// Number of rows. - /// - /// Invariant: rows is equivalent to cells.len() - rows: usize, -} + #[inline] + fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { + $imp::$method(self, *other) + } + } -impl Grid { - pub fn new(rows: usize, cols: usize) -> Grid { - let mut raw = Vec::with_capacity(rows); - for _ in 0..rows { - raw.push(Row::new(cols)); - } + impl<'a, 'b> $imp<&'a $u> for &'b $t { + type Output = <$t as $imp<$u>>::Output; - Grid { - raw: raw, - cols: cols, - rows: rows, + #[inline] + fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { + $imp::$method(*self, *other) + } + } } } - #[inline] - pub fn rows(&self) -> Iter<Row> { - self.raw.iter() + /// Macro for deriving deref + macro_rules! deref { + ($ty:ty, $target:ty) => { + impl Deref for $ty { + type Target = $target; + + #[inline] + fn deref(&self) -> &$target { + &self.0 + } + } + } } - #[inline] - pub fn rows_mut(&mut self) -> IterMut<Row> { - self.raw.iter_mut() + macro_rules! add { + ($ty:ty, $construct:expr) => { + impl ops::Add<$ty> for $ty { + type Output = $ty; + + #[inline] + fn add(self, rhs: $ty) -> $ty { + $construct(self.0 + rhs.0) + } + } + } } - #[inline] - pub fn num_rows(&self) -> usize { - self.raw.len() + macro_rules! sub { + ($ty:ty, $construct:expr) => { + impl ops::Sub<$ty> for $ty { + type Output = $ty; + + #[inline] + fn sub(self, rhs: $ty) -> $ty { + $construct(self.0 - rhs.0) + } + } + } } - #[inline] - pub fn num_cols(&self) -> usize { - self.raw[0].len() + macro_rules! zero_one { + ($ty:ty, $construct:expr) => { + impl One for $ty { + fn one() -> $ty { + $construct(1) + } + } + + impl Zero for $ty { + fn zero() -> $ty { + $construct(0) + } + } + } } - pub fn scroll(&mut self, region: Range<usize>, positions: isize) { - self.raw[region].rotate(positions) + macro_rules! ops { + ($ty:ty, $construct:expr) => { + add!($ty, $construct); + sub!($ty, $construct); + zero_one!($ty, $construct); + deref!($ty, usize); + forward_ref_binop!(impl Add, add for $ty, $ty); + + impl Step for $ty { + fn step(&self, by: &$ty) -> Option<$ty> { + Some(*self + *by) + } + + #[inline] + #[allow(trivial_numeric_casts)] + fn steps_between(start: &$ty, end: &$ty, by: &$ty) -> Option<usize> { + if *by == $construct(0) { return None; } + if *start < *end { + // Note: We assume $t <= usize here + let diff = (*end - *start).0; + let by = by.0; + if diff % by > 0 { + Some(diff / by + 1) + } else { + Some(diff / by) + } + } else { + Some(0) + } + } + } + + impl ops::AddAssign<$ty> for $ty { + #[inline] + fn add_assign(&mut self, rhs: $ty) { + self.0 += rhs.0 + } + } + + impl ops::SubAssign<$ty> for $ty { + #[inline] + fn sub_assign(&mut self, rhs: $ty) { + self.0 -= rhs.0 + } + } + + impl ops::AddAssign<usize> for $ty { + #[inline] + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs + } + } + + impl ops::SubAssign<usize> for $ty { + #[inline] + fn sub_assign(&mut self, rhs: usize) { + self.0 -= rhs + } + } + + impl From<usize> for $ty { + #[inline] + fn from(val: usize) -> $ty { + $construct(val) + } + } + + impl ops::Add<usize> for $ty { + type Output = $ty; + + #[inline] + fn add(self, rhs: usize) -> $ty { + $construct(self.0 + rhs) + } + } + + impl ops::Sub<usize> for $ty { + type Output = $ty; + + #[inline] + fn sub(self, rhs: usize) -> $ty { + $construct(self.0 - rhs) + } + } + } } - #[inline] - pub fn clear(&mut self) { - let region = 0..self.num_rows(); - self.clear_region(region); + ops!(Line, Line); + ops!(Column, Column); +} + +use self::index::Cursor; + +/// Represents the terminal display contents +#[derive(Clone)] +pub struct Grid<T> { + /// Lines in the grid. Each row holds a list of cells corresponding to the + /// columns in that row. + raw: Vec<Row<T>>, + + /// Number of columns + cols: index::Column, + + /// Number of lines. + /// + /// Invariant: lines is equivalent to raw.len() + lines: index::Line, +} + +impl<T: Clone> Grid<T> { + pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid<T> { + let mut raw = Vec::with_capacity(*lines); + for _ in index::Line(0)..lines { + raw.push(Row::new(cols, template)); + } + + Grid { + raw: raw, + cols: cols, + lines: lines, + } } - pub fn resize(&mut self, rows: usize, cols: usize) { + pub fn resize(&mut self, lines: index::Line, cols: index::Column, template: &T) { // Check that there's actually work to do and return early if not - if rows == self.rows && cols == self.cols { + if lines == self.lines && cols == self.cols { return; } - match self.rows.cmp(&rows) { - Ordering::Less => self.grow_rows(rows), - Ordering::Greater => self.shrink_rows(rows), + match self.lines.cmp(&lines) { + Ordering::Less => self.grow_lines(lines, template), + Ordering::Greater => self.shrink_lines(lines), Ordering::Equal => (), } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols), + Ordering::Less => self.grow_cols(cols, template), Ordering::Greater => self.shrink_cols(cols), Ordering::Equal => (), } } - fn grow_rows(&mut self, rows: usize) { - for _ in self.num_rows()..rows { - self.raw.push(Row::new(self.cols)); + fn grow_lines(&mut self, lines: index::Line, template: &T) { + for _ in self.num_lines()..lines { + self.raw.push(Row::new(self.cols, template)); } - self.rows = rows; + self.lines = lines; } - fn shrink_rows(&mut self, rows: usize) { - while self.raw.len() != rows { - self.raw.pop(); + fn grow_cols(&mut self, cols: index::Column, template: &T) { + for row in self.lines_mut() { + row.grow(cols, template); } - self.rows = rows; + self.cols = cols; + } + +} + +impl<T> Grid<T> { + #[inline] + pub fn lines(&self) -> Iter<Row<T>> { + self.raw.iter() + } + + #[inline] + pub fn lines_mut(&mut self) -> IterMut<Row<T>> { + self.raw.iter_mut() + } + + #[inline] + pub fn num_lines(&self) -> index::Line { + index::Line(self.raw.len()) + } + + #[inline] + pub fn num_cols(&self) -> index::Column { + index::Column(self.raw[0].len()) + } + + #[inline] + pub fn scroll(&mut self, region: Range<index::Line>, positions: isize) { + self[region].rotate(positions) + } + + #[inline] + pub fn clear<F: Fn(&mut T)>(&mut self, func: F) { + let region = index::Line(0)..self.num_lines(); + self.clear_region(region, func); } - fn grow_cols(&mut self, cols: usize) { - for row in self.rows_mut() { - row.grow(cols); + fn shrink_lines(&mut self, lines: index::Line) { + while index::Line(self.raw.len()) != lines { + self.raw.pop(); } - self.cols = cols; + self.lines = lines; } - fn shrink_cols(&mut self, cols: usize) { - for row in self.rows_mut() { + fn shrink_cols(&mut self, cols: index::Column) { + for row in self.lines_mut() { row.shrink(cols); } @@ -172,128 +374,138 @@ impl Grid { } } -impl Index<usize> for Grid { - type Output = Row; +impl<T> Index<index::Line> for Grid<T> { + type Output = Row<T>; #[inline] - fn index<'a>(&'a self, index: usize) -> &'a Row { - &self.raw[index] + fn index<'a>(&'a self, index: index::Line) -> &'a Row<T> { + &self.raw[index.0] } } -impl IndexMut<usize> for Grid { +impl<T> IndexMut<index::Line> for Grid<T> { #[inline] - fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Row { - &mut self.raw[index] + fn index_mut<'a>(&'a mut self, index: index::Line) -> &'a mut Row<T> { + &mut self.raw[index.0] } } -impl Index<Cursor> for Grid { - type Output = Cell; +impl<'cursor, T> Index<&'cursor Cursor> for Grid<T> { + type Output = T; #[inline] - fn index<'a>(&'a self, cursor: Cursor) -> &'a Cell { - &self.raw[cursor.y as usize][cursor.x as usize] + fn index<'a, 'b>(&'a self, cursor: &'b Cursor) -> &'a T { + &self.raw[cursor.line.0][cursor.col] } } -impl IndexMut<Cursor> for Grid { +impl<'cursor, T> IndexMut<&'cursor Cursor> for Grid<T> { #[inline] - fn index_mut<'a>(&'a mut self, cursor: Cursor) -> &'a mut Cell { - &mut self.raw[cursor.y as usize][cursor.x as usize] + fn index_mut<'a, 'b>(&'a mut self, cursor: &'b Cursor) -> &'a mut T { + &mut self.raw[cursor.line.0][cursor.col] } } /// A row in the grid -#[derive(Debug, Clone)] -pub struct Row(Vec<Cell>); +#[derive(Clone)] +pub struct Row<T>(Vec<T>); -impl Row { - pub fn new(columns: usize) -> Row { - Row(vec![Cell::new(' '); columns]) +impl<T: Clone> Row<T> { + pub fn new(columns: index::Column, template: &T) -> Row<T> { + Row(vec![template.to_owned(); *columns]) } - pub fn grow(&mut self, cols: usize) { - while self.len() != cols { - self.push(Cell::new(' ')); + pub fn grow(&mut self, cols: index::Column, template: &T) { + while self.len() != *cols { + self.push(template.to_owned()); } } +} - pub fn shrink(&mut self, cols: usize) { - while self.len() != cols { +impl<T> Row<T> { + pub fn shrink(&mut self, cols: index::Column) { + while self.len() != *cols { self.pop(); } } - pub fn cells(&self) -> Iter<Cell> { + #[inline] + pub fn cells(&self) -> Iter<T> { self.0.iter() } - pub fn cells_mut(&mut self) -> IterMut<Cell> { + #[inline] + pub fn cells_mut(&mut self) -> IterMut<T> { self.0.iter_mut() } } -impl<'a> IntoIterator for &'a Row { - type Item = &'a Cell; - type IntoIter = slice::Iter<'a, Cell>; +impl<'a, T> IntoIterator for &'a Row<T> { + type Item = &'a T; + type IntoIter = slice::Iter<'a, T>; - fn into_iter(self) -> slice::Iter<'a, Cell> { + #[inline] + fn into_iter(self) -> slice::Iter<'a, T> { self.iter() } } -impl<'a> IntoIterator for &'a mut Row { - type Item = &'a mut Cell; - type IntoIter = slice::IterMut<'a, Cell>; +impl<'a, T> IntoIterator for &'a mut Row<T> { + type Item = &'a mut T; + type IntoIter = slice::IterMut<'a, T>; - fn into_iter(mut self) -> slice::IterMut<'a, Cell> { + #[inline] + fn into_iter(mut self) -> slice::IterMut<'a, T> { self.iter_mut() } } -impl Deref for Row { - type Target = Vec<Cell>; +impl<T> Deref for Row<T> { + type Target = Vec<T>; + + #[inline] fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for Row { +impl<T> DerefMut for Row<T> { + #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl Index<usize> for Row { - type Output = Cell; +impl<T> Index<index::Column> for Row<T> { + type Output = T; #[inline] - fn index<'a>(&'a self, index: usize) -> &'a Cell { - &self.0[index] + fn index<'a>(&'a self, index: index::Column) -> &'a T { + &self.0[index.0] } } -impl IndexMut<usize> for Row { +impl<T> IndexMut<index::Column> for Row<T> { #[inline] - fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Cell { - &mut self.0[index] + fn index_mut<'a>(&'a mut self, index: index::Column) -> &'a mut T { + &mut self.0[index.0] } } macro_rules! row_index_range { ($range:ty) => { - impl Index<$range> for Row { - type Output = [Cell]; + impl<T> Index<$range> for Row<T> { + type Output = [T]; + #[inline] - fn index<'a>(&'a self, index: $range) -> &'a [Cell] { + fn index<'a>(&'a self, index: $range) -> &'a [T] { &self.0[index] } } - impl IndexMut<$range> for Row { + impl<T> IndexMut<$range> for Row<T> { #[inline] - fn index_mut<'a>(&'a mut self, index: $range) -> &'a mut [Cell] { + fn index_mut<'a>(&'a mut self, index: $range) -> &'a mut [T] { &mut self.0[index] } } @@ -305,17 +517,121 @@ row_index_range!(RangeTo<usize>); row_index_range!(RangeFrom<usize>); row_index_range!(RangeFull); -pub trait ClearRegion<T> { - fn clear_region(&mut self, region: T); +// ------------------------------------------------------------------------------------------------- +// Row ranges for Grid +// ------------------------------------------------------------------------------------------------- + +impl<T> Index<Range<index::Line>> for Grid<T> { + type Output = [Row<T>]; + + #[inline] + fn index(&self, index: Range<index::Line>) -> &[Row<T>] { + &self.raw[(index.start.0)..(index.end.0)] + } +} + +impl<T> IndexMut<Range<index::Line>> for Grid<T> { + #[inline] + fn index_mut(&mut self, index: Range<index::Line>) -> &mut [Row<T>] { + &mut self.raw[(index.start.0)..(index.end.0)] + } +} + +impl<T> Index<RangeTo<index::Line>> for Grid<T> { + type Output = [Row<T>]; + + #[inline] + fn index(&self, index: RangeTo<index::Line>) -> &[Row<T>] { + &self.raw[..(index.end.0)] + } +} + +impl<T> IndexMut<RangeTo<index::Line>> for Grid<T> { + #[inline] + fn index_mut(&mut self, index: RangeTo<index::Line>) -> &mut [Row<T>] { + &mut self.raw[..(index.end.0)] + } +} + +impl<T> Index<RangeFrom<index::Line>> for Grid<T> { + type Output = [Row<T>]; + + #[inline] + fn index(&self, index: RangeFrom<index::Line>) -> &[Row<T>] { + &self.raw[(index.start.0)..] + } +} + +impl<T> IndexMut<RangeFrom<index::Line>> for Grid<T> { + #[inline] + fn index_mut(&mut self, index: RangeFrom<index::Line>) -> &mut [Row<T>] { + &mut self.raw[(index.start.0)..] + } +} + +// ------------------------------------------------------------------------------------------------- +// Column ranges for Row +// ------------------------------------------------------------------------------------------------- + +impl<T> Index<Range<index::Column>> for Row<T> { + type Output = [T]; + + #[inline] + fn index(&self, index: Range<index::Column>) -> &[T] { + &self.0[(index.start.0)..(index.end.0)] + } +} + +impl<T> IndexMut<Range<index::Column>> for Row<T> { + #[inline] + fn index_mut(&mut self, index: Range<index::Column>) -> &mut [T] { + &mut self.0[(index.start.0)..(index.end.0)] + } +} + +impl<T> Index<RangeTo<index::Column>> for Row<T> { + type Output = [T]; + + #[inline] + fn index(&self, index: RangeTo<index::Column>) -> &[T] { + &self.0[..(index.end.0)] + } +} + +impl<T> IndexMut<RangeTo<index::Column>> for Row<T> { + #[inline] + fn index_mut(&mut self, index: RangeTo<index::Column>) -> &mut [T] { + &mut self.0[..(index.end.0)] + } +} + +impl<T> Index<RangeFrom<index::Column>> for Row<T> { + type Output = [T]; + + #[inline] + fn index(&self, index: RangeFrom<index::Column>) -> &[T] { + &self.0[(index.start.0)..] + } +} + +impl<T> IndexMut<RangeFrom<index::Column>> for Row<T> { + #[inline] + fn index_mut(&mut self, index: RangeFrom<index::Column>) -> &mut [T] { + &mut self.0[(index.start.0)..] + } +} + +pub trait ClearRegion<R, T> { + fn clear_region<F: Fn(&mut T)>(&mut self, region: R, func: F); } macro_rules! clear_region_impl { ($range:ty) => { - impl ClearRegion<$range> for Grid { - fn clear_region(&mut self, region: $range) { - for row in self.raw[region].iter_mut() { + impl<T> ClearRegion<$range, T> for Grid<T> { + fn clear_region<F: Fn(&mut T)>(&mut self, region: $range, func: F) { + for row in self[region].iter_mut() { for cell in row { - cell.reset(); + func(cell); } } } @@ -323,6 +639,6 @@ macro_rules! clear_region_impl { } } -clear_region_impl!(Range<usize>); -clear_region_impl!(RangeTo<usize>); -clear_region_impl!(RangeFrom<usize>); +clear_region_impl!(Range<index::Line>); +clear_region_impl!(RangeTo<index::Line>); +clear_region_impl!(RangeFrom<index::Line>); diff --git a/src/main.rs b/src/main.rs index aae02322..e73ec15c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,8 @@ #![feature(inclusive_range_syntax)] #![feature(drop_types_in_const)] #![feature(unicode)] +#![feature(zero_one)] +#![feature(step_trait)] #![feature(custom_derive, plugin)] #![plugin(serde_macros)] diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 19a69b8b..c967fa05 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -27,8 +27,8 @@ use gl; use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; use font::{Rasterizer, RasterizedGlyph, FontDesc}; -use grid::{self, Grid, Cell, CellFlags}; -use term; +use grid::{self, Grid}; +use term::{self, cell, Cell}; use super::Rgb; @@ -80,8 +80,8 @@ pub struct Glyph { /// Naïve glyph cache /// -/// Currently only keyed by `char`, and thus not possible to hold different representations of the -/// same code point. +/// Currently only keyed by `char`, and thus not possible to hold different +/// representations of the same code point. pub struct GlyphCache { /// Cache of buffered glyphs cache: HashMap<char, Glyph>, @@ -236,7 +236,7 @@ impl Batch { bg_b: cell.bg.b as f32, }; - if cell.flags.contains(grid::INVERSE) { + if cell.flags.contains(cell::INVERSE) { instance.r = cell.bg.r as f32; instance.g = cell.bg.g as f32; instance.b = cell.bg.b as f32; @@ -550,7 +550,7 @@ impl<'a> RenderApi<'a> { c: c, fg: *color, bg: term::DEFAULT_BG, - flags: grid::INVERSE, + flags: cell::INVERSE, }; self.add_render_item(row, col, &cell, glyph); } @@ -576,22 +576,25 @@ impl<'a> RenderApi<'a> { } } - pub fn render_cursor(&mut self, cursor: term::Cursor, glyph_cache: &mut GlyphCache) { + pub fn render_cursor(&mut self, cursor: &grid::index::Cursor, glyph_cache: &mut GlyphCache) { if let Some(glyph) = glyph_cache.get(term::CURSOR_SHAPE, self) { let cell = Cell { c: term::CURSOR_SHAPE, fg: term::DEFAULT_FG, bg: term::DEFAULT_BG, - flags: CellFlags::empty(), + flags: cell::Flags::empty(), }; - self.add_render_item(cursor.y as f32, cursor.x as f32, &cell, glyph); + let y: usize = *cursor.line; + let x: usize = *cursor.col; + + self.add_render_item(y as f32, x as f32, &cell, glyph); } } - pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &mut GlyphCache) { - for (i, row) in grid.rows().enumerate() { - for (j, cell) in row.cells().enumerate() { + pub fn render_grid(&mut self, grid: &Grid<Cell>, glyph_cache: &mut GlyphCache) { + for (i, line) in grid.lines().enumerate() { + for (j, cell) in line.cells().enumerate() { // Skip empty cells if cell.c == ' ' && cell.bg == term::DEFAULT_BG { continue; diff --git a/src/term.rs b/src/term.rs index 699df8de..9c8cff30 100644 --- a/src/term.rs +++ b/src/term.rs @@ -14,9 +14,10 @@ // //! Exports the `Term` type which is a high-level API for the Grid use std::ops::Range; +use std::fmt; use ansi::{self, Attr}; -use grid::{self, Grid, CellFlags, ClearRegion}; +use grid::{Grid, ClearRegion}; use tty; use ::Rgb; @@ -31,6 +32,49 @@ fn limit<T: PartialOrd>(val: T, min: T, max: T) -> T { } } +pub mod cell { + use super::{DEFAULT_FG, DEFAULT_BG}; + use ::Rgb; + + bitflags! { + pub flags Flags: u32 { + const INVERSE = 0b00000001, + const BOLD = 0b00000010, + const ITALIC = 0b00000100, + const UNDERLINE = 0b00001000, + } + } + + #[derive(Clone, Debug)] + pub struct Cell { + pub c: char, + pub fg: Rgb, + pub bg: Rgb, + pub flags: Flags, + } + + impl Cell { + pub fn new(c: char) -> Cell { + Cell { + c: c.into(), + bg: Default::default(), + fg: Default::default(), + flags: Flags::empty(), + } + } + + pub fn reset(&mut self) { + self.c = ' '; + self.flags = Flags::empty(); + + self.bg = DEFAULT_BG; + self.fg = DEFAULT_FG; + } + } +} + +pub use self::cell::Cell; + /// tomorrow night bright /// /// because contrast @@ -78,37 +122,31 @@ pub const DEFAULT_FG: Rgb = Rgb { r: 0xea, g: 0xea, b: 0xea}; pub const DEFAULT_BG: Rgb = Rgb { r: 0, g: 0, b: 0}; pub const TAB_SPACES: usize = 8; -/// State for cursor -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cursor { - pub x: u16, - pub y: u16, -} +use grid::index::{Cursor, Column, Line}; -impl Default for Cursor { - fn default() -> Cursor { - Cursor { x: 0, y: 0 } - } +trait CursorExt { + fn goto(&mut self, Line, Column); + fn advance(&mut self, Line, Column); } -impl Cursor { - pub fn goto(&mut self, x: u16, y: u16) { - self.x = x; - self.y = y; +impl CursorExt for Cursor { + fn goto(&mut self, line: Line, col: Column) { + self.col = col; + self.line = line; } - pub fn advance(&mut self, rows: i64, cols: i64) { - self.x = (self.x as i64 + cols) as u16; - self.y = (self.y as i64 + rows) as u16; + fn advance(&mut self, lines: Line, cols: Column) { + self.col = self.col + cols; + self.line = self.line + lines; } } pub struct Term { /// The grid - grid: Grid, + grid: Grid<Cell>, /// Alternate grid - alt_grid: Grid, + alt_grid: Grid<Cell>, /// Alt is active alt: bool, @@ -132,13 +170,13 @@ pub struct Term { tabs: Vec<bool>, /// Cell attributes - attr: grid::CellFlags, + attr: cell::Flags, /// Mode flags mode: TermMode, /// Scroll region - scroll_region: Range<usize>, + scroll_region: Range<Line>, /// Size size_info: SizeInfo, @@ -162,13 +200,13 @@ pub struct SizeInfo { impl SizeInfo { #[inline] - pub fn rows(&self) -> usize { - (self.height / self.cell_height) as usize + pub fn lines(&self) -> Line { + Line((self.height / self.cell_height) as usize) } #[inline] - pub fn cols(&self) -> usize { - (self.width / self.cell_width) as usize + pub fn cols(&self) -> Column { + Column((self.width / self.cell_width) as usize) } } @@ -182,21 +220,21 @@ impl Term { }; let num_cols = size.cols(); - let num_rows = size.rows(); + let num_lines = size.lines(); - println!("num_cols, num_rows = {}, {}", num_cols, num_rows); + println!("num_cols, num_lines = {}, {}", num_cols, num_lines); - let grid = Grid::new(num_rows, num_cols); + let grid = Grid::new(num_lines, num_cols, &Cell::new(' ')); - let tty = tty::new(num_rows as u8, num_cols as u8); - tty.resize(num_rows, num_cols, size.width as usize, size.height as usize); + 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 = (0..grid.num_cols()).map(|i| i % TAB_SPACES == 0) - .collect::<Vec<bool>>(); + let mut tabs = (Column(0)..grid.num_cols()).map(|i| (*i as usize) % TAB_SPACES == 0) + .collect::<Vec<bool>>(); tabs[0] = false; let alt = grid.clone(); - let scroll_region = 0..grid.num_rows(); + let scroll_region = Line(0)..grid.num_lines(); Term { grid: grid, @@ -208,7 +246,7 @@ impl Term { bg: DEFAULT_BG, tty: tty, tabs: tabs, - attr: CellFlags::empty(), + attr: cell::Flags::empty(), mode: Default::default(), scroll_region: scroll_region, size_info: size @@ -225,52 +263,51 @@ impl Term { }; let old_cols = self.size_info.cols(); - let old_rows = self.size_info.rows(); + let old_lines = self.size_info.lines(); let num_cols = size.cols(); - let num_rows = size.rows(); + let num_lines = size.lines(); self.size_info = size; - if old_cols == num_cols && old_rows == num_rows { + if old_cols == num_cols && old_lines == num_lines { return; } // Scroll up to keep cursor and as much context as possible in grid. This only runs when the - // rows decreases. - self.scroll_region = 0..self.grid.num_rows(); - // XXX why is +1 required? - let row_diff = (self.cursor_y() as isize - num_rows as isize) + 1; - if row_diff > 0 { - self.scroll(row_diff); - self.cursor.advance(-row_diff as i64, 0); + // lines decreases. + self.scroll_region = Line(0)..self.grid.num_lines(); + + // Scroll up to keep cursor in terminal + if self.cursor.line >= num_lines { + let lines = self.cursor.line - num_lines + 1; + self.scroll(lines, ScrollDirection::Down); + self.cursor.line -= lines; } - println!("num_cols, num_rows = {}, {}", num_cols, num_rows); + println!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size - self.grid.resize(num_rows, num_cols); - self.alt_grid.resize(num_rows, num_cols); + self.grid.resize(num_lines, num_cols, &Cell::new(' ')); + self.alt_grid.resize(num_lines, num_cols, &Cell::new(' ')); // Ensure cursor is in-bounds - self.cursor.y = limit(self.cursor.y, 0, num_rows as u16); - self.cursor.x = limit(self.cursor.x, 0, num_cols as u16); + self.cursor.line = limit(self.cursor.line, Line(0), num_lines); + self.cursor.col = limit(self.cursor.col, Column(0), num_cols); // Recreate tabs list - self.tabs = (0..self.grid.num_cols()).map(|i| i % TAB_SPACES == 0) - .collect::<Vec<bool>>(); + self.tabs = (Column(0)..self.grid.num_cols()).map(|i| (*i as usize) % TAB_SPACES == 0) + .collect::<Vec<bool>>(); // Make sure bottom of terminal is clear - if row_diff > 0 { - self.grid.clear_region((self.cursor.y as usize)..); - self.alt_grid.clear_region((self.cursor.y as usize)..); - } + self.grid.clear_region((self.cursor.line).., |c| c.reset()); + self.alt_grid.clear_region((self.cursor.line).., |c| c.reset()); // Reset scrolling region to new size - self.scroll_region = 0..self.grid.num_rows(); + self.scroll_region = Line(0)..self.grid.num_lines(); // Inform tty of new dimensions - self.tty.resize(num_rows, - num_cols, + self.tty.resize(*num_lines as _, + *num_cols as _, self.size_info.width as usize, self.size_info.height as usize); @@ -286,7 +323,8 @@ impl Term { &self.size_info } - pub fn grid(&self) -> &Grid { + #[inline] + pub fn grid(&self) -> &Grid<Cell> { &self.grid } @@ -301,38 +339,28 @@ impl Term { ::std::mem::swap(&mut self.cursor, &mut self.alt_cursor); if self.alt { - self.grid.clear(); + self.grid.clear(|c| c.reset()); } } #[inline] - pub fn cursor_x(&self) -> u16 { - self.cursor.x - } - - #[inline] - pub fn cursor_y(&self) -> u16 { - self.cursor.y - } - - #[inline] - pub fn cursor(&self) -> Cursor { - self.cursor + pub fn cursor(&self) -> &Cursor { + &self.cursor } /// Set character in current cursor position fn set_char(&mut self, c: char) { - if self.cursor.x == self.grid.num_cols() as u16 { + if self.cursor.col == self.grid.num_cols() { println!("wrapping"); - self.cursor.y += 1; - self.cursor.x = 0; + self.cursor.line += 1; + self.cursor.col = Column(0); } - if self.cursor.y == self.grid.num_rows() as u16 { + if self.cursor.line == self.grid.num_lines() { panic!("cursor fell off grid"); } - let cell = &mut self.grid[self.cursor]; + let cell = &mut self.grid[&self.cursor]; cell.c = c; cell.fg = self.fg; cell.bg = self.bg; @@ -340,17 +368,44 @@ impl Term { } /// Convenience function for scrolling - fn scroll(&mut self, count: isize) { - println!("[TERM] scrolling {} lines", count); - self.grid.scroll(self.scroll_region.clone(), count); - if count > 0 { - // Scrolled down, so need to clear from bottom - let start = self.scroll_region.end - (count as usize); - self.grid.clear_region(start..self.scroll_region.end); - } else { - // Scrolled up, clear from top - let end = self.scroll_region.start + ((-count) as usize); - self.grid.clear_region(self.scroll_region.start..end); + fn scroll(&mut self, lines: Line, direction: ScrollDirection) { + println!("[TERM] scrolling {} {} lines", direction, lines); + match direction { + ScrollDirection::Down => { + // Scrolled down, so need to clear from bottom + self.grid.scroll(self.scroll_region.clone(), *lines as isize); + let start = self.scroll_region.end - lines; + self.grid.clear_region(start..self.scroll_region.end, |c| c.reset()); + }, + ScrollDirection::Up => { + // Scrolled up, clear from top + self.grid.scroll(self.scroll_region.clone(), -(*lines as isize)); + let end = self.scroll_region.start + lines; + self.grid.clear_region(self.scroll_region.start..end, |c| c.reset()); + } + } + } +} + +/// Which direction to scroll +#[derive(Debug)] +enum ScrollDirection { + /// Scroll up + /// + /// Lines move down + Up, + + /// Scroll down + /// + /// Lines move up + Down, +} + +impl fmt::Display for ScrollDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ScrollDirection::Up => write!(f, "up"), + ScrollDirection::Down => write!(f, "down"), } } } @@ -358,12 +413,12 @@ impl Term { impl ansi::TermInfo for Term { #[inline] fn rows(&self) -> usize { - self.grid.num_rows() + *self.grid.num_lines() as usize } #[inline] fn cols(&self) -> usize { - self.grid.num_cols() + *self.grid.num_cols() as usize } } @@ -372,90 +427,90 @@ impl ansi::Handler for Term { #[inline] fn input(&mut self, c: char) { self.set_char(c); - self.cursor.x += 1; + self.cursor.col += 1; } + #[inline] fn goto(&mut self, x: i64, y: i64) { println!("goto: x={}, y={}", x, y); - self.cursor.goto(x as u16, y as u16); + self.cursor.goto(Line(y as usize), Column(x as usize)); } - fn goto_row(&mut self, y: i64) { - println!("goto_row: {}", y); - let x = self.cursor_x(); - self.cursor.goto(x, y as u16); + + fn goto_row(&mut self, row: i64) { + println!("goto_row: {}", row); + self.cursor.line = Line(row as usize); } - fn goto_col(&mut self, x: i64) { - println!("goto_col: {}", x); - let y = self.cursor_y(); - self.cursor.goto(x as u16, y); + fn goto_col(&mut self, col: i64) { + println!("goto_col: {}", col); + self.cursor.col = Column(col as usize); } fn insert_blank(&mut self, num: i64) { println!("insert_blank: {}", num); } fn move_up(&mut self, rows: i64) { println!("move_up: {}", rows); - self.cursor.advance(-rows, 0); + self.cursor.line -= Line(rows as usize); } fn move_down(&mut self, rows: i64) { println!("move_down: {}", rows); - self.cursor.advance(rows, 0); + self.cursor.line += Line(rows as usize); } fn move_forward(&mut self, cols: i64) { println!("move_forward: {}", cols); - self.cursor.advance(0, cols); + self.cursor.col += Column(cols as usize); } fn move_backward(&mut self, spaces: i64) { println!("move_backward: {}", spaces); - self.cursor.advance(0, -spaces); + self.cursor.col -= Column(spaces as usize); } fn identify_terminal(&mut self) { println!("identify_terminal"); } fn move_down_and_cr(&mut self, rows: i64) { println!("move_down_and_cr: {}", rows); } fn move_up_and_cr(&mut self, rows: i64) { println!("move_up_and_cr: {}", rows); } + fn put_tab(&mut self, mut count: i64) { println!("put_tab: {}", count); - let mut x = self.cursor_x(); - while x < self.grid.num_cols() as u16 && count != 0 { + let mut col = self.cursor.col; + while col < self.grid.num_cols() && count != 0 { count -= 1; loop { - if x == self.grid.num_cols() as u16 || self.tabs[x as usize] { + if col == self.grid.num_cols() || self.tabs[*col as usize] { break; } - x += 1; + col += 1; } } - self.cursor.x = x; + self.cursor.col = col; } /// Backspace `count` characters #[inline] fn backspace(&mut self) { println!("backspace"); - self.cursor.x -= 1; + self.cursor.col -= 1; } /// Carriage return #[inline] fn carriage_return(&mut self) { println!("carriage_return"); - self.cursor.x = 0; + self.cursor.col = Column(0); } /// Linefeed #[inline] fn linefeed(&mut self) { println!("linefeed"); - // TODO handle scroll? not clear what parts of this the pty handle - if self.cursor_y() + 1 >= self.scroll_region.end as u16 { - self.scroll(1); + if self.cursor.line + 1 >= self.scroll_region.end { + self.scroll(Line(1), ScrollDirection::Down); self.clear_line(ansi::LineClearMode::Right); } else { - self.cursor.y += 1; + self.cursor.line += 1; } } @@ -466,31 +521,30 @@ impl ansi::Handler for Term { fn set_horizontal_tabstop(&mut self) { println!("set_horizontal_tabstop"); } fn scroll_up(&mut self, rows: i64) { println!("scroll_up: {}", rows); - self.scroll(rows as isize); + self.scroll(Line(rows as usize), ScrollDirection::Up); } fn scroll_down(&mut self, rows: i64) { println!("scroll_down: {}", rows); - self.scroll(-rows as isize); + self.scroll(Line(rows as usize), ScrollDirection::Down); } fn insert_blank_lines(&mut self, count: i64) { println!("insert_blank_lines: {}", count); - if self.scroll_region.contains(self.cursor_y() as usize) { - self.scroll(-count as isize); + if self.scroll_region.contains(self.cursor.line) { + self.scroll(Line(count as usize), ScrollDirection::Down); } } fn delete_lines(&mut self, count: i64) { - if self.scroll_region.contains(self.cursor_y() as usize) { - self.scroll(count as isize); + if self.scroll_region.contains(self.cursor.line) { + self.scroll(Line(count as usize), ScrollDirection::Up); } } fn erase_chars(&mut self, count: i64) { println!("erase_chars: {}", count); - let row_index = self.cursor.y as usize; - let col_index = self.cursor.x as usize; + let col_index = self.cursor.col; let count = count as usize; - let row = &mut self.grid[row_index]; - for c in &mut row[col_index..(count + col_index)] { + let row = &mut self.grid[self.cursor.line]; + for c in &mut row[self.cursor.col..(col_index + count)] { c.reset(); } } @@ -503,9 +557,8 @@ impl ansi::Handler for Term { println!("clear_line: {:?}", mode); match mode { ansi::LineClearMode::Right => { - let row = &mut self.grid[self.cursor.y as usize]; - let start = self.cursor.x as usize; - for cell in &mut row[start..] { + let row = &mut self.grid[self.cursor.line]; + for cell in &mut row[self.cursor.col..] { cell.reset(); } }, @@ -516,17 +569,17 @@ impl ansi::Handler for Term { println!("clear_screen: {:?}", mode); match mode { ansi::ClearMode::Below => { - let start = self.cursor_y() as usize; - let end = self.grid.num_rows(); - for i in start..end { - let row = &mut self.grid[i]; + let start = self.cursor.line; + let end = self.grid.num_lines(); + + for row in &mut self.grid[start..end] { for cell in row { cell.reset(); } } }, ansi::ClearMode::All => { - self.grid.clear(); + self.grid.clear(|c| c.reset()); }, _ => { panic!("ansi::ClearMode::Above not implemented"); @@ -538,13 +591,10 @@ impl ansi::Handler for Term { fn reverse_index(&mut self) { println!("reverse_index"); // if cursor is at the top - if self.cursor.y == 0 { - self.scroll(-1); + if self.cursor.col == Column(0) { + self.scroll(Line(1), ScrollDirection::Up); } else { - // can't wait for nonlexical lifetimes.. omg borrowck - let x = self.cursor.x; - let y = self.cursor.y; - self.cursor.goto(x, y - 1); + self.cursor.col -= 1; } } @@ -572,16 +622,16 @@ impl ansi::Handler for Term { Attr::Reset => { self.fg = DEFAULT_FG; self.bg = DEFAULT_BG; - self.attr = CellFlags::empty(); + self.attr = cell::Flags::empty(); }, - Attr::Reverse => self.attr.insert(grid::INVERSE), - Attr::CancelReverse => self.attr.remove(grid::INVERSE), - Attr::Bold => self.attr.insert(grid::BOLD), - Attr::CancelBoldDim => self.attr.remove(grid::BOLD), - Attr::Italic => self.attr.insert(grid::ITALIC), - Attr::CancelItalic => self.attr.remove(grid::ITALIC), - Attr::Underscore => self.attr.insert(grid::UNDERLINE), - Attr::CancelUnderline => self.attr.remove(grid::UNDERLINE), + Attr::Reverse => self.attr.insert(cell::INVERSE), + Attr::CancelReverse => self.attr.remove(cell::INVERSE), + Attr::Bold => self.attr.insert(cell::BOLD), + Attr::CancelBoldDim => self.attr.remove(cell::BOLD), + Attr::Italic => self.attr.insert(cell::ITALIC), + Attr::CancelItalic => self.attr.remove(cell::ITALIC), + Attr::Underscore => self.attr.insert(cell::UNDERLINE), + Attr::CancelUnderline => self.attr.remove(cell::UNDERLINE), _ => { println!("Term got unhandled attr: {:?}", attr); } @@ -615,6 +665,6 @@ impl ansi::Handler for Term { fn set_scrolling_region(&mut self, top: i64, bot: i64) { println!("set scroll region: {:?} - {:?}", top, bot); // 1 is added to bottom for inclusive range - self.scroll_region = (top as usize)..((bot as usize) + 1); + self.scroll_region = Line(top as usize)..Line((bot as usize) + 1); } } |