diff options
-rw-r--r-- | src/ansi.rs | 91 | ||||
-rw-r--r-- | src/grid.rs | 76 | ||||
-rw-r--r-- | src/term.rs | 72 | ||||
-rw-r--r-- | src/util.rs | 79 |
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); + } +} |