diff options
author | Christian Duerr <contact@christianduerr.com> | 2021-03-30 23:25:38 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-30 23:25:38 +0000 |
commit | 3bd5ac221ab3b122962063edd1f4c10f9f2d117f (patch) | |
tree | b0a367b91611e911344aec9ff35354e5a473b6aa /alacritty_terminal/src/vi_mode.rs | |
parent | 974392cdc6fdf1ba0d213145ae578a9316e9d404 (diff) | |
download | alacritty-3bd5ac221ab3b122962063edd1f4c10f9f2d117f.tar.gz alacritty-3bd5ac221ab3b122962063edd1f4c10f9f2d117f.zip |
Unify the grid line indexing types
Previously Alacritty was using two different ways to reference lines in
the terminal. Either a `usize`, or a `Line(usize)`. These indexing
systems both served different purposes, but made it difficult to reason
about logic involving these systems because of its inconsistency.
To resolve this issue, a single new `Line(i32)` type has been
introduced. All existing references to lines and points now rely on
this definition of a line.
The indexing starts at the top of the terminal region with the line 0,
which matches the line 1 used by escape sequences. Each line in the
history becomes increasingly negative and the bottommost line is equal
to the number of visible lines minus one.
Having a system which goes into the negatives allows following the
escape sequence's indexing system closely, while at the same time making
it trivial to implement `Ord` for points.
The Alacritty UI crate is the only place which has a different indexing
system, since rendering and input puts the zero line at the top of the
viewport, rather than the top of the terminal region.
All instances which refer to a number of lines/columns instead of just a
single Line/Column have also been changed to use a `usize` instead. This
way a Line/Column will always refer to a specific place in the grid and
no confusion is created by having a count of lines as a possible index
into the grid storage.
Diffstat (limited to 'alacritty_terminal/src/vi_mode.rs')
-rw-r--r-- | alacritty_terminal/src/vi_mode.rs | 188 |
1 files changed, 90 insertions, 98 deletions
diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index 1b3390d6..54229998 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -65,135 +65,126 @@ impl ViModeCursor { /// Move vi mode cursor. #[must_use = "this returns the result of the operation, without modifying the original"] pub fn motion<T: EventListener>(mut self, term: &mut Term<T>, motion: ViMotion) -> Self { - let display_offset = term.grid().display_offset(); - let lines = term.screen_lines(); - let cols = term.cols(); - - let mut buffer_point = term.visible_to_buffer(self.point); - match motion { ViMotion::Up => { - if buffer_point.line + 1 < term.total_lines() { - buffer_point.line += 1; + if self.point.line > term.topmost_line() { + self.point.line -= 1; + } + }, + ViMotion::Down => { + if self.point.line + 1 < term.screen_lines() as i32 { + self.point.line += 1; } }, - ViMotion::Down => buffer_point.line = buffer_point.line.saturating_sub(1), ViMotion::Left => { - buffer_point = term.expand_wide(buffer_point, Direction::Left); - let wrap_point = Point::new(buffer_point.line + 1, cols - 1); - if buffer_point.column.0 == 0 - && buffer_point.line + 1 < term.total_lines() + self.point = term.expand_wide(self.point, Direction::Left); + let wrap_point = Point::new(self.point.line - 1, term.last_column()); + if self.point.column == 0 + && self.point.line > term.topmost_line() && is_wrap(term, wrap_point) { - buffer_point = wrap_point; + self.point = wrap_point; } else { - buffer_point.column = Column(buffer_point.column.saturating_sub(1)); + self.point.column = Column(self.point.column.saturating_sub(1)); } }, ViMotion::Right => { - buffer_point = term.expand_wide(buffer_point, Direction::Right); - if is_wrap(term, buffer_point) { - buffer_point = Point::new(buffer_point.line - 1, Column(0)); + self.point = term.expand_wide(self.point, Direction::Right); + if is_wrap(term, self.point) { + self.point = Point::new(self.point.line + 1, Column(0)); } else { - buffer_point.column = min(buffer_point.column + 1, cols - 1); + self.point.column = min(self.point.column + 1, term.last_column()); } }, ViMotion::First => { - buffer_point = term.expand_wide(buffer_point, Direction::Left); - while buffer_point.column.0 == 0 - && buffer_point.line + 1 < term.total_lines() - && is_wrap(term, Point::new(buffer_point.line + 1, cols - 1)) + self.point = term.expand_wide(self.point, Direction::Left); + while self.point.column == 0 + && self.point.line > term.topmost_line() + && is_wrap(term, Point::new(self.point.line - 1, term.last_column())) { - buffer_point.line += 1; + self.point.line -= 1; } - buffer_point.column = Column(0); + self.point.column = Column(0); }, - ViMotion::Last => buffer_point = last(term, buffer_point), - ViMotion::FirstOccupied => buffer_point = first_occupied(term, buffer_point), + ViMotion::Last => self.point = last(term, self.point), + ViMotion::FirstOccupied => self.point = first_occupied(term, self.point), ViMotion::High => { - let line = display_offset + lines.0 - 1; + let line = Line(-(term.grid().display_offset() as i32)); let col = first_occupied_in_line(term, line).unwrap_or_default().column; - buffer_point = Point::new(line, col); + self.point = Point::new(line, col); }, ViMotion::Middle => { - let line = display_offset + lines.0 / 2; + let display_offset = term.grid().display_offset() as i32; + let line = Line(-display_offset + term.screen_lines() as i32 / 2 - 1); let col = first_occupied_in_line(term, line).unwrap_or_default().column; - buffer_point = Point::new(line, col); + self.point = Point::new(line, col); }, ViMotion::Low => { - let line = display_offset; + let display_offset = term.grid().display_offset() as i32; + let line = Line(-display_offset + term.screen_lines() as i32 - 1); let col = first_occupied_in_line(term, line).unwrap_or_default().column; - buffer_point = Point::new(line, col); + self.point = Point::new(line, col); }, ViMotion::SemanticLeft => { - buffer_point = semantic(term, buffer_point, Direction::Left, Side::Left); + self.point = semantic(term, self.point, Direction::Left, Side::Left); }, ViMotion::SemanticRight => { - buffer_point = semantic(term, buffer_point, Direction::Right, Side::Left); + self.point = semantic(term, self.point, Direction::Right, Side::Left); }, ViMotion::SemanticLeftEnd => { - buffer_point = semantic(term, buffer_point, Direction::Left, Side::Right); + self.point = semantic(term, self.point, Direction::Left, Side::Right); }, ViMotion::SemanticRightEnd => { - buffer_point = semantic(term, buffer_point, Direction::Right, Side::Right); + self.point = semantic(term, self.point, Direction::Right, Side::Right); }, ViMotion::WordLeft => { - buffer_point = word(term, buffer_point, Direction::Left, Side::Left); + self.point = word(term, self.point, Direction::Left, Side::Left); }, ViMotion::WordRight => { - buffer_point = word(term, buffer_point, Direction::Right, Side::Left); + self.point = word(term, self.point, Direction::Right, Side::Left); }, ViMotion::WordLeftEnd => { - buffer_point = word(term, buffer_point, Direction::Left, Side::Right); + self.point = word(term, self.point, Direction::Left, Side::Right); }, ViMotion::WordRightEnd => { - buffer_point = word(term, buffer_point, Direction::Right, Side::Right); - }, - ViMotion::Bracket => { - buffer_point = term.bracket_search(buffer_point).unwrap_or(buffer_point); + self.point = word(term, self.point, Direction::Right, Side::Right); }, + ViMotion::Bracket => self.point = term.bracket_search(self.point).unwrap_or(self.point), } - term.scroll_to_point(buffer_point); - self.point = term.grid().clamp_buffer_to_visible(buffer_point); + term.scroll_to_point(self.point); self } /// Get target cursor point for vim-like page movement. #[must_use = "this returns the result of the operation, without modifying the original"] - pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: isize) -> Self { + pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: i32) -> Self { // Check number of lines the cursor needs to be moved. let overscroll = if lines > 0 { let max_scroll = term.history_size() - term.grid().display_offset(); - max(0, lines - max_scroll as isize) + max(0, lines - max_scroll as i32) } else { let max_scroll = term.grid().display_offset(); - min(0, lines + max_scroll as isize) + min(0, lines + max_scroll as i32) }; // Clamp movement to within visible region. - let mut line = self.point.line.0 as isize; - line -= overscroll; - line = max(0, min(term.screen_lines().0 as isize - 1, line)); + let line = (self.point.line - overscroll).grid_clamp(term, Boundary::Cursor); // Find the first occupied cell after scrolling has been performed. - let buffer_point = term.visible_to_buffer(self.point); - let mut target_line = buffer_point.line as isize + lines; - target_line = max(0, min(term.total_lines() as isize - 1, target_line)); - let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().column; + let target_line = (self.point.line - lines).grid_clamp(term, Boundary::Grid); + let column = first_occupied_in_line(term, target_line).unwrap_or_default().column; // Move cursor. - self.point = Point::new(Line(line as usize), col); + self.point = Point::new(line, column); self } } /// Find next end of line to move to. -fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { - let cols = term.cols(); - +fn last<T>(term: &Term<T>, mut point: Point) -> Point { // Expand across wide cells. point = term.expand_wide(point, Direction::Right); @@ -205,35 +196,35 @@ fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { occupied } else if is_wrap(term, point) { // Jump to last occupied cell across linewraps. - while point.line > 0 && is_wrap(term, point) { - point.line -= 1; + while is_wrap(term, point) { + point.line += 1; } last_occupied_in_line(term, point.line).unwrap_or(point) } else { // Jump to last column when beyond the last occupied cell. - Point::new(point.line, cols - 1) + Point::new(point.line, term.last_column()) } } /// Find next non-empty cell to move to. -fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { - let cols = term.cols(); +fn first_occupied<T>(term: &Term<T>, mut point: Point) -> Point { + let last_column = term.last_column(); // Expand left across wide chars, since we're searching lines left to right. point = term.expand_wide(point, Direction::Left); // Find first non-empty cell in current line. let occupied = first_occupied_in_line(term, point.line) - .unwrap_or_else(|| Point::new(point.line, cols - 1)); + .unwrap_or_else(|| Point::new(point.line, last_column)); // Jump across wrapped lines if we're already at this line's first occupied cell. if point == occupied { let mut occupied = None; // Search for non-empty cell in previous lines. - for line in (point.line + 1)..term.total_lines() { - if !is_wrap(term, Point::new(line, cols - 1)) { + for line in (term.topmost_line().0..point.line.0).rev().map(Line::from) { + if !is_wrap(term, Point::new(line, last_column)) { break; } @@ -247,12 +238,12 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { break occupied; } - let last_cell = Point::new(line, cols - 1); - if line == 0 || !is_wrap(term, last_cell) { + let last_cell = Point::new(line, last_column); + if !is_wrap(term, last_cell) { break last_cell; } - line -= 1; + line += 1; }) } else { occupied @@ -262,14 +253,14 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { /// Move by semantically separated word, like w/b/e/ge in vi. fn semantic<T: EventListener>( term: &mut Term<T>, - mut point: Point<usize>, + mut point: Point, direction: Direction, side: Side, -) -> Point<usize> { +) -> Point { // Expand semantically based on movement direction. - let expand_semantic = |point: Point<usize>| { + let expand_semantic = |point: Point| { // Do not expand when currently on a semantic escape char. - let cell = &term.grid()[point.line][point.column]; + let cell = &term.grid()[point]; if term.semantic_escape_chars().contains(cell.c) && !cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) { @@ -312,10 +303,10 @@ fn semantic<T: EventListener>( /// Move by whitespace separated word, like W/B/E/gE in vi. fn word<T: EventListener>( term: &mut Term<T>, - mut point: Point<usize>, + mut point: Point, direction: Direction, side: Side, -) -> Point<usize> { +) -> Point { // Make sure we jump above wide chars. point = term.expand_wide(point, direction); @@ -351,45 +342,46 @@ fn word<T: EventListener>( } /// Find first non-empty cell in line. -fn first_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>> { - (0..term.cols().0) +fn first_occupied_in_line<T>(term: &Term<T>, line: Line) -> Option<Point> { + (0..term.columns()) .map(|col| Point::new(line, Column(col))) .find(|&point| !is_space(term, point)) } /// Find last non-empty cell in line. -fn last_occupied_in_line<T>(term: &Term<T>, line: usize) -> Option<Point<usize>> { - (0..term.cols().0) +fn last_occupied_in_line<T>(term: &Term<T>, line: Line) -> Option<Point> { + (0..term.columns()) .map(|col| Point::new(line, Column(col))) .rfind(|&point| !is_space(term, point)) } /// Advance point based on direction. -fn advance<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> Point<usize> { +fn advance<T>(term: &Term<T>, point: Point, direction: Direction) -> Point { if direction == Direction::Left { - point.sub_absolute(term, Boundary::Clamp, 1) + point.sub(term, Boundary::Grid, 1) } else { - point.add_absolute(term, Boundary::Clamp, 1) + point.add(term, Boundary::Grid, 1) } } /// Check if cell at point contains whitespace. -fn is_space<T>(term: &Term<T>, point: Point<usize>) -> bool { +fn is_space<T>(term: &Term<T>, point: Point) -> bool { let cell = &term.grid()[point.line][point.column]; !cell.flags().intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) && (cell.c == ' ' || cell.c == '\t') } -fn is_wrap<T>(term: &Term<T>, point: Point<usize>) -> bool { - point.line != 0 && term.grid()[point.line][point.column].flags.contains(Flags::WRAPLINE) +/// Check if the cell at a point contains the WRAPLINE flag. +fn is_wrap<T>(term: &Term<T>, point: Point) -> bool { + term.grid()[point].flags.contains(Flags::WRAPLINE) } /// Check if point is at screen boundary. -fn is_boundary<T>(term: &Term<T>, point: Point<usize>, direction: Direction) -> bool { - let total_lines = term.total_lines(); - let num_cols = term.cols(); - (point.line + 1 >= total_lines && point.column.0 == 0 && direction == Direction::Left) - || (point.line == 0 && point.column + 1 >= num_cols && direction == Direction::Right) +fn is_boundary<T>(term: &Term<T>, point: Point, direction: Direction) -> bool { + (point.line <= term.topmost_line() && point.column == 0 && direction == Direction::Left) + || (point.line == term.bottommost_line() + && point.column + 1 >= term.columns() + && direction == Direction::Right) } #[cfg(test)] @@ -647,12 +639,12 @@ mod tests { #[test] fn scroll_semantic() { let mut term = term(); - term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5)); + term.grid_mut().scroll_up(&(Line(0)..Line(20)), 5); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::SemanticLeft); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::SemanticRight); @@ -660,7 +652,7 @@ mod tests { assert_eq!(term.grid().display_offset(), 0); cursor = cursor.motion(&mut term, ViMotion::SemanticLeftEnd); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::SemanticRightEnd); @@ -723,12 +715,12 @@ mod tests { #[test] fn scroll_word() { let mut term = term(); - term.grid_mut().scroll_up(&(Line(0)..Line(20)), Line(5)); + term.grid_mut().scroll_up(&(Line(0)..Line(20)), 5); let mut cursor = ViModeCursor::new(Point::new(Line(0), Column(0))); cursor = cursor.motion(&mut term, ViMotion::WordLeft); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::WordRight); @@ -736,7 +728,7 @@ mod tests { assert_eq!(term.grid().display_offset(), 0); cursor = cursor.motion(&mut term, ViMotion::WordLeftEnd); - assert_eq!(cursor.point, Point::new(Line(0), Column(0))); + assert_eq!(cursor.point, Point::new(Line(-5), Column(0))); assert_eq!(term.grid().display_offset(), 5); cursor = cursor.motion(&mut term, ViMotion::WordRightEnd); |