Refactor Term/Grid separation

This commit aims to clear up the separation between Term and Grid to
make way for implementing search.

The `cursor` and `cursor_save` have been moved to the grid, since
they're always bound to their specific grid and this makes updating
easier.

Since the selection is independent of the active grid, it has been moved
to the `Term`.
This commit is contained in:
Christian Duerr 2020-05-30 20:45:44 +00:00 committed by GitHub
parent f7fb67f870
commit 1dacc99183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 932 additions and 820 deletions

View File

@ -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

View File

@ -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);

View File

@ -95,7 +95,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> 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<T> 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<T> 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<T> 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<N: Notify + OnResize> Processor<N> {
// 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");

View File

@ -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();
}
}

View File

@ -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<RenderLine>,
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);

View File

@ -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<T>(&mut self, terminal: &Term<T>, 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);

View File

@ -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,

View File

@ -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<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
&& 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<CharsetIndex> for Charsets {
type Output = StandardCharset;
fn index(&self, index: CharsetIndex) -> &StandardCharset {
&self.0[index as usize]
}
}
impl IndexMut<CharsetIndex> 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<T> {
/// 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<T>,
@ -122,10 +158,6 @@ pub struct Grid<T> {
/// updates this offset accordingly.
display_offset: usize,
/// Selected region.
#[serde(skip)]
pub selection: Option<Selection>,
/// Maximum number of lines in history.
max_scroll_limit: usize,
}
@ -139,10 +171,17 @@ pub enum Scroll {
Bottom,
}
impl<T: GridCell + PartialEq + Copy> Grid<T> {
pub fn new(lines: Line, cols: Column, scrollback: usize, template: T) -> Grid<T> {
let raw = Storage::with_capacity(lines, Row::new(cols, &template));
Grid { raw, cols, lines, display_offset: 0, selection: None, max_scroll_limit: scrollback }
impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
pub fn new(lines: Line, cols: Column, max_scroll_limit: usize, template: T) -> Grid<T> {
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<T: GridCell + PartialEq + Copy> Grid<T> {
}
}
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<T: GridCell + PartialEq + Copy> Grid<T> {
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<T>| -> 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<Row<T>> = 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<Row<T>> = 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<Line>, 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<Line>, 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<T: GridCell + PartialEq + Copy> Grid<T> {
// 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<T: GridCell + PartialEq + Copy> Grid<T> {
// 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<T: GridCell + PartialEq + Copy> Grid<T> {
/// Move lines at the bottom towards the top.
///
/// This is the performance-sensitive part of scrolling.
pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: &T) {
pub fn scroll_up(&mut self, region: &Range<Line>, 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<T: GridCell + PartialEq + Copy> Grid<T> {
// 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<T: GridCell + PartialEq + Copy> Grid<T> {
//
// 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<T: GridCell + PartialEq + Copy> Grid<T> {
// 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<T: GridCell + PartialEq + Copy> Grid<T> {
// 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<T: GridCell + PartialEq + Copy> Grid<T> {
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<T> Grid<T> {
/// 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<T> Grid<T> {
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> {

View File

@ -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<T: GridCell + Default + PartialEq + Copy> Grid<T> {
/// 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<T>| -> bool {
let len = Column(row.len());
reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE)
};
self.cols = cols;
let mut reversed: Vec<Row<T>> = 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<Vec<T>> = 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<Row<T>> = 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);
}
}

View File

@ -43,20 +43,20 @@ impl<T: PartialEq> PartialEq for Row<T> {
}
impl<T: Copy> Row<T> {
pub fn new(columns: Column, template: &T) -> Row<T>
pub fn new(columns: Column, template: T) -> Row<T>
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<Vec<T>>
@ -83,14 +83,12 @@ impl<T: Copy> Row<T> {
/// 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) {

View File

@ -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<T> Storage<T> {
/// 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<T> Storage<T> {
self.zero = (self.zero + count) % self.inner.len();
}
/// Drain all rows in the grid.
pub fn drain(&mut self) -> Drain<'_, Row<T>> {
self.truncate();
self.inner.drain(..)
}
/// Update the raw storage buffer.
#[inline]
pub fn replace_inner(&mut self, vec: Vec<Row<T>>) {
self.len = vec.len();
self.inner = vec;
self.zero = 0;
}
/// Remove all rows from storage.
#[inline]
pub fn take_all(&mut self) -> Vec<Row<T>> {
self.truncate();
let mut buffer = Vec::new();
mem::swap(&mut buffer, &mut self.inner);
self.zero = 0;
self.len = 0;
buffer
}
}
impl<T> Index<usize> for Storage<T> {
@ -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),

View File

@ -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);

View File

@ -53,27 +53,28 @@ impl<L> Point<L> {
#[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<L>
pub fn sub(mut self, num_cols: Column, rhs: usize) -> Point<L>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
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<L>
pub fn add(mut self, num_cols: Column, rhs: usize) -> Point<L>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
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<L> Point<L> {
#[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<L>
pub fn sub_absolute(mut self, num_cols: Column, rhs: usize) -> Point<L>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
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<L>
pub fn add_absolute(mut self, num_cols: Column, rhs: usize) -> Point<L>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
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));
}
}

View File

@ -106,20 +106,22 @@ impl Selection {
}
}
/// Update the end of the selection.
pub fn update(&mut self, point: Point<usize>, side: Side) {
self.region.end = Anchor::new(point, side);
}
pub fn rotate(
mut self,
num_lines: usize,
num_cols: usize,
scrolling_region: &Range<Line>,
offset: isize,
num_lines: Line,
num_cols: Column,
range: &Range<Line>,
delta: isize,
) -> Option<Selection> {
// 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,

View File

@ -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));

File diff suppressed because it is too large Load Diff

View File

@ -385,11 +385,10 @@ fn last_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>>
/// Advance point based on direction.
fn advance<T>(term: &Term<T>, point: Point<usize>, left: bool) -> Point<usize> {
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)));

View File

@ -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 {