From 1dacc99183373bffa3ba287aa3241f3b1da67016 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 30 May 2020 20:45:44 +0000 Subject: [PATCH] Refactor Term/Grid separation This commit aims to clear up the separation between Term and Grid to make way for implementing search. The `cursor` and `cursor_save` have been moved to the grid, since they're always bound to their specific grid and this makes updating easier. Since the selection is independent of the active grid, it has been moved to the `Term`. --- CHANGELOG.md | 2 + alacritty/src/display.rs | 4 +- alacritty/src/event.rs | 14 +- alacritty/src/input.rs | 2 +- alacritty/src/url.rs | 14 +- alacritty/src/window.rs | 2 +- alacritty_terminal/src/ansi.rs | 45 +++ alacritty_terminal/src/grid/mod.rs | 377 ++++-------------- alacritty_terminal/src/grid/resize.rs | 274 +++++++++++++ alacritty_terminal/src/grid/row.rs | 12 +- alacritty_terminal/src/grid/storage.rs | 225 +++++------ alacritty_terminal/src/grid/tests.rs | 22 +- alacritty_terminal/src/index.rs | 135 ++++++- alacritty_terminal/src/selection.rs | 100 +++-- alacritty_terminal/src/term/cell.rs | 4 +- alacritty_terminal/src/term/mod.rs | 509 ++++++++++--------------- alacritty_terminal/src/vi_mode.rs | 9 +- alacritty_terminal/tests/ref.rs | 2 +- 18 files changed, 932 insertions(+), 820 deletions(-) create mode 100644 alacritty_terminal/src/grid/resize.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9dbd1..f9e59e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Crash when writing a fullwidth character in the last column with auto-wrap mode disabled - Paste from some apps on Wayland - Slow startup with Nvidia binary drivers on some X11 systems +- Display not scrolling when printing new lines while scrolled in history + ## 0.4.2 diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 13e0454..5e0747f 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -414,7 +414,7 @@ impl Display { let glyph_cache = &mut self.glyph_cache; let size_info = self.size_info; - let selection = !terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true); + let selection = !terminal.selection.as_ref().map(Selection::is_empty).unwrap_or(true); let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE) && !terminal.mode().contains(TermMode::VI); @@ -446,7 +446,7 @@ impl Display { // Iterate over all non-empty cells in the grid. for cell in grid_cells { // Update URL underlines. - urls.update(size_info.cols().0, cell); + urls.update(size_info.cols(), cell); // Update underline/strikeout. lines.update(cell); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index e11903e..0de130b 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -95,7 +95,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon // Update selection. if self.terminal.mode().contains(TermMode::VI) - && self.terminal.selection().as_ref().map(|s| s.is_empty()) != Some(true) + && self.terminal.selection.as_ref().map(|s| s.is_empty()) != Some(true) { self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right); } else if ElementState::Pressed == self.mouse().left_button_state { @@ -116,11 +116,11 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } fn selection_is_empty(&self) -> bool { - self.terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true) + self.terminal.selection.as_ref().map(Selection::is_empty).unwrap_or(true) } fn clear_selection(&mut self) { - *self.terminal.selection_mut() = None; + self.terminal.selection = None; self.terminal.dirty = true; } @@ -129,7 +129,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon // Update selection if one exists. let vi_mode = self.terminal.mode().contains(TermMode::VI); - if let Some(selection) = self.terminal.selection_mut() { + if let Some(selection) = &mut self.terminal.selection { selection.update(point, side); if vi_mode { @@ -142,12 +142,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) { let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::new(ty, point, side)); + self.terminal.selection = Some(Selection::new(ty, point, side)); self.terminal.dirty = true; } fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side) { - match self.terminal.selection_mut() { + match &mut self.terminal.selection { Some(selection) if selection.ty == ty && !selection.is_empty() => { self.clear_selection(); }, @@ -745,7 +745,7 @@ impl Processor { // Dump grid state. let mut grid = terminal.grid().clone(); - grid.initialize_all(&Cell::default()); + grid.initialize_all(Cell::default()); grid.truncate(); let serialized_grid = json::to_string(&grid).expect("serialize grid"); diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 6f1a71a..01c43ec 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -117,7 +117,7 @@ impl Action { ctx.toggle_selection(ty, cursor_point, Side::Left); // Make sure initial selection is not empty. - if let Some(selection) = ctx.terminal_mut().selection_mut() { + if let Some(selection) = &mut ctx.terminal_mut().selection { selection.include_all(); } } diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index 72452a9..f7c7105 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -6,7 +6,7 @@ use urlocator::{UrlLocation, UrlLocator}; use font::Metrics; -use alacritty_terminal::index::Point; +use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{RenderableCell, RenderableCellContent, SizeInfo}; @@ -19,7 +19,7 @@ use crate::renderer::rects::{RenderLine, RenderRect}; pub struct Url { lines: Vec, end_offset: u16, - num_cols: usize, + num_cols: Column, } impl Url { @@ -71,8 +71,8 @@ impl Urls { Self::default() } - /// Update tracked URLs. - pub fn update(&mut self, num_cols: usize, cell: RenderableCell) { + // Update tracked URLs. + pub fn update(&mut self, num_cols: Column, cell: RenderableCell) { // Convert cell to character. let c = match cell.inner { RenderableCellContent::Chars(chars) => chars[0], @@ -127,7 +127,7 @@ impl Urls { } // Reset at un-wrapped linebreak. - if cell.column.0 + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { + if cell.column + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { self.reset(); } } @@ -224,7 +224,7 @@ mod tests { let mut urls = Urls::new(); for cell in input { - urls.update(num_cols, cell); + urls.update(Column(num_cols), cell); } let url = urls.urls.first().unwrap(); @@ -240,7 +240,7 @@ mod tests { let mut urls = Urls::new(); for cell in input { - urls.update(num_cols, cell); + urls.update(Column(num_cols), cell); } assert_eq!(urls.urls.len(), 3); diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 155b2aa..100993b 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -420,7 +420,7 @@ impl Window { /// Adjust the IME editor position according to the new location of the cursor. #[cfg(not(windows))] pub fn update_ime_position(&mut self, terminal: &Term, size_info: &SizeInfo) { - let point = terminal.cursor().point; + let point = terminal.grid().cursor.point; let SizeInfo { cell_width, cell_height, padding_x, padding_y, .. } = size_info; let nspot_x = f64::from(padding_x + point.col.0 as f32 * cell_width); diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 01f4835..0b196ad 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -697,6 +697,51 @@ impl Default for StandardCharset { } } +impl StandardCharset { + /// Switch/Map character to the active charset. Ascii is the common case and + /// for that we want to do as little as possible. + #[inline] + pub fn map(self, c: char) -> char { + match self { + StandardCharset::Ascii => c, + StandardCharset::SpecialCharacterAndLineDrawing => match c { + '`' => '◆', + 'a' => '▒', + 'b' => '\t', + 'c' => '\u{000c}', + 'd' => '\r', + 'e' => '\n', + 'f' => '°', + 'g' => '±', + 'h' => '\u{2424}', + 'i' => '\u{000b}', + 'j' => '┘', + 'k' => '┐', + 'l' => '┌', + 'm' => '└', + 'n' => '┼', + 'o' => '⎺', + 'p' => '⎻', + 'q' => '─', + 'r' => '⎼', + 's' => '⎽', + 't' => '├', + 'u' => '┤', + 'v' => '┴', + 'w' => '┬', + 'x' => '│', + 'y' => '≤', + 'z' => '≥', + '{' => 'π', + '|' => '≠', + '}' => '£', + '~' => '·', + _ => c, + }, + } + } +} + impl<'a, H, W> vte::Perform for Performer<'a, H, W> where H: Handler + TermInfo + 'a, diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 71545dc..a136b1b 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -14,22 +14,22 @@ //! A specialized 2D grid implementation optimized for use in a terminal. -use std::cmp::{max, min, Ordering}; +use std::cmp::{max, min}; use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo}; use serde::{Deserialize, Serialize}; +use crate::ansi::{CharsetIndex, StandardCharset}; use crate::index::{Column, IndexRange, Line, Point}; -use crate::selection::Selection; -use crate::term::cell::Flags; +use crate::term::cell::{Cell, Flags}; +pub mod resize; mod row; -pub use self::row::Row; - +mod storage; #[cfg(test)] mod tests; -mod storage; +pub use self::row::Row; use self::storage::Storage; /// Bidirectional iterator. @@ -60,7 +60,6 @@ impl ::std::cmp::PartialEq for Grid { && self.cols.eq(&other.cols) && self.lines.eq(&other.lines) && self.display_offset.eq(&other.display_offset) - && self.selection.eq(&other.selection) } } @@ -76,7 +75,36 @@ pub trait GridCell { fn fast_eq(&self, other: Self) -> bool; } -/// Represents the terminal display contents. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct Cursor { + /// The location of this cursor. + pub point: Point, + + /// Template cell when using this cursor. + pub template: Cell, + + /// Currently configured graphic character sets. + pub charsets: Charsets, +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct Charsets([StandardCharset; 4]); + +impl Index for Charsets { + type Output = StandardCharset; + + fn index(&self, index: CharsetIndex) -> &StandardCharset { + &self.0[index as usize] + } +} + +impl IndexMut for Charsets { + fn index_mut(&mut self, index: CharsetIndex) -> &mut StandardCharset { + &mut self.0[index as usize] + } +} + +/// Grid based terminal content storage. /// /// ```notrust /// ┌─────────────────────────┐ <-- max_scroll_limit + lines @@ -105,6 +133,14 @@ pub trait GridCell { /// ``` #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Grid { + /// Current cursor for writing data. + #[serde(skip)] + pub cursor: Cursor, + + /// Last saved cursor. + #[serde(skip)] + pub saved_cursor: Cursor, + /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. raw: Storage, @@ -122,10 +158,6 @@ pub struct Grid { /// updates this offset accordingly. display_offset: usize, - /// Selected region. - #[serde(skip)] - pub selection: Option, - /// Maximum number of lines in history. max_scroll_limit: usize, } @@ -139,10 +171,17 @@ pub enum Scroll { Bottom, } -impl Grid { - pub fn new(lines: Line, cols: Column, scrollback: usize, template: T) -> Grid { - let raw = Storage::with_capacity(lines, Row::new(cols, &template)); - Grid { raw, cols, lines, display_offset: 0, selection: None, max_scroll_limit: scrollback } +impl Grid { + pub fn new(lines: Line, cols: Column, max_scroll_limit: usize, template: T) -> Grid { + Grid { + raw: Storage::with_capacity(lines, Row::new(cols, template)), + max_scroll_limit, + display_offset: 0, + saved_cursor: Cursor::default(), + cursor: Cursor::default(), + lines, + cols, + } } /// Clamp a buffer point to the visible region. @@ -191,33 +230,7 @@ impl Grid { } } - pub fn resize( - &mut self, - reflow: bool, - lines: Line, - cols: Column, - cursor_pos: &mut Point, - template: &T, - ) { - // Check that there's actually work to do and return early if not. - if lines == self.lines && cols == self.cols { - return; - } - - match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines, cursor_pos, template), - Ordering::Greater => self.shrink_lines(lines, cursor_pos, template), - Ordering::Equal => (), - } - - match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(reflow, cols, cursor_pos, template), - Ordering::Greater => self.shrink_cols(reflow, cols, template), - Ordering::Equal => (), - } - } - - fn increase_scroll_limit(&mut self, count: usize, template: &T) { + fn increase_scroll_limit(&mut self, count: usize, template: T) { let count = min(count, self.max_scroll_limit - self.history_size()); if count != 0 { self.raw.initialize(count, template, self.cols); @@ -228,238 +241,12 @@ impl Grid { let count = min(count, self.history_size()); if count != 0 { self.raw.shrink_lines(min(count, self.history_size())); + self.display_offset = min(self.display_offset, self.history_size()); } } - /// Add lines to the visible area. - /// - /// Alacritty keeps the cursor at the bottom of the terminal as long as there - /// is scrollback available. Once scrollback is exhausted, new lines are - /// simply added to the bottom of the screen. - fn grow_lines(&mut self, new_line_count: Line, cursor_pos: &mut Point, template: &T) { - let lines_added = new_line_count - self.lines; - - // Need to "resize" before updating buffer. - self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); - self.lines = new_line_count; - - let history_size = self.history_size(); - let from_history = min(history_size, lines_added.0); - - // Move cursor down for all lines pulled from history. - cursor_pos.line += from_history; - - if from_history != lines_added.0 { - // Move existing lines up for every line that couldn't be pulled from history. - self.scroll_up(&(Line(0)..new_line_count), lines_added - from_history, template); - } - - self.decrease_scroll_limit(*lines_added); - self.display_offset = self.display_offset.saturating_sub(*lines_added); - } - - /// Grow number of columns in each row, reflowing if necessary. - fn grow_cols(&mut self, reflow: bool, cols: Column, cursor_pos: &mut Point, template: &T) { - // Check if a row needs to be wrapped. - let should_reflow = |row: &Row| -> bool { - let len = Column(row.len()); - reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE) - }; - - let mut new_empty_lines = 0; - let mut reversed: Vec> = Vec::with_capacity(self.raw.len()); - for (i, mut row) in self.raw.drain().enumerate().rev() { - // Check if reflowing should be performed. - let last_row = match reversed.last_mut() { - Some(last_row) if should_reflow(last_row) => last_row, - _ => { - reversed.push(row); - continue; - }, - }; - - // Remove wrap flag before appending additional cells. - if let Some(cell) = last_row.last_mut() { - cell.flags_mut().remove(Flags::WRAPLINE); - } - - // Remove leading spacers when reflowing wide char to the previous line. - let last_len = last_row.len(); - if last_len >= 2 - && !last_row[Column(last_len - 2)].flags().contains(Flags::WIDE_CHAR) - && last_row[Column(last_len - 1)].flags().contains(Flags::WIDE_CHAR_SPACER) - { - last_row.shrink(Column(last_len - 1)); - } - - // Append as many cells from the next line as possible. - let len = min(row.len(), cols.0 - last_row.len()); - - // Insert leading spacer when there's not enough room for reflowing wide char. - let mut cells = if row[Column(len - 1)].flags().contains(Flags::WIDE_CHAR) { - let mut cells = row.front_split_off(len - 1); - - let mut spacer = *template; - spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER); - cells.push(spacer); - - cells - } else { - row.front_split_off(len) - }; - - last_row.append(&mut cells); - - if row.is_empty() { - if i + reversed.len() < self.lines.0 { - // Add new line and move lines up if we can't pull from history. - cursor_pos.line = Line(cursor_pos.line.saturating_sub(1)); - new_empty_lines += 1; - } else if i < self.display_offset { - // Keep viewport in place if line is outside of the visible area. - self.display_offset = self.display_offset.saturating_sub(1); - } - - // Don't push line into the new buffer. - continue; - } else if let Some(cell) = last_row.last_mut() { - // Set wrap flag if next line still has cells. - cell.flags_mut().insert(Flags::WRAPLINE); - } - - reversed.push(row); - } - - // Add padding lines. - reversed.append(&mut vec![Row::new(cols, template); new_empty_lines]); - - // Fill remaining cells and reverse iterator. - let mut new_raw = Vec::with_capacity(reversed.len()); - for mut row in reversed.drain(..).rev() { - if row.len() < cols.0 { - row.grow(cols, template); - } - new_raw.push(row); - } - - self.raw.replace_inner(new_raw); - - self.display_offset = min(self.display_offset, self.history_size()); - self.cols = cols; - } - - /// Shrink number of columns in each row, reflowing if necessary. - fn shrink_cols(&mut self, reflow: bool, cols: Column, template: &T) { - let mut new_raw = Vec::with_capacity(self.raw.len()); - let mut buffered = None; - for (i, mut row) in self.raw.drain().enumerate().rev() { - // Append lines left over from previous row. - if let Some(buffered) = buffered.take() { - row.append_front(buffered); - } - - loop { - // Check if reflowing should be performed. - let mut wrapped = match row.shrink(cols) { - Some(wrapped) if reflow => wrapped, - _ => { - new_raw.push(row); - break; - }, - }; - - // Insert spacer if a wide char would be wrapped into the last column. - if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) { - wrapped.insert(0, row[cols - 1]); - - let mut spacer = *template; - spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER); - row[cols - 1] = spacer; - } - - // Remove wide char spacer before shrinking. - let len = wrapped.len(); - if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR))) - && wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER) - { - if len == 1 { - row[cols - 1].flags_mut().insert(Flags::WRAPLINE); - new_raw.push(row); - break; - } else { - wrapped[len - 2].flags_mut().insert(Flags::WRAPLINE); - wrapped.truncate(len - 1); - } - } - - new_raw.push(row); - - // Set line as wrapped if cells got removed. - if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) { - cell.flags_mut().insert(Flags::WRAPLINE); - } - - if wrapped - .last() - .map(|c| c.flags().contains(Flags::WRAPLINE) && i >= 1) - .unwrap_or(false) - && wrapped.len() < cols.0 - { - // Make sure previous wrap flag doesn't linger around. - if let Some(cell) = wrapped.last_mut() { - cell.flags_mut().remove(Flags::WRAPLINE); - } - - // Add removed cells to start of next row. - buffered = Some(wrapped); - break; - } else { - // Make sure viewport doesn't move if line is outside of the visible area. - if i < self.display_offset { - self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); - } - - // Make sure new row is at least as long as new width. - let occ = wrapped.len(); - if occ < cols.0 { - wrapped.append(&mut vec![*template; cols.0 - occ]); - } - row = Row::from_vec(wrapped, occ); - } - } - } - - let mut reversed: Vec> = new_raw.drain(..).rev().collect(); - reversed.truncate(self.max_scroll_limit + self.lines.0); - self.raw.replace_inner(reversed); - self.cols = cols; - } - - /// Remove lines from the visible area. - /// - /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the - /// bottom of the screen. This is achieved by pushing history "out the top" - /// of the terminal window. - /// - /// Alacritty takes the same approach. - fn shrink_lines(&mut self, target: Line, cursor_pos: &mut Point, template: &T) { - // Scroll up to keep cursor inside the window. - let required_scrolling = (cursor_pos.line + 1).saturating_sub(target.0); - if required_scrolling > 0 { - self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling), template); - } - - self.selection = None; - self.raw.rotate((self.lines - target).0 as isize); - self.raw.shrink_visible_lines(target); - self.lines = target; - } - #[inline] - pub fn scroll_down(&mut self, region: &Range, positions: Line, template: &T) { - let num_lines = self.num_lines().0; - let num_cols = self.num_cols().0; - + pub fn scroll_down(&mut self, region: &Range, positions: Line, template: T) { // Whether or not there is a scrolling region active, as long as it // starts at the top, we can do a full rotation which just involves // changing the start index. @@ -469,10 +256,6 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); - self.selection = self - .selection - .take() - .and_then(|s| s.rotate(num_lines, num_cols, region, -(*positions as isize))); self.decrease_scroll_limit(*positions); @@ -484,22 +267,16 @@ impl Grid { // Finally, reset recycled lines. for i in IndexRange(Line(0)..positions) { - self.raw[i].reset(&template); + self.raw[i].reset(template); } } else { - // Rotate selection to track content. - self.selection = self - .selection - .take() - .and_then(|s| s.rotate(num_lines, num_cols, region, -(*positions as isize))); - // Subregion rotation. for line in IndexRange((region.start + positions)..region.end).rev() { self.raw.swap_lines(line, line - positions); } for line in IndexRange(region.start..(region.start + positions)) { - self.raw[line].reset(&template); + self.raw[line].reset(template); } } } @@ -507,9 +284,8 @@ impl Grid { /// Move lines at the bottom towards the top. /// /// This is the performance-sensitive part of scrolling. - pub fn scroll_up(&mut self, region: &Range, positions: Line, template: &T) { + pub fn scroll_up(&mut self, region: &Range, positions: Line, template: T) { let num_lines = self.num_lines().0; - let num_cols = self.num_cols().0; if region.start == Line(0) { // Update display offset when not pinned to active area. @@ -522,10 +298,6 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate(-(*positions as isize)); - self.selection = self - .selection - .take() - .and_then(|s| s.rotate(num_lines, num_cols, region, *positions as isize)); // This next loop swaps "fixed" lines outside of a scroll region // back into place after the rotation. The work is done in buffer- @@ -541,15 +313,9 @@ impl Grid { // // Recycled lines are just above the end of the scrolling region. for i in 0..*positions { - self.raw[i + fixed_lines].reset(&template); + self.raw[i + fixed_lines].reset(template); } } else { - // Rotate selection to track content. - self.selection = self - .selection - .take() - .and_then(|s| s.rotate(num_lines, num_cols, region, *positions as isize)); - // Subregion rotation. for line in IndexRange(region.start..(region.end - positions)) { self.raw.swap_lines(line, line + positions); @@ -557,12 +323,12 @@ impl Grid { // Clear reused lines. for line in IndexRange((region.end - positions)..region.end) { - self.raw[line].reset(&template); + self.raw[line].reset(template); } } } - pub fn clear_viewport(&mut self, template: &T) { + pub fn clear_viewport(&mut self, template: T) { // Determine how many lines to scroll up by. let end = Point { line: 0, col: self.num_cols() }; let mut iter = self.iter_from(end); @@ -583,12 +349,12 @@ impl Grid { // Reset rotated lines. for i in positions.0..self.lines.0 { - self.raw[i].reset(&template); + self.raw[i].reset(template); } } /// Completely reset the grid state. - pub fn reset(&mut self, template: &T) { + pub fn reset(&mut self, template: T) { self.clear_history(); // Reset all visible lines. @@ -596,8 +362,9 @@ impl Grid { self.raw[row].reset(template); } + self.saved_cursor = Cursor::default(); + self.cursor = Cursor::default(); self.display_offset = 0; - self.selection = None; } } @@ -637,7 +404,7 @@ impl Grid { /// This is used only for initializing after loading ref-tests. #[inline] - pub fn initialize_all(&mut self, template: &T) + pub fn initialize_all(&mut self, template: T) where T: Copy + GridCell, { @@ -663,6 +430,12 @@ impl Grid { pub fn display_offset(&self) -> usize { self.display_offset } + + #[inline] + pub fn cursor_cell(&mut self) -> &mut T { + let point = self.cursor.point; + &mut self[&point] + } } pub struct GridIterator<'a, T> { diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs new file mode 100644 index 0000000..796c585 --- /dev/null +++ b/alacritty_terminal/src/grid/resize.rs @@ -0,0 +1,274 @@ +//! Grid resize and reflow. + +use std::cmp::{min, Ordering}; + +use crate::index::{Column, Line}; +use crate::term::cell::Flags; + +use crate::grid::row::Row; +use crate::grid::{Grid, GridCell}; + +impl Grid { + /// Resize the grid's width and/or height. + pub fn resize(&mut self, reflow: bool, lines: Line, cols: Column) { + match self.lines.cmp(&lines) { + Ordering::Less => self.grow_lines(lines), + Ordering::Greater => self.shrink_lines(lines), + Ordering::Equal => (), + } + + match self.cols.cmp(&cols) { + Ordering::Less => self.grow_cols(cols, reflow), + Ordering::Greater => self.shrink_cols(cols, reflow), + Ordering::Equal => (), + } + } + + /// Add lines to the visible area. + /// + /// Alacritty keeps the cursor at the bottom of the terminal as long as there + /// is scrollback available. Once scrollback is exhausted, new lines are + /// simply added to the bottom of the screen. + fn grow_lines(&mut self, new_line_count: Line) { + let lines_added = new_line_count - self.lines; + + // Need to resize before updating buffer. + self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, T::default())); + self.lines = new_line_count; + + let history_size = self.history_size(); + let from_history = min(history_size, lines_added.0); + + // Move existing lines up for every line that couldn't be pulled from history. + if from_history != lines_added.0 { + let delta = lines_added - from_history; + self.scroll_up(&(Line(0)..new_line_count), delta, T::default()); + } + + // Move cursor down for every line pulled from history. + self.saved_cursor.point.line += from_history; + self.cursor.point.line += from_history; + + self.display_offset = self.display_offset.saturating_sub(*lines_added); + self.decrease_scroll_limit(*lines_added); + } + + /// Remove lines from the visible area. + /// + /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the + /// bottom of the screen. This is achieved by pushing history "out the top" + /// of the terminal window. + /// + /// Alacritty takes the same approach. + fn shrink_lines(&mut self, target: Line) { + // Scroll up to keep content inside the window. + let required_scrolling = (self.cursor.point.line + 1).saturating_sub(target.0); + if required_scrolling > 0 { + self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling), T::default()); + + // Clamp cursors to the new viewport size. + self.saved_cursor.point.line = min(self.saved_cursor.point.line, target - 1); + self.cursor.point.line = min(self.cursor.point.line, target - 1); + } + + self.raw.rotate((self.lines - target).0 as isize); + self.raw.shrink_visible_lines(target); + self.lines = target; + } + + /// Grow number of columns in each row, reflowing if necessary. + fn grow_cols(&mut self, cols: Column, reflow: bool) { + // Check if a row needs to be wrapped. + let should_reflow = |row: &Row| -> bool { + let len = Column(row.len()); + reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE) + }; + + self.cols = cols; + + let mut reversed: Vec> = Vec::with_capacity(self.raw.len()); + let mut new_empty_lines = 0; + + let mut rows = self.raw.take_all(); + + for (i, mut row) in rows.drain(..).enumerate().rev() { + // Check if reflowing should be performed. + let last_row = match reversed.last_mut() { + Some(last_row) if should_reflow(last_row) => last_row, + _ => { + reversed.push(row); + continue; + }, + }; + + // Remove wrap flag before appending additional cells. + if let Some(cell) = last_row.last_mut() { + cell.flags_mut().remove(Flags::WRAPLINE); + } + + // Remove leading spacers when reflowing wide char to the previous line. + let mut last_len = last_row.len(); + if last_len >= 2 + && !last_row[Column(last_len - 2)].flags().contains(Flags::WIDE_CHAR) + && last_row[Column(last_len - 1)].flags().contains(Flags::WIDE_CHAR_SPACER) + { + last_row.shrink(Column(last_len - 1)); + last_len -= 1; + } + + // Don't try to pull more cells from the next line than available. + let len = min(row.len(), cols.0 - last_len); + + // Insert leading spacer when there's not enough room for reflowing wide char. + let mut cells = if row[Column(len - 1)].flags().contains(Flags::WIDE_CHAR) { + let mut cells = row.front_split_off(len - 1); + + let mut spacer = T::default(); + spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER); + cells.push(spacer); + + cells + } else { + row.front_split_off(len) + }; + + // Reflow cells to previous row. + last_row.append(&mut cells); + + if row.is_empty() { + if i + reversed.len() < self.lines.0 { + // Add new line and move everything up if we can't pull from history. + self.saved_cursor.point.line.0 = self.saved_cursor.point.line.saturating_sub(1); + self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(1); + new_empty_lines += 1; + } else { + // Since we removed a line, rotate down the viewport. + self.display_offset = self.display_offset.saturating_sub(1); + } + + // Don't push line into the new buffer. + continue; + } else if let Some(cell) = last_row.last_mut() { + // Set wrap flag if next line still has cells. + cell.flags_mut().insert(Flags::WRAPLINE); + } + + reversed.push(row); + } + + // Add all new empty lines in one go. + reversed.append(&mut vec![Row::new(cols, T::default()); new_empty_lines]); + + // Reverse iterator and fill all rows that are still too short. + let mut new_raw = Vec::with_capacity(reversed.len()); + for mut row in reversed.drain(..).rev() { + if row.len() < cols.0 { + row.grow(cols, T::default()); + } + new_raw.push(row); + } + + self.raw.replace_inner(new_raw); + + // Clamp display offset in case lines above it got merged. + self.display_offset = min(self.display_offset, self.history_size()); + } + + /// Shrink number of columns in each row, reflowing if necessary. + fn shrink_cols(&mut self, cols: Column, reflow: bool) { + self.cols = cols; + + let mut rows = self.raw.take_all(); + + let mut new_raw = Vec::with_capacity(self.raw.len()); + let mut buffered: Option> = None; + + for (i, mut row) in rows.drain(..).enumerate().rev() { + // Append lines left over from the previous row. + if let Some(buffered) = buffered.take() { + row.append_front(buffered); + } + + loop { + // Remove all cells which require reflowing. + let mut wrapped = match row.shrink(cols) { + Some(wrapped) if reflow => wrapped, + _ => { + new_raw.push(row); + break; + }, + }; + + // Insert spacer if a wide char would be wrapped into the last column. + if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) { + wrapped.insert(0, row[cols - 1]); + + let mut spacer = T::default(); + spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER); + row[cols - 1] = spacer; + } + + // Remove wide char spacer before shrinking. + let len = wrapped.len(); + if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR))) + && wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER) + { + if len == 1 { + // Delete the wrapped content if it contains only a leading spacer. + row[cols - 1].flags_mut().insert(Flags::WRAPLINE); + new_raw.push(row); + break; + } else { + // Remove the leading spacer from the end of the wrapped row. + wrapped[len - 2].flags_mut().insert(Flags::WRAPLINE); + wrapped.truncate(len - 1); + } + } + + new_raw.push(row); + + // Set line as wrapped if cells got removed. + if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) { + cell.flags_mut().insert(Flags::WRAPLINE); + } + + if wrapped + .last() + .map(|c| c.flags().contains(Flags::WRAPLINE) && i >= 1) + .unwrap_or(false) + && wrapped.len() < cols.0 + { + // Make sure previous wrap flag doesn't linger around. + if let Some(cell) = wrapped.last_mut() { + cell.flags_mut().remove(Flags::WRAPLINE); + } + + // Add removed cells to start of next row. + buffered = Some(wrapped); + break; + } else { + // Since we added a line, rotate up the viewport. + if i < self.display_offset { + self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); + } + + // Make sure new row is at least as long as new width. + let occ = wrapped.len(); + if occ < cols.0 { + wrapped.append(&mut vec![T::default(); cols.0 - occ]); + } + row = Row::from_vec(wrapped, occ); + } + } + } + + // Reverse iterator and use it as the new grid storage. + let mut reversed: Vec> = new_raw.drain(..).rev().collect(); + reversed.truncate(self.max_scroll_limit + self.lines.0); + self.raw.replace_inner(reversed); + + // Wrap content going beyond new width if necessary. + self.saved_cursor.point.col = min(self.saved_cursor.point.col, self.cols - 1); + self.cursor.point.col = min(self.cursor.point.col, self.cols - 1); + } +} diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs index 22a1062..b5248ad 100644 --- a/alacritty_terminal/src/grid/row.rs +++ b/alacritty_terminal/src/grid/row.rs @@ -43,20 +43,20 @@ impl PartialEq for Row { } impl Row { - pub fn new(columns: Column, template: &T) -> Row + pub fn new(columns: Column, template: T) -> Row where T: GridCell, { let occ = if template.is_empty() { 0 } else { columns.0 }; - Row { inner: vec![*template; columns.0], occ } + Row { inner: vec![template; columns.0], occ } } - pub fn grow(&mut self, cols: Column, template: &T) { + pub fn grow(&mut self, cols: Column, template: T) { if self.inner.len() >= cols.0 { return; } - self.inner.append(&mut vec![*template; cols.0 - self.len()]); + self.inner.append(&mut vec![template; cols.0 - self.len()]); } pub fn shrink(&mut self, cols: Column) -> Option> @@ -83,14 +83,12 @@ impl Row { /// Reset all cells in the row to the `template` cell. #[inline] - pub fn reset(&mut self, template: &T) + pub fn reset(&mut self, template: T) where T: GridCell + PartialEq, { debug_assert!(!self.inner.is_empty()); - let template = *template; - // Mark all cells as dirty if template cell changed. let len = self.inner.len(); if !self.inner[len - 1].fast_eq(template) { diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs index 4820036..4b7ca41 100644 --- a/alacritty_terminal/src/grid/storage.rs +++ b/alacritty_terminal/src/grid/storage.rs @@ -1,6 +1,6 @@ use std::cmp::{max, PartialEq}; +use std::mem; use std::ops::{Index, IndexMut}; -use std::vec::Drain; use serde::{Deserialize, Serialize}; @@ -142,7 +142,7 @@ impl Storage { /// Dynamically grow the storage buffer at runtime. #[inline] - pub fn initialize(&mut self, additional_rows: usize, template: &T, cols: Column) + pub fn initialize(&mut self, additional_rows: usize, template: T, cols: Column) where T: GridCell + Copy, { @@ -238,18 +238,27 @@ impl Storage { self.zero = (self.zero + count) % self.inner.len(); } - /// Drain all rows in the grid. - pub fn drain(&mut self) -> Drain<'_, Row> { - self.truncate(); - self.inner.drain(..) - } - /// Update the raw storage buffer. + #[inline] pub fn replace_inner(&mut self, vec: Vec>) { self.len = vec.len(); self.inner = vec; self.zero = 0; } + + /// Remove all rows from storage. + #[inline] + pub fn take_all(&mut self) -> Vec> { + self.truncate(); + + let mut buffer = Vec::new(); + + mem::swap(&mut buffer, &mut self.inner); + self.zero = 0; + self.len = 0; + + buffer + } } impl Index for Storage { @@ -315,7 +324,7 @@ mod tests { #[test] fn with_capacity() { - let storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' ')); + let storage = Storage::with_capacity(Line(3), Row::new(Column(0), ' ')); assert_eq!(storage.inner.len(), 3); assert_eq!(storage.len, 3); @@ -325,33 +334,33 @@ mod tests { #[test] fn indexing() { - let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' ')); + let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), ' ')); - storage[0] = Row::new(Column(1), &'0'); - storage[1] = Row::new(Column(1), &'1'); - storage[2] = Row::new(Column(1), &'2'); + storage[0] = Row::new(Column(1), '0'); + storage[1] = Row::new(Column(1), '1'); + storage[2] = Row::new(Column(1), '2'); - assert_eq!(storage[0], Row::new(Column(1), &'0')); - assert_eq!(storage[1], Row::new(Column(1), &'1')); - assert_eq!(storage[2], Row::new(Column(1), &'2')); + assert_eq!(storage[0], Row::new(Column(1), '0')); + assert_eq!(storage[1], Row::new(Column(1), '1')); + assert_eq!(storage[2], Row::new(Column(1), '2')); storage.zero += 1; - assert_eq!(storage[0], Row::new(Column(1), &'1')); - assert_eq!(storage[1], Row::new(Column(1), &'2')); - assert_eq!(storage[2], Row::new(Column(1), &'0')); + assert_eq!(storage[0], Row::new(Column(1), '1')); + assert_eq!(storage[1], Row::new(Column(1), '2')); + assert_eq!(storage[2], Row::new(Column(1), '0')); } #[test] #[should_panic] fn indexing_above_inner_len() { - let storage = Storage::with_capacity(Line(1), Row::new(Column(0), &' ')); + let storage = Storage::with_capacity(Line(1), Row::new(Column(0), ' ')); let _ = &storage[2]; } #[test] fn rotate() { - let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), &' ')); + let mut storage = Storage::with_capacity(Line(3), Row::new(Column(0), ' ')); storage.rotate(2); assert_eq!(storage.zero, 2); storage.shrink_lines(2); @@ -376,9 +385,9 @@ mod tests { // Setup storage area let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'-'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '-'), ], zero: 0, visible_lines: Line(3), @@ -386,15 +395,15 @@ mod tests { }; // Grow buffer - storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); + storage.grow_visible_lines(Line(4), Row::new(Column(1), '-')); // Make sure the result is correct let expected = Storage { inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'-'), + Row::new(Column(1), '-'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '-'), ], zero: 1, visible_lines: Line(4), @@ -422,9 +431,9 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), + Row::new(Column(1), '-'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), ], zero: 1, visible_lines: Line(3), @@ -432,15 +441,15 @@ mod tests { }; // Grow buffer. - storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); + storage.grow_visible_lines(Line(4), Row::new(Column(1), '-')); // Make sure the result is correct. let expected = Storage { inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), + Row::new(Column(1), '-'), + Row::new(Column(1), '-'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), ], zero: 2, visible_lines: Line(4), @@ -467,9 +476,9 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'2'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), ], zero: 1, visible_lines: Line(3), @@ -482,9 +491,9 @@ mod tests { // Make sure the result is correct. let expected = Storage { inner: vec![ - Row::new(Column(1), &'2'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), ], zero: 1, visible_lines: Line(2), @@ -511,9 +520,9 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), ], zero: 0, visible_lines: Line(3), @@ -526,9 +535,9 @@ mod tests { // Make sure the result is correct. let expected = Storage { inner: vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), ], zero: 0, visible_lines: Line(2), @@ -561,12 +570,12 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 2, visible_lines: Line(6), @@ -579,12 +588,12 @@ mod tests { // Make sure the result is correct. let expected = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 2, visible_lines: Line(2), @@ -613,12 +622,12 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 2, visible_lines: Line(1), @@ -630,7 +639,7 @@ mod tests { // Make sure the result is correct. let expected = Storage { - inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], + inner: vec![Row::new(Column(1), '0'), Row::new(Column(1), '1')], zero: 0, visible_lines: Line(1), len: 2, @@ -655,9 +664,9 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '0'), ], zero: 2, visible_lines: Line(1), @@ -669,7 +678,7 @@ mod tests { // Make sure the result is correct. let expected = Storage { - inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], + inner: vec![Row::new(Column(1), '0'), Row::new(Column(1), '1')], zero: 0, visible_lines: Line(1), len: 2, @@ -709,12 +718,12 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 2, visible_lines: Line(0), @@ -727,12 +736,12 @@ mod tests { // Make sure the result after shrinking is correct. let shrinking_expected = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 2, visible_lines: Line(0), @@ -743,18 +752,18 @@ mod tests { assert_eq!(storage.len, shrinking_expected.len); // Grow buffer. - storage.grow_lines(4, Row::new(Column(1), &'-')); + storage.grow_lines(4, Row::new(Column(1), '-')); // Make sure the result after shrinking is correct. let growing_expected = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '-'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 3, visible_lines: Line(0), @@ -770,12 +779,12 @@ mod tests { // Setup storage area. let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '4'), + Row::new(Column(1), '5'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ], zero: 2, visible_lines: Line(0), @@ -784,18 +793,18 @@ mod tests { // Initialize additional lines. let init_size = 3; - storage.initialize(init_size, &'-', Column(1)); + storage.initialize(init_size, '-', Column(1)); // Make sure the lines are present and at the right location. let expected_init_size = std::cmp::max(init_size, MAX_CACHE_SIZE); - let mut expected_inner = vec![Row::new(Column(1), &'4'), Row::new(Column(1), &'5')]; - expected_inner.append(&mut vec![Row::new(Column(1), &'-'); expected_init_size]); + let mut expected_inner = vec![Row::new(Column(1), '4'), Row::new(Column(1), '5')]; + expected_inner.append(&mut vec![Row::new(Column(1), '-'); expected_init_size]); expected_inner.append(&mut vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), + Row::new(Column(1), '0'), + Row::new(Column(1), '1'), + Row::new(Column(1), '2'), + Row::new(Column(1), '3'), ]); let expected_storage = Storage { inner: expected_inner, @@ -813,9 +822,9 @@ mod tests { fn rotate_wrap_zero() { let mut storage = Storage { inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'-'), + Row::new(Column(1), '-'), + Row::new(Column(1), '-'), + Row::new(Column(1), '-'), ], zero: 2, visible_lines: Line(0), diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs index ef011d1..04400af 100644 --- a/alacritty_terminal/src/grid/tests.rs +++ b/alacritty_terminal/src/grid/tests.rs @@ -79,7 +79,7 @@ fn scroll_up() { grid[Line(i)][Column(0)] = i; } - grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0); + grid.scroll_up(&(Line(0)..Line(10)), Line(2), 0); assert_eq!(grid[Line(0)][Column(0)], 2); assert_eq!(grid[Line(0)].occ, 1); @@ -111,7 +111,7 @@ fn scroll_down() { grid[Line(i)][Column(0)] = i; } - grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0); + grid.scroll_down(&(Line(0)..Line(10)), Line(2), 0); assert_eq!(grid[Line(0)][Column(0)], 0); // was 8. assert_eq!(grid[Line(0)].occ, 0); @@ -183,7 +183,7 @@ fn shrink_reflow() { grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = cell('5'); - grid.resize(true, Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(true, Line(1), Column(2)); assert_eq!(grid.len(), 3); @@ -209,8 +209,8 @@ fn shrink_reflow_twice() { grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = cell('5'); - grid.resize(true, Line(1), Column(4), &mut Point::new(Line(0), Column(0)), &Cell::default()); - grid.resize(true, Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(true, Line(1), Column(4)); + grid.resize(true, Line(1), Column(2)); assert_eq!(grid.len(), 3); @@ -236,7 +236,7 @@ fn shrink_reflow_empty_cell_inside_line() { grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = Cell::default(); - grid.resize(true, Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(true, Line(1), Column(2)); assert_eq!(grid.len(), 2); @@ -248,7 +248,7 @@ fn shrink_reflow_empty_cell_inside_line() { assert_eq!(grid[0][Column(0)], cell('3')); assert_eq!(grid[0][Column(1)], cell('4')); - grid.resize(true, Line(1), Column(1), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(true, Line(1), Column(1)); assert_eq!(grid.len(), 4); @@ -273,7 +273,7 @@ fn grow_reflow() { grid[Line(1)][Column(0)] = cell('3'); grid[Line(1)][Column(1)] = Cell::default(); - grid.resize(true, Line(2), Column(3), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(true, Line(2), Column(3)); assert_eq!(grid.len(), 2); @@ -299,7 +299,7 @@ fn grow_reflow_multiline() { grid[Line(2)][Column(0)] = cell('5'); grid[Line(2)][Column(1)] = cell('6'); - grid.resize(true, Line(3), Column(6), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(true, Line(3), Column(6)); assert_eq!(grid.len(), 3); @@ -330,7 +330,7 @@ fn grow_reflow_disabled() { grid[Line(1)][Column(0)] = cell('3'); grid[Line(1)][Column(1)] = Cell::default(); - grid.resize(false, Line(2), Column(3), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(false, Line(2), Column(3)); assert_eq!(grid.len(), 2); @@ -354,7 +354,7 @@ fn shrink_reflow_disabled() { grid[Line(0)][Column(3)] = cell('4'); grid[Line(0)][Column(4)] = cell('5'); - grid.resize(false, Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); + grid.resize(false, Line(1), Column(2)); assert_eq!(grid.len(), 1); diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs index 72e7158..a5f2bc7 100644 --- a/alacritty_terminal/src/index.rs +++ b/alacritty_terminal/src/index.rs @@ -53,27 +53,28 @@ impl Point { #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn sub(mut self, num_cols: usize, rhs: usize) -> Point + pub fn sub(mut self, num_cols: Column, rhs: usize) -> Point where L: Copy + Default + Into + Add + Sub, { - let line_changes = - (rhs.saturating_sub(self.col.0) as f32 / num_cols as f32).ceil() as usize; - if self.line.into() > Line(line_changes) { + let num_cols = num_cols.0; + let line_changes = (rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols; + if self.line.into() >= Line(line_changes) { self.line = self.line - line_changes; + self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols); + self } else { - self.line = Default::default(); + Point::new(L::default(), Column(0)) } - self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols); - self } #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn add(mut self, num_cols: usize, rhs: usize) -> Point + pub fn add(mut self, num_cols: Column, rhs: usize) -> Point where L: Copy + Default + Into + Add + Sub, { + let num_cols = num_cols.0; self.line = self.line + (rhs + self.col.0) / num_cols; self.col = Column((self.col.0 + rhs) % num_cols); self @@ -81,30 +82,30 @@ impl Point { #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn sub_absolute(mut self, num_cols: usize, rhs: usize) -> Point + pub fn sub_absolute(mut self, num_cols: Column, rhs: usize) -> Point where L: Copy + Default + Into + Add + Sub, { - self.line = - self.line + (rhs.saturating_sub(self.col.0) as f32 / num_cols as f32).ceil() as usize; + let num_cols = num_cols.0; + self.line = self.line + ((rhs + num_cols - 1).saturating_sub(self.col.0) / num_cols); self.col = Column((num_cols + self.col.0 - rhs % num_cols) % num_cols); self } #[inline] #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn add_absolute(mut self, num_cols: usize, rhs: usize) -> Point + pub fn add_absolute(mut self, num_cols: Column, rhs: usize) -> Point where L: Copy + Default + Into + Add + Sub, { - let line_changes = (rhs + self.col.0) / num_cols; - if self.line.into() > Line(line_changes) { + let line_changes = (rhs + self.col.0) / num_cols.0; + if self.line.into() >= Line(line_changes) { self.line = self.line - line_changes; + self.col = Column((self.col.0 + rhs) % num_cols.0); + self } else { - self.line = Default::default(); + Point::new(L::default(), num_cols - 1) } - self.col = Column((self.col.0 + rhs) % num_cols); - self } } @@ -453,4 +454,104 @@ mod tests { assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(1))); assert!(Point::new(Line(1), Column(1)) > Point::new(Line(1), Column(0))); } + + #[test] + fn sub() { + let num_cols = Column(42); + let point = Point::new(0, Column(13)); + + let result = point.sub(num_cols, 1); + + assert_eq!(result, Point::new(0, point.col - 1)); + } + + #[test] + fn sub_wrap() { + let num_cols = Column(42); + let point = Point::new(1, Column(0)); + + let result = point.sub(num_cols, 1); + + assert_eq!(result, Point::new(0, num_cols - 1)); + } + + #[test] + fn sub_clamp() { + let num_cols = Column(42); + let point = Point::new(0, Column(0)); + + let result = point.sub(num_cols, 1); + + assert_eq!(result, point); + } + + #[test] + fn add() { + let num_cols = Column(42); + let point = Point::new(0, Column(13)); + + let result = point.add(num_cols, 1); + + assert_eq!(result, Point::new(0, point.col + 1)); + } + + #[test] + fn add_wrap() { + let num_cols = Column(42); + let point = Point::new(0, num_cols - 1); + + let result = point.add(num_cols, 1); + + assert_eq!(result, Point::new(1, Column(0))); + } + + #[test] + fn add_absolute() { + let num_cols = Column(42); + let point = Point::new(0, Column(13)); + + let result = point.add_absolute(num_cols, 1); + + assert_eq!(result, Point::new(0, point.col + 1)); + } + + #[test] + fn add_absolute_wrap() { + let num_cols = Column(42); + let point = Point::new(1, num_cols - 1); + + let result = point.add_absolute(num_cols, 1); + + assert_eq!(result, Point::new(0, Column(0))); + } + + #[test] + fn add_absolute_clamp() { + let num_cols = Column(42); + let point = Point::new(0, num_cols - 1); + + let result = point.add_absolute(num_cols, 1); + + assert_eq!(result, point); + } + + #[test] + fn sub_absolute() { + let num_cols = Column(42); + let point = Point::new(0, Column(13)); + + let result = point.sub_absolute(num_cols, 1); + + assert_eq!(result, Point::new(0, point.col - 1)); + } + + #[test] + fn sub_absolute_wrap() { + let num_cols = Column(42); + let point = Point::new(0, Column(0)); + + let result = point.sub_absolute(num_cols, 1); + + assert_eq!(result, Point::new(1, num_cols - 1)); + } } diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 7f5c21e..5b05310 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -106,20 +106,22 @@ impl Selection { } } + /// Update the end of the selection. pub fn update(&mut self, point: Point, side: Side) { self.region.end = Anchor::new(point, side); } pub fn rotate( mut self, - num_lines: usize, - num_cols: usize, - scrolling_region: &Range, - offset: isize, + num_lines: Line, + num_cols: Column, + range: &Range, + delta: isize, ) -> Option { - // Convert scrolling region from viewport to buffer coordinates. - let region_start = num_lines - scrolling_region.start.0; - let region_end = num_lines - scrolling_region.end.0; + let range_bottom = range.start.0; + let range_top = range.end.0; + let num_lines = num_lines.0; + let num_cols = num_cols.0; let (mut start, mut end) = (&mut self.region.start, &mut self.region.end); if Self::points_need_swap(start.point, end.point) { @@ -127,31 +129,30 @@ impl Selection { } // Rotate start of selection. - if (start.point.line < region_start || region_start == num_lines) - && start.point.line >= region_end + if (start.point.line < range_top || range_top == num_lines) + && start.point.line >= range_bottom { - start.point.line = usize::try_from(start.point.line as isize + offset).unwrap_or(0); + start.point.line = usize::try_from(start.point.line as isize + delta).unwrap_or(0); // If end is within the same region, delete selection once start rotates out. - if start.point.line < region_end && end.point.line >= region_end { + if start.point.line < range_bottom && end.point.line >= range_bottom { return None; } // Clamp selection to start of region. - if start.point.line >= region_start && region_start != num_lines { + if start.point.line >= range_top && range_top != num_lines { if self.ty != SelectionType::Block { start.point.col = Column(0); start.side = Side::Left; } - start.point.line = region_start - 1; + start.point.line = range_top - 1; } } // Rotate end of selection. - if (end.point.line < region_start || region_start == num_lines) - && end.point.line >= region_end + if (end.point.line < range_top || range_top == num_lines) && end.point.line >= range_bottom { - end.point.line = usize::try_from(end.point.line as isize + offset).unwrap_or(0); + end.point.line = usize::try_from(end.point.line as isize + delta).unwrap_or(0); // Delete selection if end has overtaken the start. if end.point.line > start.point.line { @@ -159,12 +160,12 @@ impl Selection { } // Clamp selection to end of region. - if end.point.line < region_end { + if end.point.line < range_bottom { if self.ty != SelectionType::Block { end.point.col = Column(num_cols - 1); end.side = Side::Right; } - end.point.line = region_end; + end.point.line = range_bottom; } } @@ -175,7 +176,7 @@ impl Selection { match self.ty { SelectionType::Simple => { let (mut start, mut end) = (self.region.start, self.region.end); - if Selection::points_need_swap(start.point, end.point) { + if Self::points_need_swap(start.point, end.point) { mem::swap(&mut start, &mut end); } @@ -519,14 +520,14 @@ mod tests { #[test] fn line_selection() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Lines, Point::new(0, Column(1)), Side::Left); selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(9, Column(0)), end: Point::new(7, Column(4)), is_block: false, @@ -535,14 +536,14 @@ mod tests { #[test] fn semantic_selection() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Semantic, Point::new(0, Column(3)), Side::Left); selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(9, Column(0)), end: Point::new(7, Column(3)), is_block: false, @@ -551,14 +552,14 @@ mod tests { #[test] fn simple_selection() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Simple, Point::new(0, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(9, Column(0)), end: Point::new(7, Column(3)), is_block: false, @@ -567,14 +568,14 @@ mod tests { #[test] fn block_selection() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Block, Point::new(0, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right); - selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(0)..num_lines), 7).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(9, Column(2)), end: Point::new(7, Column(3)), is_block: true @@ -611,15 +612,14 @@ mod tests { #[test] fn rotate_in_region_up() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Simple, Point::new(2, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right); - selection = - selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(1)..(num_lines - 1)), 4).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(8, Column(0)), end: Point::new(6, Column(3)), is_block: false, @@ -628,32 +628,30 @@ mod tests { #[test] fn rotate_in_region_down() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Simple, Point::new(5, Column(3)), Side::Right); selection.update(Point::new(8, Column(1)), Side::Left); - selection = - selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), -5).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(1)..(num_lines - 1)), -5).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(3, Column(1)), - end: Point::new(1, Column(num_cols - 1)), + end: Point::new(1, num_cols - 1), is_block: false, }); } #[test] fn rotate_in_region_up_block() { - let num_lines = 10; - let num_cols = 5; + let num_lines = Line(10); + let num_cols = Column(5); let mut selection = Selection::new(SelectionType::Block, Point::new(2, Column(3)), Side::Right); selection.update(Point::new(5, Column(1)), Side::Right); - selection = - selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap(); + selection = selection.rotate(num_lines, num_cols, &(Line(1)..(num_lines - 1)), 4).unwrap(); - assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange { + assert_eq!(selection.to_range(&term(num_cols.0, num_lines.0)).unwrap(), SelectionRange { start: Point::new(8, Column(2)), end: Point::new(6, Column(3)), is_block: true, diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index c23b359..f95ed0c 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -178,7 +178,7 @@ mod tests { #[test] fn line_length_works() { let template = Cell::default(); - let mut row = Row::new(Column(10), &template); + let mut row = Row::new(Column(10), template); row[Column(5)].c = 'a'; assert_eq!(row.line_length(), Column(6)); @@ -187,7 +187,7 @@ mod tests { #[test] fn line_length_works_with_wrapline() { let template = Cell::default(); - let mut row = Row::new(Column(10), &template); + let mut row = Row::new(Column(10), template); row[Column(9)].flags.insert(super::Flags::WRAPLINE); assert_eq!(row.line_length(), Column(10)); diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index d575942..bcb4853 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -49,6 +49,9 @@ const TITLE_STACK_MAX_DEPTH: usize = 4096; /// Default tab interval, corresponding to terminfo `it` value. const INITIAL_TABSTOPS: usize = 8; +/// Minimum number of columns and lines. +const MIN_SIZE: usize = 2; + /// A type that can expand a given point to a region. /// /// Usually this is implemented for some 2-D array type since @@ -290,7 +293,7 @@ impl<'a, C> RenderableCellsIter<'a, C> { return true; } - let num_cols = self.grid.num_cols().0; + let num_cols = self.grid.num_cols(); let cell = self.grid[&point]; // Check if wide char's spacers are selected. @@ -543,86 +546,6 @@ pub mod mode { pub use crate::term::mode::TermMode; -trait CharsetMapping { - fn map(&self, c: char) -> char { - c - } -} - -impl CharsetMapping for StandardCharset { - /// Switch/Map character to the active charset. Ascii is the common case and - /// for that we want to do as little as possible. - #[inline] - fn map(&self, c: char) -> char { - match *self { - StandardCharset::Ascii => c, - StandardCharset::SpecialCharacterAndLineDrawing => match c { - '`' => '◆', - 'a' => '▒', - 'b' => '\t', - 'c' => '\u{000c}', - 'd' => '\r', - 'e' => '\n', - 'f' => '°', - 'g' => '±', - 'h' => '\u{2424}', - 'i' => '\u{000b}', - 'j' => '┘', - 'k' => '┐', - 'l' => '┌', - 'm' => '└', - 'n' => '┼', - 'o' => '⎺', - 'p' => '⎻', - 'q' => '─', - 'r' => '⎼', - 's' => '⎽', - 't' => '├', - 'u' => '┤', - 'v' => '┴', - 'w' => '┬', - 'x' => '│', - 'y' => '≤', - 'z' => '≥', - '{' => 'π', - '|' => '≠', - '}' => '£', - '~' => '·', - _ => c, - }, - } - } -} - -#[derive(Default, Copy, Clone)] -struct Charsets([StandardCharset; 4]); - -impl Index for Charsets { - type Output = StandardCharset; - - fn index(&self, index: CharsetIndex) -> &StandardCharset { - &self.0[index as usize] - } -} - -impl IndexMut for Charsets { - fn index_mut(&mut self, index: CharsetIndex) -> &mut StandardCharset { - &mut self.0[index as usize] - } -} - -#[derive(Default, Copy, Clone)] -pub struct Cursor { - /// The location of this cursor. - pub point: Point, - - /// Template cell when using this cursor. - template: Cell, - - /// Currently configured graphic character sets. - charsets: Charsets, -} - pub struct VisualBell { /// Visual bell animation. animation: VisualBellAnimation, @@ -803,9 +726,20 @@ impl SizeInfo { } pub struct Term { - /// Terminal focus. + /// Terminal requires redraw. + pub dirty: bool, + + /// Visual bell configuration and status. + pub visual_bell: VisualBell, + + /// Terminal focus controlling the cursor shape. pub is_focused: bool, + /// Cursor for keyboard selection. + pub vi_mode_cursor: ViModeCursor, + + pub selection: Option, + /// The grid. grid: Grid, @@ -822,12 +756,6 @@ pub struct Term { /// Alt is active. alt: bool, - /// The cursor. - cursor: Cursor, - - /// Cursor location for vi mode. - pub vi_mode_cursor: ViModeCursor, - /// Index into `charsets`, pointing to what ASCII is currently being mapped to. active_charset: CharsetIndex, @@ -842,16 +770,6 @@ pub struct Term { /// Range going from top to bottom of the terminal, indexed from the top of the viewport. scroll_region: Range, - pub dirty: bool, - - pub visual_bell: VisualBell, - - /// Saved cursor from main grid. - cursor_save: Cursor, - - /// Saved cursor from alt grid. - cursor_save_alt: Cursor, - semantic_escape_chars: String, /// Colors used for rendering. @@ -893,14 +811,6 @@ pub struct Term { } impl Term { - pub fn selection(&self) -> &Option { - &self.grid.selection - } - - pub fn selection_mut(&mut self) -> &mut Option { - &mut self.grid.selection - } - #[inline] pub fn scroll_display(&mut self, scroll: Scroll) where @@ -938,10 +848,7 @@ impl Term { alt_grid: alt, alt: false, active_charset: Default::default(), - cursor: Default::default(), vi_mode_cursor: Default::default(), - cursor_save: Default::default(), - cursor_save_alt: Default::default(), tabs, mode: Default::default(), scroll_region, @@ -959,6 +866,7 @@ impl Term { title: None, default_title: config.window.title.clone(), title_stack: Vec::new(), + selection: None, } } @@ -1000,8 +908,8 @@ impl Term { /// Convert the active selection to a String. pub fn selection_to_string(&self) -> Option { - let selection = self.grid.selection.clone()?; - let SelectionRange { start, end, is_block } = selection.to_range(self)?; + let selection_range = self.selection.as_ref().and_then(|s| s.to_range(self))?; + let SelectionRange { start, end, is_block } = selection_range; let mut res = String::new(); @@ -1125,7 +1033,7 @@ impl Term { /// background color. Cells with an alternate background color are /// considered renderable as are cells with any text content. pub fn renderable_cells<'b, C>(&'b self, config: &'b Config) -> RenderableCellsIter<'_, C> { - let selection = self.grid.selection.as_ref().and_then(|s| s.to_range(self)); + let selection = self.selection.as_ref().and_then(|s| s.to_range(self)); RenderableCellsIter::new(&self, config, selection) } @@ -1134,52 +1042,31 @@ impl Term { pub fn resize(&mut self, size: &SizeInfo) { let old_cols = self.grid.num_cols(); let old_lines = self.grid.num_lines(); - let mut num_cols = size.cols(); - let mut num_lines = size.lines(); + let num_cols = max(size.cols(), Column(MIN_SIZE)); + let num_lines = max(size.lines(), Line(MIN_SIZE)); if old_cols == num_cols && old_lines == num_lines { debug!("Term::resize dimensions unchanged"); return; } - self.grid.selection = None; - self.alt_grid.selection = None; - - // Should not allow less than 2 cols, causes all sorts of checks to be required. - if num_cols <= Column(1) { - num_cols = Column(2); - } - - // Should not allow less than 2 lines, causes all sorts of checks to be required. - if num_lines <= Line(1) { - num_lines = Line(2); - } - debug!("New num_cols is {} and num_lines is {}", num_cols, num_lines); let is_alt = self.mode.contains(TermMode::ALT_SCREEN); - let alt_cursor_point = - if is_alt { &mut self.cursor_save.point } else { &mut self.cursor_save_alt.point }; - // Resize grids to new size. - self.grid.resize(!is_alt, num_lines, num_cols, &mut self.cursor.point, &Cell::default()); - self.alt_grid.resize(is_alt, num_lines, num_cols, alt_cursor_point, &Cell::default()); + self.grid.resize(!is_alt, num_lines, num_cols); + self.alt_grid.resize(is_alt, num_lines, num_cols); - // Reset scrolling region to new size. - self.scroll_region = Line(0)..self.grid.num_lines(); - - // Ensure cursors are in-bounds. - self.cursor.point.col = min(self.cursor.point.col, num_cols - 1); - self.cursor.point.line = min(self.cursor.point.line, num_lines - 1); - self.cursor_save.point.col = min(self.cursor_save.point.col, num_cols - 1); - self.cursor_save.point.line = min(self.cursor_save.point.line, num_lines - 1); - self.cursor_save_alt.point.col = min(self.cursor_save_alt.point.col, num_cols - 1); - self.cursor_save_alt.point.line = min(self.cursor_save_alt.point.line, num_lines - 1); + // Clamp vi cursor to viewport. self.vi_mode_cursor.point.col = min(self.vi_mode_cursor.point.col, num_cols - 1); self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1); // Recreate tabs list. self.tabs.resize(self.grid.num_cols()); + + // Reset scrolling region and selection. + self.scroll_region = Line(0)..self.grid.num_lines(); + self.selection = None; } #[inline] @@ -1187,20 +1074,16 @@ impl Term { &self.mode } - #[inline] - pub fn cursor(&self) -> &Cursor { - &self.cursor - } - pub fn swap_alt(&mut self) { if self.alt { - let template = self.cursor.template; + let template = self.grid.cursor.template; self.grid.region_mut(..).each(|c| c.reset(&template)); } - self.grid.selection = None; self.alt = !self.alt; mem::swap(&mut self.grid, &mut self.alt_grid); + + self.selection = None; } /// Scroll screen down. @@ -1210,12 +1093,25 @@ impl Term { #[inline] fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) { trace!("Scrolling down relative: origin={}, lines={}", origin, lines); + + let num_lines = self.grid.num_lines(); + let num_cols = self.grid.num_cols(); + lines = min(lines, self.scroll_region.end - self.scroll_region.start); lines = min(lines, self.scroll_region.end - origin); + let region = origin..self.scroll_region.end; + let absolute_region = (num_lines - region.end)..(num_lines - region.start); + + // Scroll selection. + self.selection = self + .selection + .take() + .and_then(|s| s.rotate(num_lines, num_cols, &absolute_region, -(lines.0 as isize))); + // Scroll between origin and bottom - let template = Cell { bg: self.cursor.template.bg, ..Cell::default() }; - self.grid.scroll_down(&(origin..self.scroll_region.end), lines, &template); + let template = Cell { bg: self.grid.cursor.template.bg, ..Cell::default() }; + self.grid.scroll_down(®ion, lines, template); } /// Scroll screen up @@ -1223,13 +1119,25 @@ impl Term { /// Text moves up; clear at top /// Expects origin to be in scroll range. #[inline] - fn scroll_up_relative(&mut self, origin: Line, lines: Line) { + fn scroll_up_relative(&mut self, origin: Line, mut lines: Line) { trace!("Scrolling up relative: origin={}, lines={}", origin, lines); - let lines = min(lines, self.scroll_region.end - self.scroll_region.start); + let num_lines = self.grid.num_lines(); + let num_cols = self.grid.num_cols(); + + lines = min(lines, self.scroll_region.end - self.scroll_region.start); + + let region = origin..self.scroll_region.end; + let absolute_region = (num_lines - region.end)..(num_lines - region.start); + + // Scroll selection. + self.selection = self + .selection + .take() + .and_then(|s| s.rotate(num_lines, num_cols, &absolute_region, lines.0 as isize)); // Scroll from origin to bottom less number of lines. - let template = Cell { bg: self.cursor.template.bg, ..Cell::default() }; - self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template); + let template = Cell { bg: self.grid.cursor.template.bg, ..Cell::default() }; + self.grid.scroll_up(®ion, lines, template); } fn deccolm(&mut self) @@ -1241,7 +1149,7 @@ impl Term { self.set_scrolling_region(1, self.grid.num_lines().0); // Clear grid. - let template = self.cursor.template; + let template = self.grid.cursor.template; self.grid.region_mut(..).each(|c| c.reset(&template)); } @@ -1267,12 +1175,13 @@ impl Term { #[inline] pub fn toggle_vi_mode(&mut self) { self.mode ^= TermMode::VI; - self.grid.selection = None; + self.selection = None; // Reset vi mode cursor position to match primary cursor. if self.mode.contains(TermMode::VI) { - let line = min(self.cursor.point.line + self.grid.display_offset(), self.lines() - 1); - self.vi_mode_cursor = ViModeCursor::new(Point::new(line, self.cursor.point.col)); + let cursor = self.grid.cursor.point; + let line = min(cursor.line + self.grid.display_offset(), self.lines() - 1); + self.vi_mode_cursor = ViModeCursor::new(Point::new(line, cursor.col)); } self.dirty = true; @@ -1294,8 +1203,8 @@ impl Term { // Update selection if one is active. let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point); - if let Some(selection) = &mut self.grid.selection { - // Do not extend empty selections started by single mouse click. + if let Some(selection) = &mut self.selection { + // Do not extend empty selections started by a single mouse click. if !selection.is_empty() { selection.update(viewport_point, Side::Left); selection.include_all(); @@ -1322,15 +1231,15 @@ impl Term { trace!("Wrapping input"); - self.grid[&self.cursor.point].flags.insert(Flags::WRAPLINE); + self.grid.cursor_cell().flags.insert(Flags::WRAPLINE); - if (self.cursor.point.line + 1) >= self.scroll_region.end { + if (self.grid.cursor.point.line + 1) >= self.scroll_region.end { self.linefeed(); } else { - self.cursor.point.line += 1; + self.grid.cursor.point.line += 1; } - self.cursor.point.col = Column(0); + self.grid.cursor.point.col = Column(0); self.input_needs_wrap = false; } @@ -1340,10 +1249,13 @@ impl Term { where T: EventListener, { - let cell = &mut self.grid[&self.cursor.point]; - *cell = self.cursor.template; - cell.c = self.cursor.charsets[self.active_charset].map(c); - cell + let mut cell = self.grid.cursor.template; + cell.c = self.grid.cursor.charsets[self.active_charset].map(c); + + let cursor_cell = self.grid.cursor_cell(); + *cursor_cell = cell; + + cursor_cell } /// Get rendering information about the active cursor. @@ -1354,7 +1266,7 @@ impl Term { let mut point = if vi_mode { self.vi_mode_cursor.point } else { - let mut point = self.cursor.point; + let mut point = self.grid.cursor.point; point.line += self.grid.display_offset(); point }; @@ -1430,8 +1342,8 @@ impl Handler for Term { // Handle zero-width characters. if width == 0 { - let mut col = self.cursor.point.col.0.saturating_sub(1); - let line = self.cursor.point.line; + let mut col = self.grid.cursor.point.col.0.saturating_sub(1); + let line = self.grid.cursor.point.line; if self.grid[line][Column(col)].flags.contains(Flags::WIDE_CHAR_SPACER) { col = col.saturating_sub(1); } @@ -1447,9 +1359,9 @@ impl Handler for Term { let num_cols = self.grid.num_cols(); // If in insert mode, first shift cells to the right. - if self.mode.contains(TermMode::INSERT) && self.cursor.point.col + width < num_cols { - let line = self.cursor.point.line; - let col = self.cursor.point.col; + if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.col + width < num_cols { + let line = self.grid.cursor.point.line; + let col = self.grid.cursor.point.col; let line = &mut self.grid[line]; let src = line[col..].as_ptr(); @@ -1462,7 +1374,7 @@ impl Handler for Term { if width == 1 { self.write_at_cursor(c); } else { - if self.cursor.point.col + 1 >= num_cols { + if self.grid.cursor.point.col + 1 >= num_cols { if self.mode.contains(TermMode::LINE_WRAP) { // Insert placeholder before wide char if glyph does not fit in this row. self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER); @@ -1478,12 +1390,12 @@ impl Handler for Term { self.write_at_cursor(c).flags.insert(Flags::WIDE_CHAR); // Write spacer to cell following the wide glyph. - self.cursor.point.col += 1; + self.grid.cursor.point.col += 1; self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER); } - if self.cursor.point.col + 1 < num_cols { - self.cursor.point.col += 1; + if self.grid.cursor.point.col + 1 < num_cols { + self.grid.cursor.point.col += 1; } else { self.input_needs_wrap = true; } @@ -1506,34 +1418,35 @@ impl Handler for Term { (Line(0), self.grid.num_lines() - 1) }; - self.cursor.point.line = min(line + y_offset, max_y); - self.cursor.point.col = min(col, self.grid.num_cols() - 1); + self.grid.cursor.point.line = min(line + y_offset, max_y); + self.grid.cursor.point.col = min(col, self.grid.num_cols() - 1); self.input_needs_wrap = false; } #[inline] fn goto_line(&mut self, line: Line) { trace!("Going to line: {}", line); - self.goto(line, self.cursor.point.col) + self.goto(line, self.grid.cursor.point.col) } #[inline] fn goto_col(&mut self, col: Column) { trace!("Going to column: {}", col); - self.goto(self.cursor.point.line, col) + self.goto(self.grid.cursor.point.line, col) } #[inline] fn insert_blank(&mut self, count: Column) { - // Ensure inserting within terminal bounds. + let cursor = self.grid.cursor; - let count = min(count, self.grid.num_cols() - self.cursor.point.col); + // Ensure inserting within terminal bounds + let count = min(count, self.grid.num_cols() - cursor.point.col); - let source = self.cursor.point.col; - let destination = self.cursor.point.col + count; + let source = cursor.point.col; + let destination = cursor.point.col + count; let num_cells = (self.grid.num_cols() - destination).0; - let line = &mut self.grid[self.cursor.point.line]; + let line = &mut self.grid[cursor.point.line]; unsafe { let src = line[source..].as_ptr(); @@ -1545,35 +1458,36 @@ impl Handler for Term { // Cells were just moved out towards the end of the line; fill in // between source and dest with blanks. for c in &mut line[source..destination] { - c.reset(&self.cursor.template); + c.reset(&cursor.template); } } #[inline] fn move_up(&mut self, lines: Line) { trace!("Moving up: {}", lines); - let move_to = Line(self.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, self.cursor.point.col) + let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0)); + self.goto(move_to, self.grid.cursor.point.col) } #[inline] fn move_down(&mut self, lines: Line) { trace!("Moving down: {}", lines); - let move_to = self.cursor.point.line + lines; - self.goto(move_to, self.cursor.point.col) + let move_to = self.grid.cursor.point.line + lines; + self.goto(move_to, self.grid.cursor.point.col) } #[inline] fn move_forward(&mut self, cols: Column) { trace!("Moving forward: {}", cols); - self.cursor.point.col = min(self.cursor.point.col + cols, self.grid.num_cols() - 1); + let num_cols = self.grid.num_cols(); + self.grid.cursor.point.col = min(self.grid.cursor.point.col + cols, num_cols - 1); self.input_needs_wrap = false; } #[inline] fn move_backward(&mut self, cols: Column) { trace!("Moving backward: {}", cols); - self.cursor.point.col -= min(self.cursor.point.col, cols); + self.grid.cursor.point.col = Column(self.grid.cursor.point.col.saturating_sub(cols.0)); self.input_needs_wrap = false; } @@ -1591,7 +1505,7 @@ impl Handler for Term { let _ = writer.write_all(b"\x1b[0n"); }, 6 => { - let pos = self.cursor.point; + let pos = self.grid.cursor.point; let response = format!("\x1b[{};{}R", pos.line + 1, pos.col + 1); let _ = writer.write_all(response.as_bytes()); }, @@ -1602,14 +1516,14 @@ impl Handler for Term { #[inline] fn move_down_and_cr(&mut self, lines: Line) { trace!("Moving down and cr: {}", lines); - let move_to = self.cursor.point.line + lines; + let move_to = self.grid.cursor.point.line + lines; self.goto(move_to, Column(0)) } #[inline] fn move_up_and_cr(&mut self, lines: Line) { trace!("Moving up and cr: {}", lines); - let move_to = Line(self.cursor.point.line.0.saturating_sub(lines.0)); + let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0)); self.goto(move_to, Column(0)) } @@ -1622,22 +1536,23 @@ impl Handler for Term { return; } - while self.cursor.point.col < self.grid.num_cols() && count != 0 { + while self.grid.cursor.point.col < self.grid.num_cols() && count != 0 { count -= 1; - let cell = &mut self.grid[&self.cursor.point]; + let c = self.grid.cursor.charsets[self.active_charset].map('\t'); + let cell = self.grid.cursor_cell(); if cell.c == ' ' { - cell.c = self.cursor.charsets[self.active_charset].map('\t'); + cell.c = c; } loop { - if (self.cursor.point.col + 1) == self.grid.num_cols() { + if (self.grid.cursor.point.col + 1) == self.grid.num_cols() { break; } - self.cursor.point.col += 1; + self.grid.cursor.point.col += 1; - if self.tabs[self.cursor.point.col] { + if self.tabs[self.grid.cursor.point.col] { break; } } @@ -1648,8 +1563,9 @@ impl Handler for Term { #[inline] fn backspace(&mut self) { trace!("Backspace"); - if self.cursor.point.col > Column(0) { - self.cursor.point.col -= 1; + + if self.grid.cursor.point.col > Column(0) { + self.grid.cursor.point.col -= 1; self.input_needs_wrap = false; } } @@ -1658,7 +1574,7 @@ impl Handler for Term { #[inline] fn carriage_return(&mut self) { trace!("Carriage return"); - self.cursor.point.col = Column(0); + self.grid.cursor.point.col = Column(0); self.input_needs_wrap = false; } @@ -1666,11 +1582,11 @@ impl Handler for Term { #[inline] fn linefeed(&mut self) { trace!("Linefeed"); - let next = self.cursor.point.line + 1; + let next = self.grid.cursor.point.line + 1; if next == self.scroll_region.end { self.scroll_up(Line(1)); } else if next < self.grid.num_lines() { - self.cursor.point.line += 1; + self.grid.cursor.point.line += 1; } } @@ -1721,8 +1637,7 @@ impl Handler for Term { #[inline] fn set_horizontal_tabstop(&mut self) { trace!("Setting horizontal tabstop"); - let column = self.cursor.point.col; - self.tabs[column] = true; + self.tabs[self.grid.cursor.point.col] = true; } #[inline] @@ -1740,49 +1655,54 @@ impl Handler for Term { #[inline] fn insert_blank_lines(&mut self, lines: Line) { trace!("Inserting blank {} lines", lines); - if self.scroll_region.contains(&self.cursor.point.line) { - let origin = self.cursor.point.line; + + let origin = self.grid.cursor.point.line; + if self.scroll_region.contains(&origin) { self.scroll_down_relative(origin, lines); } } #[inline] fn delete_lines(&mut self, lines: Line) { - let origin = self.cursor.point.line; + let origin = self.grid.cursor.point.line; let lines = min(self.lines() - origin, lines); trace!("Deleting {} lines", lines); - if lines.0 > 0 && self.scroll_region.contains(&self.cursor.point.line) { + if lines.0 > 0 && self.scroll_region.contains(&self.grid.cursor.point.line) { self.scroll_up_relative(origin, lines); } } #[inline] fn erase_chars(&mut self, count: Column) { - trace!("Erasing chars: count={}, col={}", count, self.cursor.point.col); - let start = self.cursor.point.col; + let cursor = self.grid.cursor; + + trace!("Erasing chars: count={}, col={}", count, cursor.point.col); + + let start = cursor.point.col; let end = min(start + count, self.grid.num_cols()); - let row = &mut self.grid[self.cursor.point.line]; // Cleared cells have current background color set. + let row = &mut self.grid[cursor.point.line]; for c in &mut row[start..end] { - c.reset(&self.cursor.template); + c.reset(&cursor.template); } } #[inline] fn delete_chars(&mut self, count: Column) { let cols = self.grid.num_cols(); + let cursor = self.grid.cursor; // Ensure deleting within terminal bounds. let count = min(count, cols); - let start = self.cursor.point.col; + let start = cursor.point.col; let end = min(start + count, cols - 1); let n = (cols - end).0; - let line = &mut self.grid[self.cursor.point.line]; + let line = &mut self.grid[cursor.point.line]; unsafe { let src = line[end..].as_ptr(); @@ -1795,7 +1715,7 @@ impl Handler for Term { // 1 cell. let end = cols - count; for c in &mut line[end..] { - c.reset(&self.cursor.template); + c.reset(&cursor.template); } } @@ -1804,14 +1724,14 @@ impl Handler for Term { trace!("Moving backward {} tabs", count); for _ in 0..count { - let mut col = self.cursor.point.col; + let mut col = self.grid.cursor.point.col; for i in (0..(col.0)).rev() { if self.tabs[index::Column(i)] { col = index::Column(i); break; } } - self.cursor.point.col = col; + self.grid.cursor.point.col = col; } } @@ -1823,44 +1743,39 @@ impl Handler for Term { #[inline] fn save_cursor_position(&mut self) { trace!("Saving cursor position"); - let cursor = if self.alt { &mut self.cursor_save_alt } else { &mut self.cursor_save }; - *cursor = self.cursor; + self.grid.saved_cursor = self.grid.cursor; } #[inline] fn restore_cursor_position(&mut self) { trace!("Restoring cursor position"); - let source = if self.alt { &self.cursor_save_alt } else { &self.cursor_save }; - self.cursor = *source; - self.cursor.point.line = min(self.cursor.point.line, self.grid.num_lines() - 1); - self.cursor.point.col = min(self.cursor.point.col, self.grid.num_cols() - 1); + self.grid.cursor = self.grid.saved_cursor; } #[inline] fn clear_line(&mut self, mode: ansi::LineClearMode) { trace!("Clearing line: {:?}", mode); - let col = self.cursor.point.col; - + let cursor = self.grid.cursor; match mode { ansi::LineClearMode::Right => { - let row = &mut self.grid[self.cursor.point.line]; - for cell in &mut row[col..] { - cell.reset(&self.cursor.template); + let row = &mut self.grid[cursor.point.line]; + for cell in &mut row[cursor.point.col..] { + cell.reset(&cursor.template); } }, ansi::LineClearMode::Left => { - let row = &mut self.grid[self.cursor.point.line]; - for cell in &mut row[..=col] { - cell.reset(&self.cursor.template); + let row = &mut self.grid[cursor.point.line]; + for cell in &mut row[..=cursor.point.col] { + cell.reset(&cursor.template); } }, ansi::LineClearMode::All => { - let row = &mut self.grid[self.cursor.point.line]; + let row = &mut self.grid[cursor.point.line]; for cell in &mut row[..] { - cell.reset(&self.cursor.template); + cell.reset(&cursor.template); } }, } @@ -1934,34 +1849,34 @@ impl Handler for Term { #[inline] fn clear_screen(&mut self, mode: ansi::ClearMode) { trace!("Clearing screen: {:?}", mode); - let template = self.cursor.template; + let template = self.grid.cursor.template; // Remove active selections. - self.grid.selection = None; + self.selection = None; match mode { ansi::ClearMode::Above => { + let cursor = self.grid.cursor.point; + // If clearing more than one line. - if self.cursor.point.line > Line(1) { + if cursor.line > Line(1) { // Fully clear all lines before the current line. - self.grid - .region_mut(..self.cursor.point.line) - .each(|cell| cell.reset(&template)); + self.grid.region_mut(..cursor.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()); - for cell in &mut self.grid[self.cursor.point.line][..end] { + let end = min(cursor.col + 1, self.grid.num_cols()); + for cell in &mut self.grid[cursor.line][..end] { cell.reset(&template); } }, ansi::ClearMode::Below => { - for cell in &mut self.grid[self.cursor.point.line][self.cursor.point.col..] { + let cursor = self.grid.cursor.point; + for cell in &mut self.grid[cursor.line][cursor.col..] { cell.reset(&template); } - if self.cursor.point.line < self.grid.num_lines() - 1 { - self.grid - .region_mut((self.cursor.point.line + 1)..) - .each(|cell| cell.reset(&template)); + if cursor.line < self.grid.num_lines() - 1 { + self.grid.region_mut((cursor.line + 1)..).each(|cell| cell.reset(&template)); } }, ansi::ClearMode::All => { @@ -1969,7 +1884,7 @@ impl Handler for Term { self.grid.region_mut(..).each(|c| c.reset(&template)); } else { let template = Cell { bg: template.bg, ..Cell::default() }; - self.grid.clear_viewport(&template); + self.grid.clear_viewport(template); } }, ansi::ClearMode::Saved => self.grid.clear_history(), @@ -1981,8 +1896,7 @@ impl Handler for Term { trace!("Clearing tabs: {:?}", mode); match mode { ansi::TabulationClearMode::Current => { - let column = self.cursor.point.col; - self.tabs[column] = false; + self.tabs[self.grid.cursor.point.col] = false; }, ansi::TabulationClearMode::All => { self.tabs.clear_all(); @@ -1997,30 +1911,28 @@ impl Handler for Term { self.swap_alt(); } self.input_needs_wrap = false; - self.cursor = Default::default(); self.active_charset = Default::default(); self.mode = Default::default(); - self.cursor_save = Default::default(); - self.cursor_save_alt = Default::default(); self.colors = self.original_colors; self.color_modified = [false; color::COUNT]; self.cursor_style = None; - self.grid.reset(&Cell::default()); - self.alt_grid.reset(&Cell::default()); + self.grid.reset(Cell::default()); + self.alt_grid.reset(Cell::default()); self.scroll_region = Line(0)..self.grid.num_lines(); self.tabs = TabStops::new(self.grid.num_cols()); self.title_stack = Vec::new(); self.title = None; + self.selection = None; } #[inline] fn reverse_index(&mut self) { trace!("Reversing index"); - // If cursor is at the top. - if self.cursor.point.line == self.scroll_region.start { + + if self.grid.cursor.point.line == self.scroll_region.start { self.scroll_down(Line(1)); } else { - self.cursor.point.line -= min(self.cursor.point.line, Line(1)); + self.grid.cursor.point.line = Line(self.grid.cursor.point.line.saturating_sub(1)); } } @@ -2028,28 +1940,29 @@ impl Handler for Term { #[inline] fn terminal_attribute(&mut self, attr: Attr) { trace!("Setting attribute: {:?}", attr); + let cursor = &mut self.grid.cursor; match attr { - Attr::Foreground(color) => self.cursor.template.fg = color, - Attr::Background(color) => self.cursor.template.bg = color, + Attr::Foreground(color) => cursor.template.fg = color, + Attr::Background(color) => cursor.template.bg = color, Attr::Reset => { - self.cursor.template.fg = Color::Named(NamedColor::Foreground); - self.cursor.template.bg = Color::Named(NamedColor::Background); - self.cursor.template.flags = Flags::empty(); + cursor.template.fg = Color::Named(NamedColor::Foreground); + cursor.template.bg = Color::Named(NamedColor::Background); + cursor.template.flags = Flags::empty(); }, - Attr::Reverse => self.cursor.template.flags.insert(Flags::INVERSE), - Attr::CancelReverse => self.cursor.template.flags.remove(Flags::INVERSE), - Attr::Bold => self.cursor.template.flags.insert(Flags::BOLD), - Attr::CancelBold => self.cursor.template.flags.remove(Flags::BOLD), - Attr::Dim => self.cursor.template.flags.insert(Flags::DIM), - Attr::CancelBoldDim => self.cursor.template.flags.remove(Flags::BOLD | Flags::DIM), - Attr::Italic => self.cursor.template.flags.insert(Flags::ITALIC), - Attr::CancelItalic => self.cursor.template.flags.remove(Flags::ITALIC), - Attr::Underline => self.cursor.template.flags.insert(Flags::UNDERLINE), - Attr::CancelUnderline => self.cursor.template.flags.remove(Flags::UNDERLINE), - Attr::Hidden => self.cursor.template.flags.insert(Flags::HIDDEN), - Attr::CancelHidden => self.cursor.template.flags.remove(Flags::HIDDEN), - Attr::Strike => self.cursor.template.flags.insert(Flags::STRIKEOUT), - Attr::CancelStrike => self.cursor.template.flags.remove(Flags::STRIKEOUT), + Attr::Reverse => cursor.template.flags.insert(Flags::INVERSE), + Attr::CancelReverse => cursor.template.flags.remove(Flags::INVERSE), + Attr::Bold => cursor.template.flags.insert(Flags::BOLD), + Attr::CancelBold => cursor.template.flags.remove(Flags::BOLD), + Attr::Dim => cursor.template.flags.insert(Flags::DIM), + Attr::CancelBoldDim => cursor.template.flags.remove(Flags::BOLD | Flags::DIM), + Attr::Italic => cursor.template.flags.insert(Flags::ITALIC), + Attr::CancelItalic => cursor.template.flags.remove(Flags::ITALIC), + Attr::Underline => cursor.template.flags.insert(Flags::UNDERLINE), + Attr::CancelUnderline => cursor.template.flags.remove(Flags::UNDERLINE), + Attr::Hidden => cursor.template.flags.insert(Flags::HIDDEN), + Attr::CancelHidden => cursor.template.flags.remove(Flags::HIDDEN), + Attr::Strike => cursor.template.flags.insert(Flags::STRIKEOUT), + Attr::CancelStrike => cursor.template.flags.remove(Flags::STRIKEOUT), _ => { debug!("Term got unhandled attr: {:?}", attr); }, @@ -2187,7 +2100,7 @@ impl Handler for Term { #[inline] fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { trace!("Configuring charset {:?} as {:?}", index, charset); - self.cursor.charsets[index] = charset; + self.grid.cursor.charsets[index] = charset; } #[inline] @@ -2337,7 +2250,7 @@ mod tests { mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); { - *term.selection_mut() = Some(Selection::new( + term.selection = Some(Selection::new( SelectionType::Semantic, Point { line: 2, col: Column(1) }, Side::Left, @@ -2346,7 +2259,7 @@ mod tests { } { - *term.selection_mut() = Some(Selection::new( + term.selection = Some(Selection::new( SelectionType::Semantic, Point { line: 2, col: Column(4) }, Side::Left, @@ -2355,7 +2268,7 @@ mod tests { } { - *term.selection_mut() = Some(Selection::new( + term.selection = Some(Selection::new( SelectionType::Semantic, Point { line: 1, col: Column(1) }, Side::Left, @@ -2385,7 +2298,7 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - *term.selection_mut() = Some(Selection::new( + term.selection = Some(Selection::new( SelectionType::Lines, Point { line: 0, col: Column(3) }, Side::Left, @@ -2419,7 +2332,7 @@ mod tests { let mut selection = Selection::new(SelectionType::Simple, Point { line: 2, col: Column(0) }, Side::Left); selection.update(Point { line: 0, col: Column(2) }, Side::Right); - *term.selection_mut() = Some(selection); + term.selection = Some(selection); assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into())); } @@ -2471,7 +2384,7 @@ mod tests { let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); // Add one line of scrollback. - term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); + term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), Cell::default()); // Clear the history. term.clear_screen(ansi::ClearMode::Saved); @@ -2505,14 +2418,14 @@ mod tests { term.newline(); } assert_eq!(term.grid.history_size(), 10); - assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Increase visible lines. size.height = 30.; term.resize(&size); assert_eq!(term.grid.history_size(), 0); - assert_eq!(term.cursor.point, Point::new(Line(19), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(19), Column(0))); } #[test] @@ -2533,7 +2446,7 @@ mod tests { term.newline(); } assert_eq!(term.grid.history_size(), 10); - assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Enter alt screen. term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); @@ -2546,7 +2459,7 @@ mod tests { term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); assert_eq!(term.grid().history_size(), 0); - assert_eq!(term.cursor.point, Point::new(Line(19), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(19), Column(0))); } #[test] @@ -2567,14 +2480,14 @@ mod tests { term.newline(); } assert_eq!(term.grid.history_size(), 10); - assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Increase visible lines. size.height = 5.; term.resize(&size); assert_eq!(term.grid().history_size(), 15); - assert_eq!(term.cursor.point, Point::new(Line(4), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(4), Column(0))); } #[test] @@ -2595,7 +2508,7 @@ mod tests { term.newline(); } assert_eq!(term.grid.history_size(), 10); - assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Enter alt screen. term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); @@ -2608,7 +2521,7 @@ mod tests { term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); assert_eq!(term.grid().history_size(), 15); - assert_eq!(term.cursor.point, Point::new(Line(4), Column(0))); + assert_eq!(term.grid.cursor.point, Point::new(Line(4), Column(0))); } #[test] diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index 7fa6c14..650c3a8 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -385,11 +385,10 @@ fn last_occupied_in_line(term: &Term, line: usize) -> Option> /// Advance point based on direction. fn advance(term: &Term, point: Point, left: bool) -> Point { - let cols = term.grid().num_cols(); if left { - point.sub_absolute(cols.0, 1) + point.sub_absolute(term.grid().num_cols(), 1) } else { - point.add_absolute(cols.0, 1) + point.add_absolute(term.grid().num_cols(), 1) } } @@ -679,7 +678,7 @@ mod tests { #[test] fn scroll_semantic() { let mut term = term(); - term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5), &Default::default()); + term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5), Default::default()); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); @@ -755,7 +754,7 @@ mod tests { #[test] fn scroll_word() { let mut term = term(); - term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5), &Default::default()); + term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5), Default::default()); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index 99a9db5..41959b2 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -107,7 +107,7 @@ fn ref_test(dir: &Path) { // Truncate invisible lines from the grid. let mut term_grid = terminal.grid().clone(); - term_grid.initialize_all(&Cell::default()); + term_grid.initialize_all(Cell::default()); term_grid.truncate(); if grid != term_grid {