diff options
author | Joe Wilm <joe@jwilm.com> | 2016-11-28 14:30:08 -0800 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-12-11 20:23:41 -0800 |
commit | 7bf3d059c39c0b8e4529db6f7d84a1e1997738e8 (patch) | |
tree | 5db6d111a8af454b29beecc25bfa49ebc24b0d9c /src/term | |
parent | 74dcba59d8bec0a228cf1350bccab2aabd0304dd (diff) | |
download | alacritty-7bf3d059c39c0b8e4529db6f7d84a1e1997738e8.tar.gz alacritty-7bf3d059c39c0b8e4529db6f7d84a1e1997738e8.zip |
Move term::cell module to its own file
The cell module was previously implemented within term.rs. Now each
module has its own file.
Diffstat (limited to 'src/term')
-rw-r--r-- | src/term/cell.rs | 76 | ||||
-rw-r--r-- | src/term/mod.rs | 978 |
2 files changed, 1054 insertions, 0 deletions
diff --git a/src/term/cell.rs b/src/term/cell.rs new file mode 100644 index 00000000..506fde0e --- /dev/null +++ b/src/term/cell.rs @@ -0,0 +1,76 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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. + + +use std::mem; + +use ansi; +use Rgb; + +bitflags! { + #[derive(Serialize, Deserialize)] + pub flags Flags: u32 { + const INVERSE = 0b00000001, + const BOLD = 0b00000010, + const ITALIC = 0b00000100, + const UNDERLINE = 0b00001000, + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Color { + Rgb(Rgb), + Ansi(ansi::Color), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Cell { + pub c: char, + pub fg: Color, + pub bg: Color, + pub flags: Flags, +} + +impl Cell { + pub fn bold(&self) -> bool { + self.flags.contains(BOLD) + } + + pub fn new(c: char, fg: Color, bg: Color) -> Cell { + Cell { + c: c.into(), + bg: bg, + fg: fg, + flags: Flags::empty(), + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.c == ' ' && + self.bg == Color::Ansi(ansi::Color::Background) && + !self.flags.contains(INVERSE) + } + + #[inline] + pub fn reset(&mut self, template: &Cell) { + // memcpy template to self + *self = template.clone(); + } + + #[inline] + pub fn swap_fg_and_bg(&mut self) { + mem::swap(&mut self.fg, &mut self.bg); + } +} diff --git a/src/term/mod.rs b/src/term/mod.rs new file mode 100644 index 00000000..b5ccb244 --- /dev/null +++ b/src/term/mod.rs @@ -0,0 +1,978 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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. +// +//! Exports the `Term` type which is a high-level API for the Grid +use std::ops::{Deref, Range}; +use std::ptr; +use std::cmp; + +use ansi::{self, Attr, Handler}; +use grid::{Grid, ClearRegion}; +use index::{Cursor, Column, Line}; +use ansi::Color; + +pub mod cell; +pub use self::cell::Cell; + +/// Iterator that yields cells needing render +/// +/// Yields cells that require work to be displayed (that is, not a an empty +/// background cell). Additionally, this manages some state of the grid only +/// relevant for rendering like temporarily changing the cell with the cursor. +/// +/// This manages the cursor during a render. The cursor location is inverted to +/// draw it, and reverted after drawing to maintain state. +pub struct RenderableCellsIter<'a> { + grid: &'a mut Grid<Cell>, + cursor: &'a Cursor, + mode: TermMode, + line: Line, + column: Column, +} + + +impl<'a> RenderableCellsIter<'a> { + /// Create the renderable cells iterator + /// + /// The cursor and terminal mode are required for properly displaying the + /// cursor. + fn new<'b>( + grid: &'b mut Grid<Cell>, + cursor: &'b Cursor, + mode: TermMode + ) -> RenderableCellsIter<'b> { + RenderableCellsIter { + grid: grid, + cursor: cursor, + mode: mode, + line: Line(0), + column: Column(0), + }.initialize() + } + + fn initialize(self) -> Self { + if self.cursor_is_visible() { + self.grid[self.cursor].swap_fg_and_bg(); + } + + self + } + + /// Check if the cursor should be rendered. + #[inline] + fn cursor_is_visible(&self) -> bool { + self.mode.contains(mode::SHOW_CURSOR) && self.grid.contains(self.cursor) + } +} + +impl<'a> Drop for RenderableCellsIter<'a> { + /// Resets temporary render state on the grid + fn drop(&mut self) { + if self.cursor_is_visible() { + self.grid[self.cursor].swap_fg_and_bg(); + } + } +} + +pub struct IndexedCell { + pub line: Line, + pub column: Column, + pub inner: Cell +} + +impl Deref for IndexedCell { + type Target = Cell; + + #[inline(always)] + fn deref(&self) -> &Cell { + &self.inner + } +} + +impl<'a> Iterator for RenderableCellsIter<'a> { + type Item = IndexedCell; + + /// Gets the next renderable cell + /// + /// Skips empty (background) cells and applies any flags to the cell state + /// (eg. invert fg and bg colors). + #[inline(always)] + fn next(&mut self) -> Option<Self::Item> { + while self.line < self.grid.num_lines() { + while self.column < self.grid.num_cols() { + // Grab current state for this iteration + let line = self.line; + let column = self.column; + let cell = &self.grid[line][column]; + + // Update state for next iteration + self.column += 1; + + // Skip empty cells + if cell.is_empty() { + continue; + } + + // fg, bg are dependent on INVERSE flag + let (fg, bg) = if cell.flags.contains(cell::INVERSE) { + (&cell.bg, &cell.fg) + } else { + (&cell.fg, &cell.bg) + }; + + return Some(IndexedCell { + line: line, + column: column, + inner: Cell { + flags: cell.flags, + c: cell.c, + fg: *fg, + bg: *bg, + } + }) + } + + self.column = Column(0); + self.line += 1; + } + + None + } +} + +/// coerce val to be between min and max +#[inline] +fn limit<T: PartialOrd + Ord>(val: T, min: T, max: T) -> T { + cmp::min(cmp::max(min, val), max) +} + +pub mod mode { + bitflags! { + pub flags TermMode: u8 { + const SHOW_CURSOR = 0b00000001, + const APP_CURSOR = 0b00000010, + const APP_KEYPAD = 0b00000100, + const MOUSE_REPORT_CLICK = 0b00001000, + const ANY = 0b11111111, + const NONE = 0b00000000, + } + } + + impl Default for TermMode { + fn default() -> TermMode { + SHOW_CURSOR + } + } +} + +pub use self::mode::TermMode; + +pub const TAB_SPACES: usize = 8; + +pub struct Term { + /// The grid + grid: Grid<Cell>, + + /// Alternate grid + alt_grid: Grid<Cell>, + + /// Alt is active + alt: bool, + + /// The cursor + cursor: Cursor, + + /// Alt cursor + alt_cursor: Cursor, + + /// Tabstops + tabs: Vec<bool>, + + /// Mode flags + mode: TermMode, + + /// Scroll region + scroll_region: Range<Line>, + + /// Size + size_info: SizeInfo, + + /// Template cell + template_cell: Cell, + + /// Empty cell + empty_cell: Cell, + + pub dirty: bool, +} + +/// Terminal size info +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct SizeInfo { + /// Terminal window width + pub width: f32, + + /// Terminal window height + pub height: f32, + + /// Width of individual cell + pub cell_width: f32, + + /// Height of individual cell + pub cell_height: f32, +} + +impl SizeInfo { + #[inline] + pub fn lines(&self) -> Line { + Line((self.height / self.cell_height) as usize) + } + + #[inline] + pub fn cols(&self) -> Column { + Column((self.width / self.cell_width) as usize) + } +} + +impl Term { + pub fn new(size: SizeInfo) -> Term { + let template = Cell::new( + ' ', + cell::Color::Ansi(Color::Foreground), + cell::Color::Ansi(Color::Background) + ); + + let num_cols = size.cols(); + let num_lines = size.lines(); + + let grid = Grid::new(num_lines, num_cols, &template); + + 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 = Line(0)..grid.num_lines(); + + Term { + dirty: true, + grid: grid, + alt_grid: alt, + alt: false, + cursor: Cursor::default(), + alt_cursor: Cursor::default(), + tabs: tabs, + mode: Default::default(), + scroll_region: scroll_region, + size_info: size, + template_cell: template.clone(), + empty_cell: template, + } + } + + /// Convert the given pixel values to a grid coordinate + /// + /// The mouse coordinates are expected to be relative to the top left. The + /// line and column returned are also relative to the top left. + /// + /// Returns None if the coordinates are outside the screen + pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<(Line, Column)> { + let size = self.size_info(); + if x > size.width as usize || y > size.height as usize { + return None; + } + + let col = Column(x / (size.cell_width as usize)); + let line = Line(y / (size.cell_height as usize)); + + Some((line, col)) + } + + /// Access to the raw grid data structure + /// + /// This is a bit of a hack; when the window is closed, the event processor + /// serializes the grid state to a file. + pub fn grid(&self) -> &Grid<Cell> { + &self.grid + } + + /// Iterate over the *renderable* cells in the terminal + /// + /// A renderable cell is any cell which has content other than the default + /// background color. Cells with an alternate background color are + /// considered renderable as are cells with any text content. + pub fn renderable_cells<'a>(&'a mut self) -> RenderableCellsIter<'a> { + RenderableCellsIter::new(&mut self.grid, &self.cursor, self.mode) + } + + /// Resize terminal to new dimensions + pub fn resize(&mut self, width: f32, height: f32) { + let size = SizeInfo { + width: width, + height: height, + cell_width: self.size_info.cell_width, + cell_height: self.size_info.cell_height, + }; + + let old_cols = self.size_info.cols(); + let old_lines = self.size_info.lines(); + let num_cols = size.cols(); + let num_lines = size.lines(); + + self.size_info = size; + + 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 + // 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_up(lines); + self.cursor.line -= lines; + } + + println!("num_cols, num_lines = {}, {}", num_cols, num_lines); + + // Resize grids to new size + let template = self.template_cell.clone(); + self.grid.resize(num_lines, num_cols, &template); + self.alt_grid.resize(num_lines, num_cols, &template); + + // Ensure cursor is in-bounds + 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 = (Column(0)..self.grid.num_cols()).map(|i| (*i as usize) % TAB_SPACES == 0) + .collect::<Vec<bool>>(); + + self.tabs[0] = false; + + // Make sure bottom of terminal is clear + let template = self.empty_cell.clone(); + self.grid.clear_region((self.cursor.line).., |c| c.reset(&template)); + self.alt_grid.clear_region((self.cursor.line).., |c| c.reset(&template)); + + // Reset scrolling region to new size + self.scroll_region = Line(0)..self.grid.num_lines(); + } + + #[inline] + pub fn size_info(&self) -> &SizeInfo { + &self.size_info + } + + #[inline] + pub fn mode(&self) -> &TermMode { + &self.mode + } + + pub fn swap_alt(&mut self) { + self.alt = !self.alt; + ::std::mem::swap(&mut self.grid, &mut self.alt_grid); + ::std::mem::swap(&mut self.cursor, &mut self.alt_cursor); + + if self.alt { + let template = self.empty_cell.clone(); + self.grid.clear(|c| c.reset(&template)); + } + } + + /// Scroll screen down + /// + /// Text moves down; clear at bottom + #[inline] + fn scroll_down_relative(&mut self, origin: Line, lines: Line) { + debug_println!("scroll_down: {}", lines); + + // Copy of cell template; can't have it borrowed when calling clear/scroll + let template = self.empty_cell.clone(); + + // Clear `lines` lines at bottom of area + { + let end = self.scroll_region.end; + let start = end - lines; + self.grid.clear_region(start..end, |c| c.reset(&template)); + } + + // Scroll between origin and bottom + { + let end = self.scroll_region.end; + let start = origin + lines; + self.grid.scroll_down(start..end, lines); + } + } + + /// Scroll screen up + /// + /// Text moves up; clear at top + #[inline] + fn scroll_up_relative(&mut self, origin: Line, lines: Line) { + debug_println!("scroll_up: {}", lines); + + // Copy of cell template; can't have it borrowed when calling clear/scroll + let template = self.empty_cell.clone(); + + // Clear `lines` lines starting from origin to origin + lines + { + let start = origin; + let end = start + lines; + self.grid.clear_region(start..end, |c| c.reset(&template)); + } + + // Scroll from origin to bottom less number of lines + { + let start = origin; + let end = self.scroll_region.end - lines; + self.grid.scroll_up(start..end, lines); + } + } +} + +impl ansi::TermInfo for Term { + #[inline] + fn lines(&self) -> Line { + self.grid.num_lines() + } + + #[inline] + fn cols(&self) -> Column { + self.grid.num_cols() + } +} + +impl ansi::Handler for Term { + /// A character to be displayed + #[inline] + fn input(&mut self, c: char) { + if self.cursor.col == self.grid.num_cols() { + debug_println!("wrapping"); + if (self.cursor.line + 1) >= self.scroll_region.end { + self.linefeed(); + } else { + self.cursor.line += 1; + } + self.cursor.col = Column(0); + } + + unsafe { + if ::std::intrinsics::unlikely(self.cursor.line == self.grid.num_lines()) { + panic!("cursor fell off grid"); + } + } + + let cell = &mut self.grid[&self.cursor]; + *cell = self.template_cell.clone(); + cell.c = c; + self.cursor.col += 1; + } + + #[inline] + fn goto(&mut self, line: Line, col: Column) { + debug_println!("goto: line={}, col={}", line, col); + self.cursor.line = line; + self.cursor.col = col; + } + + #[inline] + fn goto_line(&mut self, line: Line) { + debug_println!("goto_line: {}", line); + self.cursor.line = line; + } + + #[inline] + fn goto_col(&mut self, col: Column) { + debug_println!("goto_col: {}", col); + self.cursor.col = col; + } + + #[inline] + fn insert_blank(&mut self, count: Column) { + // Ensure inserting within terminal bounds + let count = ::std::cmp::min(count, self.size_info.cols() - self.cursor.col); + + let source = self.cursor.col; + let destination = self.cursor.col + count; + let num_cells = (self.size_info.cols() - destination).0; + + let line = self.cursor.line; // borrowck + let line = &mut self.grid[line]; + + unsafe { + let src = line[source..].as_ptr(); + let dst = line[destination..].as_mut_ptr(); + + ptr::copy(src, dst, num_cells); + } + + // Cells were just moved out towards the end of the line; fill in + // between source and dest with blanks. + let template = self.empty_cell.clone(); + for c in &mut line[source..destination] { + c.reset(&template); + } + } + + #[inline] + fn move_up(&mut self, lines: Line) { + debug_println!("move_up: {}", lines); + self.cursor.line -= lines; + } + + #[inline] + fn move_down(&mut self, lines: Line) { + debug_println!("move_down: {}", lines); + self.cursor.line += lines; + } + + #[inline] + fn move_forward(&mut self, cols: Column) { + debug_println!("move_forward: {}", cols); + self.cursor.col += cols; + } + + #[inline] + fn move_backward(&mut self, cols: Column) { + debug_println!("move_backward: {}", cols); + self.cursor.col -= cols; + } + + #[inline] + fn identify_terminal(&mut self) { + err_println!("[unimplemented] identify_terminal"); + } + + #[inline] + fn move_down_and_cr(&mut self, lines: Line) { + err_println!("[unimplemented] move_down_and_cr: {}", lines); + } + + #[inline] + fn move_up_and_cr(&mut self, lines: Line) { + err_println!("[unimplemented] move_up_and_cr: {}", lines); + } + + #[inline] + fn put_tab(&mut self, mut count: i64) { + debug_println!("put_tab: {}", count); + + let mut col = self.cursor.col; + while col < self.grid.num_cols() && count != 0 { + count -= 1; + loop { + if col == self.grid.num_cols() || self.tabs[*col as usize] { + break; + } + col += 1; + } + } + + self.cursor.col = col; + } + + /// Backspace `count` characters + #[inline] + fn backspace(&mut self) { + debug_println!("backspace"); + if self.cursor.col > Column(0) { + self.cursor.col -= 1; + } + } + + /// Carriage return + #[inline] + fn carriage_return(&mut self) { + debug_println!("carriage_return"); + self.cursor.col = Column(0); + } + + /// Linefeed + #[inline] + fn linefeed(&mut self) { + debug_println!("linefeed"); + if self.cursor.line + 1 == self.scroll_region.end { + self.scroll_up(Line(1)); + } else { + self.cursor.line += 1; + } + } + + /// Set current position as a tabstop + #[inline] + fn bell(&mut self) { + debug_println!("bell"); + } + + #[inline] + fn substitute(&mut self) { + err_println!("[unimplemented] substitute"); + } + + #[inline] + fn newline(&mut self) { + err_println!("[unimplemented] newline"); + } + + #[inline] + fn set_horizontal_tabstop(&mut self) { + err_println!("[unimplemented] set_horizontal_tabstop"); + } + + #[inline] + fn scroll_up(&mut self, lines: Line) { + let origin = self.scroll_region.start; + self.scroll_up_relative(origin, lines); + } + + #[inline] + fn scroll_down(&mut self, lines: Line) { + let origin = self.scroll_region.start; + self.scroll_down_relative(origin, lines); + } + + #[inline] + fn insert_blank_lines(&mut self, lines: Line) { + debug_println!("insert_blank_lines: {}", lines); + if self.scroll_region.contains(self.cursor.line) { + let origin = self.cursor.line; + self.scroll_down_relative(origin, lines); + } + } + + #[inline] + fn delete_lines(&mut self, lines: Line) { + debug_println!("delete_lines: {}", lines); + if self.scroll_region.contains(self.cursor.line) { + let origin = self.cursor.line; + self.scroll_up_relative(origin, lines); + } + } + + #[inline] + fn erase_chars(&mut self, count: Column) { + debug_println!("erase_chars: {}", count); + let start = self.cursor.col; + let end = start + count; + + let row = &mut self.grid[self.cursor.line]; + let template = self.empty_cell.clone(); + for c in &mut row[start..end] { + c.reset(&template); + } + } + + #[inline] + fn delete_chars(&mut self, count: Column) { + // Ensure deleting within terminal bounds + let count = ::std::cmp::min(count, self.size_info.cols()); + + let start = self.cursor.col; + let end = self.cursor.col + count; + let n = (self.size_info.cols() - end).0; + + let line = self.cursor.line; // borrowck + let line = &mut self.grid[line]; + + unsafe { + let src = line[end..].as_ptr(); + let dst = line[start..].as_mut_ptr(); + + ptr::copy(src, dst, n); + } + + // Clear last `count` cells in line. If deleting 1 char, need to delete 1 cell. + let template = self.empty_cell.clone(); + let end = self.size_info.cols() - count; + for c in &mut line[end..] { + c.reset(&template); + } + } + + #[inline] + fn move_backward_tabs(&mut self, count: i64) { + err_println!("[unimplemented] move_backward_tabs: {}", count); + } + + #[inline] + fn move_forward_tabs(&mut self, count: i64) { + err_println!("[unimplemented] move_forward_tabs: {}", count); + } + + #[inline] + fn save_cursor_position(&mut self) { + err_println!("[unimplemented] save_cursor_position"); + } + + #[inline] + fn restore_cursor_position(&mut self) { + err_println!("[unimplemented] restore_cursor_position"); + } + + #[inline] + fn clear_line(&mut self, mode: ansi::LineClearMode) { + debug_println!("clear_line: {:?}", mode); + let template = self.empty_cell.clone(); + match mode { + ansi::LineClearMode::Right => { + let row = &mut self.grid[self.cursor.line]; + for cell in &mut row[self.cursor.col..] { + cell.reset(&template); + } + }, + ansi::LineClearMode::Left => { + let row = &mut self.grid[self.cursor.line]; + for cell in &mut row[..(self.cursor.col + 1)] { + cell.reset(&template); + } + }, + ansi::LineClearMode::All => { + let row = &mut self.grid[self.cursor.line]; + for cell in &mut row[..] { + cell.reset(&template); + } + }, + } + } + + #[inline] + fn clear_screen(&mut self, mode: ansi::ClearMode) { + debug_println!("clear_screen: {:?}", mode); + let template = self.empty_cell.clone(); + match mode { + ansi::ClearMode::Below => { + for row in &mut self.grid[self.cursor.line..] { + for cell in row { + cell.reset(&template); + } + } + }, + ansi::ClearMode::All => { + self.grid.clear(|c| c.reset(&template)); + }, + _ => { + panic!("ansi::ClearMode::Above not implemented"); + } + } + } + + #[inline] + fn clear_tabs(&mut self, mode: ansi::TabulationClearMode) { + err_println!("[unimplemented] clear_tabs: {:?}", mode); + } + + #[inline] + fn reset_state(&mut self) { + err_println!("[unimplemented] reset_state"); + } + + #[inline] + fn reverse_index(&mut self) { + debug_println!("reverse_index"); + // if cursor is at the top + if self.cursor.line == self.scroll_region.start { + self.scroll_down(Line(1)); + } else { + self.cursor.line -= 1; + } + } + + /// set a terminal attribute + #[inline] + fn terminal_attribute(&mut self, attr: Attr) { + debug_println!("Set Attribute: {:?}", attr); + match attr { + Attr::Foreground(named_color) => { + self.template_cell.fg = cell::Color::Ansi(named_color); + }, + Attr::Background(named_color) => { + self.template_cell.bg = cell::Color::Ansi(named_color); + }, + Attr::ForegroundSpec(rgb) => { + self.template_cell.fg = cell::Color::Rgb(rgb); + }, + Attr::BackgroundSpec(rgb) => { + self.template_cell.bg = cell::Color::Rgb(rgb); + }, + Attr::Reset => { + self.template_cell.fg = cell::Color::Ansi(Color::Foreground); + self.template_cell.bg = cell::Color::Ansi(Color::Background); + self.template_cell.flags = cell::Flags::empty(); + }, + Attr::Reverse => self.template_cell.flags.insert(cell::INVERSE), + Attr::CancelReverse => self.template_cell.flags.remove(cell::INVERSE), + Attr::Bold => self.template_cell.flags.insert(cell::BOLD), + Attr::CancelBoldDim => self.template_cell.flags.remove(cell::BOLD), + Attr::Italic => self.template_cell.flags.insert(cell::ITALIC), + Attr::CancelItalic => self.template_cell.flags.remove(cell::ITALIC), + Attr::Underscore => self.template_cell.flags.insert(cell::UNDERLINE), + Attr::CancelUnderline => self.template_cell.flags.remove(cell::UNDERLINE), + _ => { + debug_println!("Term got unhandled attr: {:?}", attr); + } + } + } + + #[inline] + fn set_mode(&mut self, mode: ansi::Mode) { + debug_println!("set_mode: {:?}", mode); + match mode { + ansi::Mode::SwapScreenAndSetRestoreCursor => self.swap_alt(), + ansi::Mode::ShowCursor => self.mode.insert(mode::SHOW_CURSOR), + ansi::Mode::CursorKeys => self.mode.insert(mode::APP_CURSOR), + ansi::Mode::ReportMouseClicks => self.mode.insert(mode::MOUSE_REPORT_CLICK), + _ => { + debug_println!(".. ignoring set_mode"); + } + } + } + + #[inline] + fn unset_mode(&mut self,mode: ansi::Mode) { + debug_println!("unset_mode: {:?}", mode); + match mode { + ansi::Mode::SwapScreenAndSetRestoreCursor => self.swap_alt(), + ansi::Mode::ShowCursor => self.mode.remove(mode::SHOW_CURSOR), + ansi::Mode::CursorKeys => self.mode.remove(mode::APP_CURSOR), + ansi::Mode::ReportMouseClicks => self.mode.remove(mode::MOUSE_REPORT_CLICK), + _ => { + debug_println!(".. ignoring unset_mode"); + } + } + } + + #[inline] + fn set_scrolling_region(&mut self, region: Range<Line>) { + debug_println!("set scroll region: {:?}", region); + self.scroll_region = region; + self.goto(Line(0), Column(0)); + } + + #[inline] + fn set_keypad_application_mode(&mut self) { + debug_println!("set mode::APP_KEYPAD"); + self.mode.insert(mode::APP_KEYPAD); + } + + #[inline] + fn unset_keypad_application_mode(&mut self) { + debug_println!("unset mode::APP_KEYPAD"); + self.mode.remove(mode::APP_KEYPAD); + } +} + +#[cfg(test)] +mod tests { + extern crate serde_json; + extern crate test; + + use super::limit; + + use ansi::{Color}; + use grid::Grid; + use index::{Line, Column}; + use term::{cell, Cell}; + + /// Check that the grid can be serialized back and forth losslessly + /// + /// This test is in the term module as opposed to the grid since we want to + /// test this property with a T=Cell. + #[test] + fn grid_serde() { + let template = Cell::new( + ' ', + cell::Color::Ansi(Color::Foreground), + cell::Color::Ansi(Color::Background) + ); + + let grid = Grid::new(Line(24), Column(80), &template); + let serialized = serde_json::to_string(&grid).expect("ser"); + let deserialized = serde_json::from_str::<Grid<Cell>>(&serialized) + .expect("de"); + + assert_eq!(deserialized, grid); + } + + #[test] + fn limit_works() { + assert_eq!(limit(5, 1, 10), 5); + assert_eq!(limit(5, 6, 10), 6); + assert_eq!(limit(5, 1, 4), 4); + } +} + +#[cfg(test)] +mod bench { + extern crate test; + extern crate serde_json as json; + + use std::io::Read; + use std::fs::File; + use std::mem; + use std::path::Path; + + use grid::Grid; + + use super::{SizeInfo, Term}; + use super::cell::Cell; + + fn read_string<P>(path: P) -> String + where P: AsRef<Path> + { + let mut res = String::new(); + File::open(path.as_ref()).unwrap() + .read_to_string(&mut res).unwrap(); + + res + } + + /// Benchmark for the renderable cells iterator + /// + /// The renderable cells iterator yields cells that require work to be displayed (that is, not a + /// an empty background cell). This benchmark measures how long it takes to process the whole + /// iterator. + /// + /// When this benchmark was first added, it averaged ~78usec on my macbook pro. The total + /// render time for this grid is anywhere between ~1500 and ~2000usec (measured imprecisely with + /// the visual meter). + #[bench] + fn render_iter(b: &mut test::Bencher) { + // Need some realistic grid state; using one of the ref files. + let serialized_grid = read_string( + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/ref/vim_large_window_scroll/grid.json") + ); + let serialized_size = read_string( + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/ref/vim_large_window_scroll/size.json") + ); + + let mut grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); + let size: SizeInfo = json::from_str(&serialized_size).unwrap(); + + let mut terminal = Term::new(size); + mem::swap(&mut terminal.grid, &mut grid); + + b.iter(|| { + let iter = terminal.renderable_cells(); + for cell in iter { + test::black_box(cell); + } + }) + } +} |