aboutsummaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/grid
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal/src/grid')
-rw-r--r--alacritty_terminal/src/grid/mod.rs248
-rw-r--r--alacritty_terminal/src/grid/storage.rs9
-rw-r--r--alacritty_terminal/src/grid/tests.rs8
3 files changed, 163 insertions, 102 deletions
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 87de1d7a..53f7ebea 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -19,8 +19,9 @@ use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};
use serde::{Deserialize, Serialize};
-use crate::index::{self, Column, IndexRange, Line, Point};
+use crate::index::{Column, IndexRange, Line, Point};
use crate::selection::Selection;
+use crate::term::cell::Flags;
mod row;
pub use self::row::Row;
@@ -68,8 +69,8 @@ impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
pub trait GridCell {
fn is_empty(&self) -> bool;
- fn is_wrap(&self) -> bool;
- fn set_wrap(&mut self, wrap: bool);
+ fn flags(&self) -> &Flags;
+ fn flags_mut(&mut self) -> &mut Flags;
/// Fast equality approximation.
///
@@ -112,10 +113,10 @@ pub struct Grid<T> {
raw: Storage<T>,
/// Number of columns
- cols: index::Column,
+ cols: Column,
/// Number of visible lines.
- lines: index::Line,
+ lines: Line,
/// Offset of displayed area
///
@@ -144,7 +145,7 @@ pub enum Scroll {
}
impl<T: GridCell + PartialEq + Copy> Grid<T> {
- pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> 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,
@@ -213,8 +214,8 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
pub fn resize(
&mut self,
reflow: bool,
- lines: index::Line,
- cols: index::Column,
+ lines: Line,
+ cols: Column,
cursor_pos: &mut Point,
template: &T,
) {
@@ -259,7 +260,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// 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: index::Line, template: &T) {
+ fn grow_lines(&mut self, new_line_count: Line, template: &T) {
let lines_added = new_line_count - self.lines;
// Need to "resize" before updating buffer
@@ -276,107 +277,173 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.display_offset = self.display_offset.saturating_sub(*lines_added);
}
- fn grow_cols(
- &mut self,
- reflow: bool,
- cols: index::Column,
- cursor_pos: &mut Point,
- template: &T,
- ) {
+ // 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 new_raw: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
+ let mut reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
for (i, mut row) in self.raw.drain().enumerate().rev() {
- if let Some(last_row) = new_raw.last_mut() {
- // Grow the current line if there's wrapped content available
- if reflow
- && last_row.len() < cols.0
- && last_row.last().map(GridCell::is_wrap) == Some(true)
- {
- // Remove wrap flag before appending additional cells
- if let Some(cell) = last_row.last_mut() {
- cell.set_wrap(false);
- }
+ // FIXME: Rust 1.39.0+ allows moving in pattern guard here
+ // Check if reflowing shoud be performed
+ let mut last_row = reversed.last_mut();
+ let last_row = match last_row {
+ Some(ref mut 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);
+ }
- // Append as many cells from the next line as possible
- let len = min(row.len(), cols.0 - last_row.len());
- let mut cells = row.front_split_off(len);
- last_row.append(&mut cells);
-
- if row.is_empty() {
- let raw_len = i + 1 + new_raw.len();
- if raw_len < self.lines.0 || self.scroll_limit == 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 {
- // Make sure viewport doesn't move if line is outside of the visible
- // area
- if i < self.display_offset {
- self.display_offset = self.display_offset.saturating_sub(1);
- }
-
- // Remove one line from scrollback, since we just moved it to the
- // viewport
- self.scroll_limit = self.scroll_limit.saturating_sub(1);
- self.display_offset = min(self.display_offset, self.scroll_limit);
- }
-
- // 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.set_wrap(true);
+ // 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() {
+ let raw_len = i + 1 + reversed.len();
+ if raw_len < self.lines.0 || self.scroll_limit == 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 {
+ // Make sure viewport doesn't move if line is outside of the visible
+ // area
+ if i < self.display_offset {
+ self.display_offset = self.display_offset.saturating_sub(1);
}
+
+ // Remove one line from scrollback, since we just moved it to the
+ // viewport
+ self.scroll_limit = self.scroll_limit.saturating_sub(1);
+ self.display_offset = min(self.display_offset, self.scroll_limit);
}
+
+ // 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);
}
- new_raw.push(row);
+ reversed.push(row);
}
// Add padding lines
- new_raw.append(&mut vec![Row::new(cols, template); new_empty_lines]);
+ reversed.append(&mut vec![Row::new(cols, template); new_empty_lines]);
// Fill remaining cells and reverse iterator
- let mut reversed = Vec::with_capacity(new_raw.len());
- for mut row in new_raw.drain(..).rev() {
+ 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);
}
- reversed.push(row);
+ new_raw.push(row);
}
- self.raw.replace_inner(reversed);
+ self.raw.replace_inner(new_raw);
self.cols = cols;
}
- fn shrink_cols(&mut self, reflow: bool, cols: index::Column, template: &T) {
+ // 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);
}
- let mut wrapped = row.shrink(cols);
- new_raw.push(row);
+ loop {
+ // FIXME: Rust 1.39.0+ allows moving in pattern guard here
+ // Check if reflowing shoud be performed
+ let wrapped = row.shrink(cols);
+ let mut wrapped = match wrapped {
+ Some(_) if reflow => wrapped.unwrap(),
+ _ => {
+ 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);
- while let (Some(mut wrapped_cells), true) = (wrapped.take(), reflow) {
// Set line as wrapped if cells got removed
if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) {
- cell.set_wrap(true);
+ cell.flags_mut().insert(Flags::WRAPLINE);
}
- if Some(true) == wrapped_cells.last().map(|c| c.is_wrap() && i >= 1)
- && wrapped_cells.len() < cols.0
+ 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_cells.last_mut() {
- cell.set_wrap(false);
+ if let Some(cell) = wrapped.last_mut() {
+ cell.flags_mut().remove(Flags::WRAPLINE);
}
// Add removed cells to start of next row
- buffered = Some(wrapped_cells);
+ buffered = Some(wrapped);
+ break;
} else {
// Make sure viewport doesn't move if line is outside of the visible area
if i < self.display_offset {
@@ -384,17 +451,11 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
}
// Make sure new row is at least as long as new width
- let occ = wrapped_cells.len();
+ let occ = wrapped.len();
if occ < cols.0 {
- wrapped_cells.append(&mut vec![*template; cols.0 - occ]);
+ wrapped.append(&mut vec![*template; cols.0 - occ]);
}
- let mut row = Row::from_vec(wrapped_cells, occ);
-
- // Since inserted might exceed cols, we need to check it again
- wrapped = row.shrink(cols);
-
- // Add new row with all removed cells
- new_raw.push(row);
+ row = Row::from_vec(wrapped, occ);
// Increase scrollback history
self.scroll_limit = min(self.scroll_limit + 1, self.max_scroll_limit);
@@ -415,7 +476,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// of the terminal window.
///
/// Alacritty takes the same approach.
- fn shrink_lines(&mut self, target: index::Line) {
+ fn shrink_lines(&mut self, target: Line) {
let prev = self.lines;
self.selection = None;
@@ -429,19 +490,14 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// # Panics
///
/// This method will panic if `Line` is larger than the grid dimensions
- pub fn line_to_offset(&self, line: index::Line) -> usize {
+ pub fn line_to_offset(&self, line: Line) -> usize {
assert!(line < self.num_lines());
*(self.num_lines() - line - 1)
}
#[inline]
- pub fn scroll_down(
- &mut self,
- region: &Range<index::Line>,
- positions: index::Line,
- template: &T,
- ) {
+ 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.
@@ -482,7 +538,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// scroll_up moves lines at the bottom towards the top
///
/// This is the performance-sensitive part of scrolling.
- pub fn scroll_up(&mut self, region: &Range<index::Line>, positions: index::Line, template: &T) {
+ pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: &T) {
if region.start == Line(0) {
// Update display offset when not pinned to active area
if self.display_offset != 0 {
@@ -570,7 +626,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
#[allow(clippy::len_without_is_empty)]
impl<T> Grid<T> {
#[inline]
- pub fn num_lines(&self) -> index::Line {
+ pub fn num_lines(&self) -> Line {
self.lines
}
@@ -579,7 +635,7 @@ impl<T> Grid<T> {
}
#[inline]
- pub fn num_cols(&self) -> index::Column {
+ pub fn num_cols(&self) -> Column {
self.cols
}
@@ -693,11 +749,11 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
}
/// Index active region by line
-impl<T> Index<index::Line> for Grid<T> {
+impl<T> Index<Line> for Grid<T> {
type Output = Row<T>;
#[inline]
- fn index(&self, index: index::Line) -> &Row<T> {
+ fn index(&self, index: Line) -> &Row<T> {
&self.raw[index]
}
}
@@ -712,9 +768,9 @@ impl<T> Index<usize> for Grid<T> {
}
}
-impl<T> IndexMut<index::Line> for Grid<T> {
+impl<T> IndexMut<Line> for Grid<T> {
#[inline]
- fn index_mut(&mut self, index: index::Line) -> &mut Row<T> {
+ fn index_mut(&mut self, index: Line) -> &mut Row<T> {
&mut self.raw[index]
}
}
diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs
index 5d9339d5..3182da57 100644
--- a/alacritty_terminal/src/grid/storage.rs
+++ b/alacritty_terminal/src/grid/storage.rs
@@ -330,17 +330,20 @@ mod test {
use crate::grid::storage::Storage;
use crate::grid::GridCell;
use crate::index::{Column, Line};
+ use crate::term::cell::Flags;
impl GridCell for char {
fn is_empty(&self) -> bool {
*self == ' ' || *self == '\t'
}
- fn is_wrap(&self) -> bool {
- false
+ fn flags(&self) -> &Flags {
+ unimplemented!();
}
- fn set_wrap(&mut self, _wrap: bool) {}
+ fn flags_mut(&mut self) -> &mut Flags {
+ unimplemented!();
+ }
fn fast_eq(&self, other: Self) -> bool {
self == &other
diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs
index f3480b14..e4fdad5c 100644
--- a/alacritty_terminal/src/grid/tests.rs
+++ b/alacritty_terminal/src/grid/tests.rs
@@ -24,11 +24,13 @@ impl GridCell for usize {
*self == 0
}
- fn is_wrap(&self) -> bool {
- false
+ fn flags(&self) -> &Flags {
+ unimplemented!();
}
- fn set_wrap(&mut self, _wrap: bool) {}
+ fn flags_mut(&mut self) -> &mut Flags {
+ unimplemented!();
+ }
fn fast_eq(&self, other: Self) -> bool {
self == &other