Add support for scrolling regions

It's now possible to move around within Vim without the screen becoming
corrupt!

The ANSI parser now calls a (new) `set_scrolling_region` on the handler
when the DECSTBM CSI is received. In order to provide a sensible default
in case that the sequence doesn't include arguments, a TermInfo trait
was added which currently has methods for inspecting number of rows and
columns. This was added as an additional trait instead of being included
on Handler since they have semantically different purposes. The tests
had to be updated to account for the additional trait bounds.

The utilities module now has a `Rotate` trait which is implemented for
the built-in slice type. This means that slices and anything derefing to
a slice can be rotated. Since VecDeque doesn't support slicing (it's
a circular buffer), the grid rows are now held in a Vec to support
rotation.

For ergomomic access to the grid for scrolling and clearing regions,
additional Index/IndexMut implementations were added to the grid::Row
type.

Finally, a `reset` method was added to `Cell` which properly resets the
state to default (instead of just clearing the char). This supports
region clearing and also fixed a bug where cell backgrounds would remain
after being cleared.
This commit is contained in:
Joe Wilm 2016-06-08 10:39:49 -07:00
parent 0e7bb8d76e
commit 8126841ed3
No known key found for this signature in database
GPG Key ID: 39B57C6972F518DA
4 changed files with 262 additions and 62 deletions

View File

@ -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;
#[derive(Default)]
struct AttrHandler {
attr: Option<Attr>,
}
impl Handler for AttrHandler {
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() {
#[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, 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());

View File

@ -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 unfeed(&mut self) {
// do the borrowck dance
let row = self.raw.pop_back().unwrap();
self.raw.push_front(row);
pub fn scroll(&mut self, region: Range<usize>, positions: isize) {
self.raw[region].rotate(positions)
}
#[inline]
pub fn clear(&mut self) {
for row in self.raw.iter_mut() {
let region = 0..self.num_rows();
self.clear_region(region);
}
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]
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}