summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--alacritty/src/display.rs4
-rw-r--r--alacritty/src/event.rs14
-rw-r--r--alacritty/src/input.rs2
-rw-r--r--alacritty/src/url.rs14
-rw-r--r--alacritty/src/window.rs2
-rw-r--r--alacritty_terminal/src/ansi.rs45
-rw-r--r--alacritty_terminal/src/grid/mod.rs377
-rw-r--r--alacritty_terminal/src/grid/resize.rs274
-rw-r--r--alacritty_terminal/src/grid/row.rs12
-rw-r--r--alacritty_terminal/src/grid/storage.rs225
-rw-r--r--alacritty_terminal/src/grid/tests.rs22
-rw-r--r--alacritty_terminal/src/index.rs135
-rw-r--r--alacritty_terminal/src/selection.rs100
-rw-r--r--alacritty_terminal/src/term/cell.rs4
-rw-r--r--alacritty_terminal/src/term/mod.rs509
-rw-r--r--alacritty_terminal/src/vi_mode.rs9
-rw-r--r--alacritty_terminal/tests/ref.rs2
18 files changed, 932 insertions, 820 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc9dbd16..f9e59e2f 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 13e0454a..5e0747f3 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 e11903ef..0de130b9 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -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");
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 6f1a71aa..01c43ecc 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 72452a9e..f7c7105c 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<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);
diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs
index 155b2aa2..100993b2 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<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);
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 01f48358..0b196ad5 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 71545dca..a136b1b5 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<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> {
diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs
new file mode 100644
index 00000000..796c5859
--- /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<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);
+ }
+}
diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs
index 22a10625..b5248ad7 100644
--- a/alacritty_terminal/src/grid/row.rs
+++ b/alacritty_terminal/src/grid/row.rs
@@ -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) {
diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs
index 4820036f..4b7ca41a 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<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),
diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs
index ef011d16..04400afc 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 72e7158a..a5f2bc76 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -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));
+ }
}
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index 7f5c21ec..5b05310c 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<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,
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs
index c23b3599..f95ed0c3 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 d5759421..bcb4853f 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<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]
- }
-}
-
-#[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<T> {
- /// 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<Selection>,
+
/// The grid.
grid: Grid<Cell>,
@@ -822,12 +756,6 @@ pub struct Term<T> {
/// 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<T> {
/// Range going from top to bottom of the terminal, indexed from the top of the viewport.
scroll_region: Range<Line>,
- 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<T> {
}
impl<T> Term<T> {
- pub fn selection(&self) -> &Option<Selection> {
- &self.grid.selection
- }
-
- pub fn selection_mut(&mut self) -> &mut Option<Selection> {
- &mut self.grid.selection
- }
-
#[inline]
pub fn scroll_display(&mut self, scroll: Scroll)
where
@@ -938,10 +848,7 @@ impl<T> Term<T> {
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<T> Term<T> {
title: None,
default_title: config.window.title.clone(),
title_stack: Vec::new(),
+ selection: None,
}
}
@@ -1000,8 +908,8 @@ impl<T> Term<T> {
/// Convert the active selection to a String.
pub fn selection_to_string(&self) -> Option<String> {
- 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<T> Term<T> {
/// 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<C>) -> 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<T> Term<T> {
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<T> Term<T> {
&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<T> Term<T> {
#[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(&region, lines, template);
}
/// Scroll screen up
@@ -1223,13 +1119,25 @@ impl<T> Term<T> {
/// 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(&region, lines, template);
}
fn deccolm(&mut self)
@@ -1241,7 +1149,7 @@ impl<T> Term<T> {
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<T> Term<T> {
#[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<T> Term<T> {
// 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<T> Term<T> {
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<T> Term<T> {
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<T> Term<T> {
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<T: EventListener> Handler for Term<T> {
// 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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
(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<T: EventListener> Handler for Term<T> {
// 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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
// 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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
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<T: EventListener> Handler for Term<T> {
#[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<T: EventListener> Handler for Term<T> {
#[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 7fa6c148..650c3a8a 100644
--- a/alacritty_terminal/src/vi_mode.rs
+++ b/alacritty_terminal/src/vi_mode.rs
@@ -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)));
diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs
index 99a9db54..41959b2f 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 {