summaryrefslogtreecommitdiffstats
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2020-07-09 02:16:20 +0000
committerGitHub <noreply@github.com>2020-07-09 05:16:20 +0300
commit9974bc8baa45fda0b4ba3db2ae615fb7f90f7029 (patch)
tree36647597bc3b8f8c805079fc38e17e5ef0dddd69 /alacritty_terminal
parent65bff1878ff68698da021b4d5aa00d1cfad41d0a (diff)
Fix cursor reflow
To make sure that output is consistent even while resizing the window, the cursor will now reflow with the content whenever the window size is changed. Since the saved cursor is more likely to represent a position in the grid rather than a reference to the content below it and handling of resize before jumping back to it is more likely than with the primary cursor, no reflow is performed for the saved cursor The primary cursor is unfortunately always reflowed automatically by shells like zsh, which has always caused problems like duplicating parts of the prompt and stretching it out "infinitely". Since the cursor is now reflowed appropriately the duplication of the shell prompt should be reduced, however it is possible that the shell moves the cursor up one line after it has already been reflowed, which will cause a line of history to be deleted if there is no duplicated prompt line above the reflowed prompt. Since this behavior is identical in VTE and Kitty, no attempt is made to work around it in this patch. Fixes #3584.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/src/grid/resize.rs119
1 files changed, 91 insertions, 28 deletions
diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs
index 95a8c2fc..a0493fc0 100644
--- a/alacritty_terminal/src/grid/resize.rs
+++ b/alacritty_terminal/src/grid/resize.rs
@@ -83,13 +83,19 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<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)
+ reflow && len.0 > 0 && 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 cursor_line_delta = 0;
+
+ // Remove the linewrap special case, by moving the cursor outside of the grid.
+ if self.cursor.input_needs_wrap && reflow {
+ self.cursor.input_needs_wrap = false;
+ self.cursor.point.col += 1;
+ }
let mut rows = self.raw.take_all();
@@ -119,10 +125,13 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
}
// Don't try to pull more cells from the next line than available.
- let len = min(row.len(), cols.0 - last_len);
+ let mut num_wrapped = cols.0 - last_len;
+ let len = min(row.len(), num_wrapped);
// 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) {
+ num_wrapped -= 1;
+
let mut cells = row.front_split_off(len - 1);
let mut spacer = T::default();
@@ -138,30 +147,38 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
last_row.append(&mut cells);
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
- if row.is_clear() && (i != cursor_buffer_line || row.len() == 0) {
- 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 {
+ let mut is_clear = row.is_clear();
+
+ if i == cursor_buffer_line && reflow {
+ // Resize cursor's line and reflow the cursor if necessary.
+ let mut target = self.cursor.point.sub(cols, num_wrapped);
+
+ // Clamp to the last column, if no content was reflown with the cursor.
+ if target.col.0 == 0 {
+ self.cursor.input_needs_wrap = true;
+ target = target.sub(cols, 1);
+ }
+ self.cursor.point.col = target.col;
+
+ let line_delta = (self.cursor.point.line - target.line).0;
+ cursor_line_delta += line_delta;
+ is_clear &= line_delta != 0;
+ } else if is_clear {
+ if i + reversed.len() >= self.lines.0 {
// Since we removed a line, rotate down the viewport.
self.display_offset = self.display_offset.saturating_sub(1);
- // Rotate cursors down if content below them was pulled from history.
+ // Rotate cursor down if content below them was pulled from history.
if i < cursor_buffer_line {
self.cursor.point.line += 1;
}
-
- let saved_buffer_line = (self.lines - self.saved_cursor.point.line - 1).0;
- if i < saved_buffer_line {
- self.saved_cursor.point.line += 1;
- }
}
// Don't push line into the new buffer.
continue;
- } else if let Some(cell) = last_row.last_mut() {
+ }
+
+ if let (Some(cell), false) = (last_row.last_mut(), is_clear) {
// Set wrap flag if next line still has cells.
cell.flags_mut().insert(Flags::WRAPLINE);
}
@@ -169,8 +186,22 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
reversed.push(row);
}
- // Add all new empty lines in one go.
- reversed.append(&mut vec![Row::new(cols, T::default()); new_empty_lines]);
+ // Make sure we have at least the viewport filled.
+ if reversed.len() < self.lines.0 {
+ let delta = self.lines.0 - reversed.len();
+ self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(delta);
+ reversed.append(&mut vec![Row::new(cols, T::default()); delta]);
+ }
+
+ // Pull content down to put cursor in correct position, or move cursor up if there's no
+ // more lines to delete below the cursor.
+ if cursor_line_delta != 0 {
+ let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
+ let available = min(cursor_buffer_line, reversed.len() - self.lines.0);
+ let overflow = cursor_line_delta.saturating_sub(available);
+ reversed.truncate(reversed.len() + overflow - cursor_line_delta);
+ self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(overflow);
+ }
// Reverse iterator and fill all rows that are still too short.
let mut new_raw = Vec::with_capacity(reversed.len());
@@ -191,14 +222,26 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
fn shrink_cols(&mut self, cols: Column, reflow: bool) {
self.cols = cols;
- let mut rows = self.raw.take_all();
+ // Remove the linewrap special case, by moving the cursor outside of the grid.
+ if self.cursor.input_needs_wrap && reflow {
+ self.cursor.input_needs_wrap = false;
+ self.cursor.point.col += 1;
+ }
let mut new_raw = Vec::with_capacity(self.raw.len());
let mut buffered: Option<Vec<T>> = None;
+ let mut rows = self.raw.take_all();
for (i, mut row) in rows.drain(..).enumerate().rev() {
// Append lines left over from the previous row.
if let Some(buffered) = buffered.take() {
+ // Add a column for every cell added before the cursor, if it goes beyond the new
+ // width it is then later reflown.
+ let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
+ if i == cursor_buffer_line {
+ self.cursor.point.col += buffered.len();
+ }
+
row.append_front(buffered);
}
@@ -207,8 +250,16 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
let mut wrapped = match row.shrink(cols) {
Some(wrapped) if reflow => wrapped,
_ => {
- new_raw.push(row);
- break;
+ let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
+ if reflow && i == cursor_buffer_line && self.cursor.point.col > cols {
+ // If there are empty cells before the cursor, we assume it is explicit
+ // whitespace and need to wrap it like normal content.
+ Vec::new()
+ } else {
+ // Since it fits, just push the existing line without any reflow.
+ new_raw.push(row);
+ break;
+ }
},
};
@@ -260,9 +311,12 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
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);
+ // Reflow the cursor if it is on this line beyond the width.
+ let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
+ if cursor_buffer_line == i && self.cursor.point.col >= cols {
+ // Since only a single new line is created, we subtract only `cols` from
+ // the cursor instead of reflowing it completely.
+ self.cursor.point.col -= cols;
}
// Make sure new row is at least as long as new width.
@@ -280,8 +334,17 @@ impl<T: GridCell + Default + PartialEq + Copy> Grid<T> {
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);
+ // Reflow the primary cursor, or clamp it if reflow is disabled.
+ if !reflow {
+ self.cursor.point.col = min(self.cursor.point.col, cols - 1);
+ } else if self.cursor.point.col == cols {
+ self.cursor.input_needs_wrap = true;
+ self.cursor.point.col -= 1;
+ } else {
+ self.cursor.point = self.cursor.point.add(cols, 0);
+ }
+
+ // Clamp the saved cursor to the grid.
+ self.saved_cursor.point.col = min(self.saved_cursor.point.col, cols - 1);
}
}