aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/grid.rs620
-rw-r--r--src/main.rs2
-rw-r--r--src/renderer/mod.rs27
-rw-r--r--src/term.rs350
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);
}
}