diff options
Diffstat (limited to 'src/term/mod.rs')
-rw-r--r-- | src/term/mod.rs | 484 |
1 files changed, 283 insertions, 201 deletions
diff --git a/src/term/mod.rs b/src/term/mod.rs index fd22fe54..04d110af 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,9 +24,9 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed}; -use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive}; -use selection::{self, Span, Selection}; +use grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter, Scroll}; +use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear}; +use selection::{self, Selection, Locations}; use config::{Config, VisualBellAnimation}; use {MouseCursor, Rgb}; use copypasta::{Clipboard, Load, Store}; @@ -38,7 +38,10 @@ pub use self::cell::Cell; use self::cell::LineLength; impl selection::SemanticSearch for Term { - fn semantic_search_left(&self, mut point: Point) -> Point { + fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> { + // Limit the starting point to the last line in the history + point.line = min(point.line, self.grid.len() - 1); + let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -57,7 +60,10 @@ impl selection::SemanticSearch for Term { point } - fn semantic_search_right(&self, mut point: Point) -> Point { + fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> { + // Limit the starting point to the last line in the history + point.line = min(point.line, self.grid.len() - 1); + let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -95,12 +101,11 @@ impl selection::Dimensions for Term { /// 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> { + inner: DisplayIter<'a, Cell>, grid: &'a Grid<Cell>, cursor: &'a Point, - cursor_index: index::Linear, + cursor_offset: usize, mode: TermMode, - line: Line, - column: Column, config: &'a Config, colors: &'a color::List, selection: Option<RangeInclusive<index::Linear>>, @@ -118,52 +123,92 @@ impl<'a> RenderableCellsIter<'a> { colors: &'b color::List, mode: TermMode, config: &'b Config, - selection: Option<RangeInclusive<index::Linear>>, + selection: Option<Locations>, cursor_style: CursorStyle, ) -> RenderableCellsIter<'b> { - let cursor_index = Linear(cursor.line.0 * grid.num_cols().0 + cursor.col.0); + let cursor_offset = grid.line_to_offset(cursor.line); + let inner = grid.display_iter(); + + let mut selection_range = None; + if let Some(loc) = selection { + // Get on-screen lines of the selection's locations + let start_line = grid.buffer_line_to_visible(loc.start.line); + let end_line = grid.buffer_line_to_visible(loc.end.line); + + // Get start/end locations based on what part of selection is on screen + let locations = match (start_line, end_line) { + (Some(start_line), Some(end_line)) => { + Some((start_line, loc.start.col, end_line, loc.end.col)) + }, + (Some(start_line), None) => { + Some((start_line, loc.start.col, Line(0), Column(0))) + }, + (None, Some(end_line)) => { + Some((grid.num_lines(), Column(0), end_line, loc.end.col)) + }, + (None, None) => None, + }; + + if let Some((start_line, start_col, end_line, end_col)) = locations { + // start and end *lines* are swapped as we switch from buffer to + // Line coordinates. + let mut end = Point { + line: start_line, + col: start_col, + }; + let mut start = Point { + line: end_line, + col: end_col, + }; + + if start > end { + ::std::mem::swap(&mut start, &mut end); + } + + let cols = grid.num_cols(); + let start = Linear(start.line.0 * cols.0 + start.col.0); + let end = Linear(end.line.0 * cols.0 + end.col.0); + + // Update the selection + selection_range = Some(RangeInclusive::new(start, end)); + } + } RenderableCellsIter { - grid, cursor, - cursor_index, + cursor_offset, + grid, + inner, mode, - line: Line(0), - column: Column(0), - selection, + selection: selection_range, config, colors, cursor_cells: ArrayDeque::new(), }.initialize(cursor_style) } - fn push_cursor_cells( - &mut self, - original_cell: Cell, - cursor_cell: Cell, - wide_cell: Cell, - ) { + fn push_cursor_cells(&mut self, original: Cell, cursor: Cell, wide: Cell) { // Prints the char under the cell if cursor is situated on a non-empty cell self.cursor_cells.push_back(Indexed { line: self.cursor.line, column: self.cursor.col, - inner: original_cell, + inner: original, }).expect("won't exceed capacity"); // Prints the cursor self.cursor_cells.push_back(Indexed { line: self.cursor.line, column: self.cursor.col, - inner: cursor_cell, + inner: cursor, }).expect("won't exceed capacity"); // If cursor is over a wide (2 cell size) character, // print the second cursor cell - if self.is_wide_cursor(&cursor_cell) { + if self.is_wide_cursor(&cursor) { self.cursor_cells.push_back(Indexed { line: self.cursor.line, column: self.cursor.col + 1, - inner: wide_cell, + inner: wide, }).expect("won't exceed capacity"); } } @@ -326,6 +371,7 @@ impl<'a> RenderableCellsIter<'a> { } pub struct RenderableCell { + /// A _Display_ line (not necessarily an _Active_ line) pub line: Line, pub column: Column, pub c: char, @@ -344,81 +390,71 @@ impl<'a> Iterator for RenderableCellsIter<'a> { /// (eg. invert fg and bg colors). #[inline] 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 mut column = self.column; - let cell = &self.grid[line][column]; - - let index = Linear(line.0 * self.grid.num_cols().0 + column.0); - - let (cell, selected) = if index == self.cursor_index { - // Cursor cell - let cell = self.cursor_cells.pop_front().unwrap(); - column = cell.column; - - // Since there may be multiple cursor cells (for a wide - // char), only update iteration position after all cursor - // cells have been drawn. - if self.cursor_cells.is_empty() { - self.line = cell.line; - self.column = cell.column + 1; - } - (cell.inner, false) - } else { - // Normal cell - self.column += 1; + loop { + // Handle cursor + let (cell, selected) = if self.cursor_offset == self.inner.offset() && + self.inner.column() == self.cursor.col + { + // Cursor cell + let mut cell = self.cursor_cells.pop_front().unwrap(); + cell.line = self.inner.line(); + + // Since there may be multiple cursor cells (for a wide + // char), only update iteration position after all cursor + // cells have been drawn. + if self.cursor_cells.is_empty() { + self.inner.next(); + } + (cell, false) + } else { + let cell = self.inner.next()?; - let selected = self.selection.as_ref() - .map(|range| range.contains_(index)) - .unwrap_or(false); + let index = Linear(cell.line.0 * self.grid.num_cols().0 + cell.column.0); - // Skip empty cells - if cell.is_empty() && !selected { - continue; - } - (*cell, selected) - }; + let selected = self.selection.as_ref() + .map(|range| range.contains_(index)) + .unwrap_or(false); - // Apply inversion and lookup RGB values - let mut bg_alpha = 1.0; - let fg_rgb; - let bg_rgb; - - let invert = selected ^ cell.inverse(); - - if invert { - if cell.fg == cell.bg { - bg_rgb = self.colors[NamedColor::Foreground]; - fg_rgb = self.colors[NamedColor::Background]; - bg_alpha = 1.0 - } else { - bg_rgb = self.compute_fg_rgb(cell.fg, &cell); - fg_rgb = self.compute_bg_rgb(cell.bg); - } - } else { - fg_rgb = self.compute_fg_rgb(cell.fg, &cell); - bg_rgb = self.compute_bg_rgb(cell.bg); - bg_alpha = self.compute_bg_alpha(cell.bg); + // Skip empty cells + if cell.is_empty() && !selected { + continue; } - return Some(RenderableCell { - line, - column, - flags: cell.flags, - c: cell.c, - fg: fg_rgb, - bg: bg_rgb, - bg_alpha, - }) + (cell, selected) + }; + + // Apply inversion and lookup RGB values + let mut bg_alpha = 1.0; + let fg_rgb; + let bg_rgb; + + let invert = selected ^ cell.inverse(); + + if invert { + if cell.fg == cell.bg { + bg_rgb = self.colors[NamedColor::Foreground]; + fg_rgb = self.colors[NamedColor::Background]; + bg_alpha = 1.0 + } else { + bg_rgb = self.compute_fg_rgb(cell.fg, &cell); + fg_rgb = self.compute_bg_rgb(cell.bg); + } + } else { + fg_rgb = self.compute_fg_rgb(cell.fg, &cell); + bg_rgb = self.compute_bg_rgb(cell.bg); + bg_alpha = self.compute_bg_alpha(cell.bg); } - self.column = Column(0); - self.line += 1; + return Some(RenderableCell { + line: cell.line, + column: cell.column, + flags: cell.flags, + c: cell.c, + fg: fg_rgb, + bg: bg_rgb, + bg_alpha, + }) } - - None } } @@ -733,6 +769,9 @@ pub struct Term { /// Number of spaces in one tab tabspaces: usize, + + /// Automatically scroll to bottom when new lines are added + auto_scroll: bool, } /// Terminal size info @@ -768,54 +807,62 @@ impl SizeInfo { Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) } - fn contains_point(&self, x: usize, y:usize) -> bool { + pub fn contains_point(&self, x: usize, y:usize) -> bool { x <= (self.width - self.padding_x) as usize && x >= self.padding_x as usize && y <= (self.height - self.padding_y) as usize && y >= self.padding_y as usize } - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> { - if !self.contains_point(x, y) { - return None; - } - - let col = Column((x - self.padding_x as usize) / (self.cell_width as usize)); - let line = Line((y - self.padding_y as usize) / (self.cell_height as usize)); + pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { + let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); + let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); - Some(Point { + Point { line: min(line, self.lines() - 1), col: min(col, self.cols() - 1) - }) + } } } impl Term { + pub fn selection(&self) -> &Option<Selection> { + &self.grid.selection + } + + pub fn selection_mut(&mut self) -> &mut Option<Selection> { + &mut self.grid.selection + } + #[inline] pub fn get_next_title(&mut self) -> Option<String> { self.next_title.take() } + pub fn scroll_display(&mut self, scroll: Scroll) { + self.grid.scroll_display(scroll); + self.dirty = true; + } + #[inline] pub fn get_next_mouse_cursor(&mut self) -> Option<MouseCursor> { self.next_mouse_cursor.take() } pub fn new(config: &Config, size: SizeInfo) -> Term { - let template = Cell::default(); - let num_cols = size.cols(); let num_lines = size.lines(); - let grid = Grid::new(num_lines, num_cols, &template); + let history_size = config.scrolling().history as usize; + let grid = Grid::new(num_lines, num_cols, history_size, Cell::default()); + let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default()); let tabspaces = config.tabspaces(); let tabs = IndexRange::from(Column(0)..grid.num_cols()) .map(|i| (*i as usize) % tabspaces == 0) .collect::<Vec<bool>>(); - let alt = grid.clone(); let scroll_region = Line(0)..grid.num_lines(); Term { @@ -846,6 +893,7 @@ impl Term { default_cursor_style: config.cursor_style(), dynamic_title: config.dynamic_title(), tabspaces, + auto_scroll: config.scrolling().auto_scroll, } } @@ -872,6 +920,9 @@ impl Term { self.visual_bell.update_config(config); self.default_cursor_style = config.cursor_style(); self.dynamic_title = config.dynamic_title(); + self.auto_scroll = config.scrolling().auto_scroll; + self.grid + .update_history(config.scrolling().history as usize, &self.cursor.template); } #[inline] @@ -879,11 +930,11 @@ impl Term { self.dirty } - pub fn string_from_selection(&self, span: &Span) -> String { + pub fn selection_to_string(&self) -> Option<String> { /// Need a generic push() for the Append trait trait PushChar { fn push_char(&mut self, c: char); - fn maybe_newline(&mut self, grid: &Grid<Cell>, line: Line, ending: Column) { + fn maybe_newline(&mut self, grid: &Grid<Cell>, line: usize, ending: Column) { if ending != Column(0) && !grid[line][ending - 1].flags.contains(cell::Flags::WRAPLINE) { self.push_char('\n'); } @@ -900,16 +951,19 @@ impl Term { use std::ops::Range; trait Append : PushChar { - fn append(&mut self, grid: &Grid<Cell>, line: Line, cols: Range<Column>) -> Option<Range<Column>>; + fn append(&mut self, grid: &Grid<Cell>, line: usize, cols: Range<Column>) -> Option<Range<Column>>; } impl Append for String { fn append( &mut self, grid: &Grid<Cell>, - line: Line, + mut line: usize, cols: Range<Column> ) -> Option<Range<Column>> { + // Select until last line still within the buffer + line = min(line, grid.len() - 1); + let grid_line = &grid[line]; let line_length = grid_line.line_length(); let line_end = min(line_length, cols.end + 1); @@ -935,43 +989,57 @@ impl Term { } } + let selection = self.grid.selection.clone()?; + let span = selection.to_span(self)?; + let mut res = String::new(); - let (start, end) = span.to_locations(); + let Locations { mut start, mut end } = span.to_locations(); + + if start > end { + ::std::mem::swap(&mut start, &mut end); + } + let line_count = end.line - start.line; let max_col = Column(usize::max_value() - 1); match line_count { // Selection within single line - Line(0) => { + 0 => { res.append(&self.grid, start.line, start.col..end.col); }, // Selection ends on line following start - Line(1) => { + 1 => { + // Ending line + res.append(&self.grid, end.line, end.col..max_col); + // Starting line - res.append(&self.grid, start.line, start.col..max_col); + res.append(&self.grid, start.line, Column(0)..start.col); - // Ending line - res.append(&self.grid, end.line, Column(0)..end.col); }, // Multi line selection _ => { - // Starting line - res.append(&self.grid, start.line, start.col..max_col); + // Ending line + res.append(&self.grid, end.line, end.col..max_col); - let middle_range = IndexRange::from((start.line + 1)..(end.line)); - for line in middle_range { + let middle_range = (start.line + 1)..(end.line); + for line in middle_range.rev() { res.append(&self.grid, line, Column(0)..max_col); } - // Ending line - res.append(&self.grid, end.line, Column(0)..end.col); + // Starting line + res.append(&self.grid, start.line, Column(0)..start.col); + } } - res + Some(res) + } + + pub(crate) fn visible_to_buffer(&self, point: Point) -> Point<usize> { + self.grid.visible_to_buffer(point) } /// Convert the given pixel values to a grid coordinate @@ -981,7 +1049,11 @@ impl Term { /// /// Returns None if the coordinates are outside the screen pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> { - self.size_info().pixels_to_coords(x, y) + if self.size_info.contains_point(x, y) { + Some(self.size_info.pixels_to_coords(x, y)) + } else { + None + } } /// Access to the raw grid data structure @@ -1000,11 +1072,14 @@ impl Term { pub fn renderable_cells<'b>( &'b self, config: &'b Config, - selection: Option<&'b Selection>, window_focused: bool, ) -> RenderableCellsIter { - let selection = selection.and_then(|s| s.to_span(self)) - .map(|span| span.to_range()); + let selection = self.grid.selection.as_ref() + .and_then(|s| s.to_span(self)) + .map(|span| { + span.to_locations() + }); + let cursor = if window_focused || !config.unfocused_hollow_cursor() { self.cursor_style.unwrap_or(self.default_cursor_style) } else { @@ -1045,6 +1120,9 @@ impl Term { return; } + self.grid.selection = None; + self.alt_grid.selection = None; + // Should not allow less than 1 col, causes all sorts of checks to be required. if num_cols <= Column(1) { num_cols = Column(2); @@ -1055,28 +1133,34 @@ impl Term { num_lines = Line(2); } - // 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.point.line >= num_lines { let lines = self.cursor.point.line - num_lines + 1; - self.grid.scroll_up(&(Line(0)..old_lines), lines); + self.grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor.template); } // Scroll up alt grid as well if self.cursor_save_alt.point.line >= num_lines { let lines = self.cursor_save_alt.point.line - num_lines + 1; - self.alt_grid.scroll_up(&(Line(0)..old_lines), lines); + self.alt_grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor_save_alt.template); + } + + // Move prompt down when growing if scrollback lines are available + if num_lines > old_lines { + if self.mode.contains(TermMode::ALT_SCREEN) { + let growage = min(num_lines - old_lines, Line(self.alt_grid.scroll_limit())); + self.cursor_save.point.line += growage; + } else { + let growage = min(num_lines - old_lines, Line(self.grid.scroll_limit())); + self.cursor.point.line += growage; + } } debug!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size - let template = Cell::default(); - self.grid.resize(num_lines, num_cols, &template); - self.alt_grid.resize(num_lines, num_cols, &template); + self.grid.resize(num_lines, num_cols, &Cell::default()); + self.alt_grid.resize(num_lines, num_cols, &Cell::default()); // Reset scrolling region to new size self.scroll_region = Line(0)..self.grid.num_lines(); @@ -1093,14 +1177,6 @@ impl Term { self.tabs = IndexRange::from(Column(0)..self.grid.num_cols()) .map(|i| (*i as usize) % self.tabspaces == 0) .collect::<Vec<bool>>(); - - if num_lines > old_lines { - // Make sure bottom of terminal is clear - let template = self.cursor.template; - self.grid.clear_region((self.cursor.point.line + 1).., |c| c.reset(&template)); - self.alt_grid.clear_region((self.cursor_save_alt.point.line + 1).., |c| c.reset(&template)); - } - } #[inline] @@ -1121,7 +1197,7 @@ impl Term { pub fn swap_alt(&mut self) { if self.alt { let template = &self.cursor.template; - self.grid.clear(|c| c.reset(template)); + self.grid.region_mut(..).each(|c| c.reset(template)); } self.alt = !self.alt; @@ -1133,21 +1209,13 @@ impl Term { /// Text moves down; clear at bottom /// Expects origin to be in scroll range. #[inline] - fn scroll_down_relative(&mut self, origin: Line, lines: Line) { + fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) { trace!("scroll_down_relative: origin={}, lines={}", origin, lines); - let lines = min(lines, self.scroll_region.end - self.scroll_region.start); - - // Copy of cell template; can't have it borrowed when calling clear/scroll - let template = self.cursor.template; - - // Clear `lines` lines at bottom of area - { - let start = max(origin, Line(self.scroll_region.end.0.saturating_sub(lines.0))); - self.grid.clear_region(start..self.scroll_region.end, |c| c.reset(&template)); - } + lines = min(lines, self.scroll_region.end - self.scroll_region.start); + lines = min(lines, self.scroll_region.end - origin); // Scroll between origin and bottom - self.grid.scroll_down(&(origin..self.scroll_region.end), lines); + self.grid.scroll_down(&(origin..self.scroll_region.end), lines, &self.cursor.template); } /// Scroll screen up @@ -1159,17 +1227,8 @@ impl Term { trace!("scroll_up_relative: origin={}, lines={}", origin, lines); let lines = min(lines, self.scroll_region.end - self.scroll_region.start); - // Copy of cell template; can't have it borrowed when calling clear/scroll - let template = self.cursor.template; - - // Clear `lines` lines starting from origin to origin + lines - { - let end = min(origin + lines, self.scroll_region.end); - self.grid.clear_region(origin..end, |c| c.reset(&template)); - } - // Scroll from origin to bottom less number of lines - self.grid.scroll_up(&(origin..self.scroll_region.end), lines); + self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &self.cursor.template); } fn deccolm(&mut self) { @@ -1180,7 +1239,7 @@ impl Term { // Clear grid let template = self.cursor.template; - self.grid.clear(|c| c.reset(&template)); + self.grid.region_mut(..).each(|c| c.reset(&template)); } #[inline] @@ -1219,6 +1278,11 @@ impl ansi::Handler for Term { /// A character to be displayed #[inline] fn input(&mut self, c: char) { + // If enabled, scroll to bottom when character is received + if self.auto_scroll { + self.scroll_display(Scroll::Bottom); + } + if self.input_needs_wrap { if !self.mode.contains(mode::TermMode::LINE_WRAP) { return; @@ -1301,11 +1365,8 @@ impl ansi::Handler for Term { let mut template = self.cursor.template; template.c = 'E'; - for row in &mut self.grid.lines_mut() { - for cell in row { - cell.reset(&template); - } - } + self.grid.region_mut(..) + .each(|c| c.reset(&template)); } #[inline] @@ -1716,25 +1777,19 @@ impl ansi::Handler for Term { cell.reset(&template); } if self.cursor.point.line < self.grid.num_lines() - 1 { - for row in &mut self.grid[(self.cursor.point.line + 1)..] { - for cell in row { - cell.reset(&template); - } - } + self.grid.region_mut((self.cursor.point.line + 1)..) + .each(|cell| cell.reset(&template)); } }, ansi::ClearMode::All => { - self.grid.clear(|c| c.reset(&template)); + self.grid.region_mut(..).each(|c| c.reset(&template)); }, ansi::ClearMode::Above => { // If clearing more than one line if self.cursor.point.line > Line(1) { // Fully clear all lines before the current line - for row in &mut self.grid[..self.cursor.point.line] { - for cell in row { - cell.reset(&template); - } - } + self.grid.region_mut(..self.cursor.point.line) + .each(|cell| cell.reset(&template)); } // Clear up to the current column in the current line let end = min(self.cursor.point.col + 1, self.grid.num_cols()); @@ -1743,7 +1798,9 @@ impl ansi::Handler for Term { } }, // If scrollback is implemented, this should clear it - ansi::ClearMode::Saved => return + ansi::ClearMode::Saved => { + self.grid.clear_history(); + } } } @@ -1783,6 +1840,8 @@ impl ansi::Handler for Term { self.colors = self.original_colors; self.color_modified = [false; color::COUNT]; self.cursor_style = None; + self.grid.clear_history(); + self.grid.region_mut(..).each(|c| c.reset(&Cell::default())); } #[inline] @@ -1950,9 +2009,9 @@ mod tests { use super::{Cell, Term, SizeInfo}; use term::cell; - use grid::Grid; + use grid::{Grid, Scroll}; use index::{Point, Line, Column}; - use ansi::{Handler, CharsetIndex, StandardCharset}; + use ansi::{self, Handler, CharsetIndex, StandardCharset}; use selection::Selection; use std::mem; use input::FONT_SIZE_STEP; @@ -1970,7 +2029,7 @@ mod tests { padding_y: 0.0, }; let mut term = Term::new(&Default::default(), size); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), &Cell::default()); + let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0, Cell::default()); for i in 0..5 { for j in 0..2 { grid[Line(j)][Column(i)].c = 'a'; @@ -1987,18 +2046,18 @@ mod tests { mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); { - let selection = Selection::semantic(Point { line: Line(0), col: Column(1) }, &term); - assert_eq!(term.string_from_selection(&selection.to_span(&term).unwrap()), "aa"); + *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(1) })); + assert_eq!(term.selection_to_string(), Some(String::from("aa"))); } { - let selection = Selection::semantic(Point { line: Line(0), col: Column(4) }, &term); - assert_eq!(term.string_from_selection(&selection.to_span(&term).unwrap()), "aaa"); + *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(4) })); + assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } { - let selection = Selection::semantic(Point { line: Line(1), col: Column(1) }, &term); - assert_eq!(term.string_from_selection(&selection.to_span(&term).unwrap()), "aaa"); + *term.selection_mut() = Some(Selection::semantic(Point { line: 1, col: Column(1) })); + assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } } @@ -2013,7 +2072,7 @@ mod tests { padding_y: 0.0, }; let mut term = Term::new(&Default::default(), size); - let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), &Cell::default()); + let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0, Cell::default()); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; } @@ -2023,10 +2082,8 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - let selection = Selection::lines(Point { line: Line(0), col: Column(3) }); - if let Some(span) = selection.to_span(&term) { - assert_eq!(term.string_from_selection(&span), "\"aa\"a\n"); - } + *term.selection_mut() = Some(Selection::lines(Point { line: 0, col: Column(3) })); + assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n"))); } /// Check that the grid can be serialized back and forth losslessly @@ -2037,7 +2094,7 @@ mod tests { fn grid_serde() { let template = Cell::default(); - let grid = Grid::new(Line(24), Column(80), &template); + let grid: Grid<Cell> = Grid::new(Line(24), Column(80), 0, template); let serialized = serde_json::to_string(&grid).expect("ser"); let deserialized = serde_json::from_str::<Grid<Cell>>(&serialized) .expect("de"); @@ -2129,6 +2186,31 @@ mod tests { let expected_font_size: Size = config.font().size(); assert_eq!(term.font_size, expected_font_size); } + + #[test] + fn clear_saved_lines() { + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + padding_x: 0.0, + padding_y: 0.0, + }; + let config: Config = Default::default(); + let mut term: Term = Term::new(&config, size); + + // Add one line of scrollback + term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); + + // Clear the history + term.clear_screen(ansi::ClearMode::Saved); + + // Make sure that scrolling does not change the grid + let mut scrolled_grid = term.grid.clone(); + scrolled_grid.scroll_display(Scroll::Top); + assert_eq!(term.grid, scrolled_grid); + } } #[cfg(all(test, feature = "bench"))] @@ -2185,7 +2267,7 @@ mod benches { mem::swap(&mut terminal.grid, &mut grid); b.iter(|| { - let iter = terminal.renderable_cells(&config, None, false); + let iter = terminal.renderable_cells(&config, false); for cell in iter { test::black_box(cell); } |