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 {