diff --git a/CHANGELOG.md b/CHANGELOG.md index afea1233..cdfaffa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Documentation for class in `--help` missing information on setting general class - Linewrap tracking when switching between primary and alternate screen buffer - Preservation of the alternate screen's saved cursor when swapping to primary screen and back +- Reflow of cursor during resize ## 0.4.3 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 Grid { // Check if a row needs to be wrapped. let should_reflow = |row: &Row| -> bool { let len = Column(row.len()); - reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE) + reflow && len.0 > 0 && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE) }; self.cols = cols; let mut reversed: Vec> = Vec::with_capacity(self.raw.len()); - let mut new_empty_lines = 0; + let mut 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 Grid { } // 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 Grid { 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 Grid { 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 Grid { 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> = 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 Grid { 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 Grid { 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 Grid { 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); } }