aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ansi.rs91
-rw-r--r--src/grid.rs76
-rw-r--r--src/term.rs72
-rw-r--r--src/util.rs79
4 files changed, 259 insertions, 59 deletions
diff --git a/src/ansi.rs b/src/ansi.rs
index fe271c82..c1829f19 100644
--- a/src/ansi.rs
+++ b/src/ansi.rs
@@ -26,6 +26,12 @@ pub enum Escape {
DisplayAttr(u8),
}
+/// Trait that provides properties of terminal
+pub trait TermInfo {
+ fn rows(&self) -> usize;
+ fn cols(&self) -> usize;
+}
+
/// Control requiring action
#[derive(Debug, Eq, PartialEq)]
pub enum Control {
@@ -359,6 +365,9 @@ pub trait Handler {
/// Unset mode
fn unset_mode(&mut self, Mode) {}
+
+ /// DECSTBM - Set the terminal scrolling region
+ fn set_scrolling_region(&mut self, top: i64, bot: i64) {}
}
/// An implementation of handler that just prints everything it gets
@@ -403,6 +412,19 @@ impl Handler for DebugHandler {
fn terminal_attribute(&mut self, attr: Attr) { println!("terminal_attribute: {:?}", attr); }
fn set_mode(&mut self, mode: Mode) { println!("set_mode: {:?}", mode); }
fn unset_mode(&mut self, mode: Mode) { println!("unset_mode: {:?}", mode); }
+ fn set_scrolling_region(&mut self, top: i64, bot: i64) {
+ println!("set scroll region: {:?} - {:?}", top, bot);
+ }
+}
+
+impl TermInfo for DebugHandler {
+ fn rows(&self) -> usize {
+ 24
+ }
+
+ fn cols(&self) -> usize {
+ 80
+ }
}
impl Parser {
@@ -418,7 +440,7 @@ impl Parser {
///
/// Maybe returns an Item which represents a state change of the terminal
pub fn advance<H>(&mut self, handler: &mut H, c: char)
- where H: Handler
+ where H: Handler + TermInfo
{
// println!("state: {:?}; char: {:?}", self.state, c);
// Control characters get handled immediately
@@ -444,13 +466,13 @@ impl Parser {
}
fn advance_base<H>(&mut self, handler: &mut H, c: char)
- where H: Handler
+ where H: Handler + TermInfo
{
handler.input(c);
}
fn other<H>(&mut self, handler: &mut H, c: char)
- where H: Handler
+ where H: Handler + TermInfo
{
if c == 0x07 as char || c == 0x18 as char || c == 0x1a as char ||
c == 0x1b as char || is_control_c1(c)
@@ -466,7 +488,7 @@ impl Parser {
/// TODO Handle `ST`, `'#'`, `'P'`, `'_'`, `'^'`, `']'`, `'k'`,
/// 'n', 'o', '(', ')', '*', '+', '=', '>'
fn escape<H>(&mut self, handler: &mut H, c: char)
- where H: Handler
+ where H: Handler + TermInfo
{
// Helper for items which complete a sequence.
macro_rules! sequence_complete {
@@ -499,7 +521,7 @@ impl Parser {
}
fn csi<H>(&mut self, handler: &mut H, c: char)
- where H: Handler
+ where H: Handler + TermInfo
{
self.buf[self.idx] = c;
self.idx += 1;
@@ -513,7 +535,7 @@ impl Parser {
///
/// ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
fn csi_parse<H>(&mut self, handler: &mut H)
- where H: Handler
+ where H: Handler + TermInfo
{
let mut idx = 0;
let mut args = [0i64; CSI_ATTR_MAX];
@@ -739,7 +761,15 @@ impl Parser {
}
}
'n' => handler.identify_terminal(),
- 'r' => unknown!(), // set scrolling region
+ 'r' => {
+ if private {
+ unknown!();
+ }
+ let top = arg_or_default!(args[0], 1);
+ let bottom = arg_or_default!(args[1], handler.rows() as i64);
+
+ handler.set_scrolling_region(top - 1, bottom - 1);
+ },
's' => handler.save_cursor_position(),
'u' => handler.restore_cursor_position(),
_ => unknown!(),
@@ -753,7 +783,7 @@ impl Parser {
}
fn control<H>(&mut self, handler: &mut H, c: char)
- where H: Handler
+ where H: Handler + TermInfo
{
match c {
C0::HT => handler.put_tab(1),
@@ -1094,29 +1124,39 @@ impl Default for State {
#[cfg(test)]
mod tests {
use std::io::{Cursor, Read};
- use super::{Parser, Escape, Handler, Attr, Rgb, DebugHandler};
+ use super::{Parser, Escape, Handler, Attr, DebugHandler, TermInfo};
use ::Rgb;
- #[test]
- fn parse_control_attribute() {
- #[derive(Default)]
- struct TestHandler {
- attr: Option<Attr>,
+ #[derive(Default)]
+ struct AttrHandler {
+ attr: Option<Attr>,
+ }
+
+ impl Handler for AttrHandler {
+ fn terminal_attribute(&mut self, attr: Attr) {
+ self.attr = Some(attr);
}
+ }
- impl Handler for TestHandler {
- fn terminal_attribute(&mut self, attr: Attr) {
- self.attr = Some(attr);
- }
+ impl TermInfo for AttrHandler {
+ fn rows(&self) -> usize {
+ 24
}
+ fn cols(&self) -> usize {
+ 80
+ }
+ }
+
+ #[test]
+ fn parse_control_attribute() {
static BYTES: &'static [u8] = &[
0x1b, 0x5b, 0x31, 0x6d
];
let cursor = Cursor::new(BYTES);
let mut parser = Parser::new();
- let mut handler = TestHandler::default();
+ let mut handler = AttrHandler::default();
for c in cursor.chars() {
parser.advance(&mut handler, c.unwrap());
@@ -1127,17 +1167,6 @@ mod tests {
#[test]
fn parse_truecolor_attr() {
- #[derive(Default)]
- struct TestHandler {
- attr: Option<Attr>,
- }
-
- impl Handler for TestHandler {
- fn terminal_attribute(&mut self, attr: Attr) {
- self.attr = Some(attr);
- }
- }
-
static BYTES: &'static [u8] = &[
0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x32, 0x3b, 0x31, 0x32,
0x38, 0x3b, 0x36, 0x36, 0x3b, 0x32, 0x35, 0x35, 0x6d
@@ -1145,7 +1174,7 @@ mod tests {
let mut cursor = Cursor::new(BYTES);
let mut parser = Parser::new();
- let mut handler = TestHandler::default();
+ let mut handler = AttrHandler::default();
for c in cursor.chars() {
parser.advance(&mut handler, c.unwrap());
diff --git a/src/grid.rs b/src/grid.rs
index e094867d..7a1fedb8 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -1,10 +1,11 @@
//! Functions for computing properties of the terminal grid
-use std::collections::{vec_deque, VecDeque};
-use std::ops::{Index, IndexMut, Deref, DerefMut};
+use std::ops::{Index, IndexMut, Deref, DerefMut, Range, RangeTo, RangeFrom};
use std::slice::{Iter, IterMut};
-use term::Cursor;
+use util::Rotate;
+
+use term::{Cursor, DEFAULT_FG, DEFAULT_BG};
use ::Rgb;
/// Calculate the number of cells for an axis
@@ -40,13 +41,22 @@ impl Cell {
flags: CellFlags::empty(),
}
}
+
+ pub fn reset(&mut self) {
+ self.c = ' ';
+ self.flags = CellFlags::empty();
+
+ // FIXME shouldn't know about term
+ self.bg = DEFAULT_BG;
+ self.fg = DEFAULT_FG;
+ }
}
/// 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: VecDeque<Row>,
+ raw: Vec<Row>,
/// Number of columns
cols: usize,
@@ -59,9 +69,9 @@ pub struct Grid {
impl Grid {
pub fn new(rows: usize, cols: usize) -> Grid {
- let mut raw = VecDeque::with_capacity(rows);
+ let mut raw = Vec::with_capacity(rows);
for _ in 0..rows {
- raw.push_back(Row::new(cols));
+ raw.push(Row::new(cols));
}
Grid {
@@ -72,12 +82,12 @@ impl Grid {
}
#[inline]
- pub fn rows(&self) -> vec_deque::Iter<Row> {
+ pub fn rows(&self) -> Iter<Row> {
self.raw.iter()
}
#[inline]
- pub fn rows_mut(&mut self) -> vec_deque::IterMut<Row> {
+ pub fn rows_mut(&mut self) -> IterMut<Row> {
self.raw.iter_mut()
}
@@ -91,22 +101,20 @@ impl Grid {
self.raw[0].len()
}
- pub fn feed(&mut self) {
- // do the borrowck dance
- let row = self.raw.pop_front().unwrap();
- self.raw.push_back(row);
+ pub fn scroll(&mut self, region: Range<usize>, positions: isize) {
+ self.raw[region].rotate(positions)
}
- pub fn unfeed(&mut self) {
- // do the borrowck dance
- let row = self.raw.pop_back().unwrap();
- self.raw.push_front(row);
+ #[inline]
+ pub fn clear(&mut self) {
+ let region = 0..self.num_rows();
+ self.clear_region(region);
}
- pub fn clear(&mut self) {
- for row in self.raw.iter_mut() {
+ pub fn clear_region(&mut self, region: Range<usize>) {
+ for row in self.raw[region].iter_mut() {
for cell in row.iter_mut() {
- cell.c = ' ';
+ cell.reset();
}
}
}
@@ -190,3 +198,33 @@ impl IndexMut<usize> for Row {
&mut self.0[index]
}
}
+
+impl Index<RangeFrom<usize>> for Row {
+ type Output = [Cell];
+ #[inline]
+ fn index<'a>(&'a self, index: RangeFrom<usize>) -> &'a [Cell] {
+ &self.0[index]
+ }
+}
+
+impl IndexMut<RangeFrom<usize>> for Row {
+ #[inline]
+ fn index_mut<'a>(&'a mut self, index: RangeFrom<usize>) -> &'a mut [Cell] {
+ &mut self.0[index]
+ }
+}
+
+impl Index<RangeTo<usize>> for Row {
+ type Output = [Cell];
+ #[inline]
+ fn index<'a>(&'a self, index: RangeTo<usize>) -> &'a [Cell] {
+ &self.0[index]
+ }
+}
+
+impl IndexMut<RangeTo<usize>> for Row {
+ #[inline]
+ fn index_mut<'a>(&'a mut self, index: RangeTo<usize>) -> &'a mut [Cell] {
+ &mut self.0[index]
+ }
+}
diff --git a/src/term.rs b/src/term.rs
index dedd9d9c..0bcf6b24 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,5 +1,6 @@
/// Exports the `Term` type which is a high-level API for the Grid
use std::sync::Arc;
+use std::ops::Range;
use ansi::{self, Attr, DebugHandler};
use grid::{self, Grid, CellFlags};
@@ -109,6 +110,9 @@ pub struct Term {
/// Mode flags
mode: TermMode,
+
+ /// Scroll region
+ scroll_region: Range<usize>,
}
impl Term {
@@ -119,6 +123,7 @@ impl Term {
tabs[0] = false;
let alt = grid.clone();
+ let scroll_region = 0..grid.num_rows();
Term {
grid: grid,
@@ -133,6 +138,7 @@ impl Term {
attr: CellFlags::empty(),
dirty: false,
mode: TermMode::empty(),
+ scroll_region: scroll_region,
}
}
@@ -208,6 +214,33 @@ impl Term {
self.cursor.x = 0;
self.cursor.y += 1;
}
+
+ /// 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);
+ }
+ }
+}
+
+impl ansi::TermInfo for Term {
+ #[inline]
+ fn rows(&self) -> usize {
+ self.grid.num_rows()
+ }
+
+ #[inline]
+ fn cols(&self) -> usize {
+ self.grid.num_cols()
+ }
}
impl ansi::Handler for Term {
@@ -306,8 +339,8 @@ impl ansi::Handler for Term {
self.dirty = true;
println!("linefeed");
// TODO handle scroll? not clear what parts of this the pty handle
- if self.cursor_y() + 1 == self.grid.num_rows() as u16 {
- self.grid.feed();
+ if self.cursor_y() + 1 >= self.scroll_region.end as u16 {
+ self.scroll(1);
self.clear_line(ansi::LineClearMode::Right);
} else {
self.cursor.y += 1;
@@ -319,10 +352,25 @@ impl ansi::Handler for Term {
fn substitute(&mut self) { println!("substitute"); }
fn newline(&mut self) { println!("newline"); }
fn set_horizontal_tabstop(&mut self) { println!("set_horizontal_tabstop"); }
- fn scroll_up(&mut self, rows: i64) { println!("scroll_up: {}", rows); }
- fn scroll_down(&mut self, rows: i64) { println!("scroll_down: {}", rows); }
- fn insert_blank_lines(&mut self, count: i64) { println!("insert_blank_lines: {}", count); }
- fn delete_lines(&mut self, count: i64) { println!("delete_lines: {}", count); }
+ fn scroll_up(&mut self, rows: i64) {
+ println!("scroll_up: {}", rows);
+ self.scroll(-rows as isize);
+ }
+ fn scroll_down(&mut self, rows: i64) {
+ println!("scroll_down: {}", rows);
+ self.scroll(rows as isize);
+ }
+ 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);
+ }
+ }
+ fn delete_lines(&mut self, count: i64) {
+ if self.scroll_region.contains(self.cursor_y() as usize) {
+ self.scroll(count as isize);
+ }
+ }
fn erase_chars(&mut self, count: i64) { println!("erase_chars: {}", count); }
fn delete_chars(&mut self, count: i64) { println!("delete_chars: {}", count); }
fn move_backward_tabs(&mut self, count: i64) { println!("move_backward_tabs: {}", count); }
@@ -337,8 +385,8 @@ impl ansi::Handler for Term {
let cols = self.grid.num_cols();
let row = &mut self.grid[self.cursor.y as usize];
let start = self.cursor.x as usize;
- for col in start..cols {
- row[col].c = ' ';
+ for cell in row[start..].iter_mut() {
+ cell.reset();
}
},
_ => (),
@@ -373,7 +421,7 @@ impl ansi::Handler for Term {
println!("reverse_index");
// if cursor is at the top
if self.cursor.y == 0 {
- self.grid.unfeed();
+ self.scroll(-1);
} else {
// can't wait for nonlexical lifetimes.. omg borrowck
let x = self.cursor.x;
@@ -443,4 +491,10 @@ 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);
+ }
}
diff --git a/src/util.rs b/src/util.rs
index 0a3de227..aa382560 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,3 +1,5 @@
+use std::iter::Iterator;
+
/// Threading utilities
pub mod thread {
/// Like `thread::spawn`, but with a `name` argument
@@ -10,3 +12,80 @@ pub mod thread {
::std::thread::Builder::new().name(name.into()).spawn(f).expect("thread spawn works")
}
}
+
+/// Types that can have their elements rotated
+pub trait Rotate {
+ fn rotate(&mut self, positions: isize);
+}
+
+impl<T> Rotate for [T] {
+ fn rotate(&mut self, positions: isize) {
+ // length is needed over and over
+ let len = self.len();
+
+ // Enforce positions in [0, len) and treat negative rotations as a
+ // posititive rotation of len - positions.
+ let positions = if positions > 0 {
+ positions as usize % len
+ } else {
+ len - (-positions as usize) % len
+ };
+
+ // If positions is 0 or the entire slice, it's a noop.
+ if positions == 0 || positions == len {
+ return;
+ }
+
+ self[..positions].reverse();
+ self[positions..].reverse();
+ self.reverse();
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use super::Rotate;
+
+ #[test]
+ fn rotate_forwards_works() {
+ let s = &mut [1, 2, 3, 4, 5];
+ s.rotate(1);
+ assert_eq!(&[2, 3, 4, 5, 1], s);
+ }
+
+ #[test]
+ fn rotate_backwards_works() {
+ let s = &mut [1, 2, 3, 4, 5];
+ s.rotate(-1);
+ assert_eq!(&[5, 1, 2, 3, 4], s);
+ }
+
+ #[test]
+ fn rotate_multiple_forwards() {
+ let s = &mut [1, 2, 3, 4, 5, 6, 7];
+ s.rotate(2);
+ assert_eq!(&[3, 4, 5, 6, 7, 1, 2], s);
+ }
+
+ #[test]
+ fn rotate_multiple_backwards() {
+ let s = &mut [1, 2, 3, 4, 5];
+ s.rotate(-3);
+ assert_eq!(&[3, 4, 5, 1, 2], s);
+ }
+
+ #[test]
+ fn rotate_forwards_overflow() {
+ let s = &mut [1, 2, 3, 4, 5];
+ s.rotate(6);
+ assert_eq!(&[2, 3, 4, 5, 1], s);
+ }
+
+ #[test]
+ fn rotate_backwards_overflow() {
+ let s = &mut [1, 2, 3, 4, 5];
+ s.rotate(-6);
+ assert_eq!(&[5, 1, 2, 3, 4], s);
+ }
+}