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/term | |
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/term')
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 4 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 448 | ||||
-rw-r--r-- | alacritty_terminal/src/term/search.rs | 261 |
3 files changed, 326 insertions, 387 deletions
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 91ee3391..255cbce7 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 mut row = Row::<Cell>::new(Column(10)); + let mut row = Row::<Cell>::new(10); row[Column(5)].c = 'a'; assert_eq!(row.line_length(), Column(6)); @@ -186,7 +186,7 @@ mod tests { #[test] fn line_length_works_with_wrapline() { - let mut row = Row::<Cell>::new(Column(10)); + let mut row = Row::<Cell>::new(10); 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 b853ceba..8d0fc0f8 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -16,7 +16,7 @@ use crate::ansi::{ use crate::config::Config; use crate::event::{Event, EventListener}; use crate::grid::{Dimensions, DisplayIter, Grid, Scroll}; -use crate::index::{self, Boundary, Column, Direction, IndexRange, Line, Point, Side}; +use crate::index::{self, Boundary, Column, Direction, Line, Point, Side}; use crate::selection::{Selection, SelectionRange}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::{Colors, Rgb}; @@ -29,7 +29,7 @@ pub mod search; /// Minimum number of columns. /// /// A minimum of 2 is necessary to hold fullwidth unicode characters. -pub const MIN_COLS: usize = 2; +pub const MIN_COLUMNS: usize = 2; /// Minimum number of visible lines. pub const MIN_SCREEN_LINES: usize = 1; @@ -97,10 +97,10 @@ pub struct SizeInfo { padding_y: f32, /// Number of lines in the viewport. - screen_lines: Line, + screen_lines: usize, /// Number of columns in the viewport. - cols: Column, + columns: usize, } impl SizeInfo { @@ -120,10 +120,10 @@ impl SizeInfo { } let lines = (height - 2. * padding_y) / cell_height; - let screen_lines = Line(max(lines as usize, MIN_SCREEN_LINES)); + let screen_lines = max(lines as usize, MIN_SCREEN_LINES); - let cols = (width - 2. * padding_x) / cell_width; - let cols = Column(max(cols as usize, MIN_COLS)); + let columns = (width - 2. * padding_x) / cell_width; + let columns = max(columns as usize, MIN_COLUMNS); SizeInfo { width, @@ -133,13 +133,13 @@ impl SizeInfo { padding_x: padding_x.floor(), padding_y: padding_y.floor(), screen_lines, - cols, + columns, } } #[inline] pub fn reserve_lines(&mut self, count: usize) { - self.screen_lines = Line(max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES)); + self.screen_lines = max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES); } /// Check if coordinates are inside the terminal grid. @@ -147,26 +147,12 @@ impl SizeInfo { /// The padding, message bar or search are not counted as part of the grid. #[inline] pub fn contains_point(&self, x: usize, y: usize) -> bool { - x <= (self.padding_x + self.cols.0 as f32 * self.cell_width) as usize + x <= (self.padding_x + self.columns as f32 * self.cell_width) as usize && x > self.padding_x as usize - && y <= (self.padding_y + self.screen_lines.0 as f32 * self.cell_height) as usize + && y <= (self.padding_y + self.screen_lines as f32 * self.cell_height) as usize && y > self.padding_y as usize } - /// Convert window space pixels to terminal grid coordinates. - /// - /// If the coordinates are outside of the terminal grid, like positions inside the padding, the - /// coordinates will be clamped to the closest grid coordinates. - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { - let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); - let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); - - Point { - line: min(line, Line(self.screen_lines.saturating_sub(1))), - column: min(col, Column(self.cols.saturating_sub(1))), - } - } - #[inline] pub fn width(&self) -> f32 { self.width @@ -197,20 +183,27 @@ impl SizeInfo { self.padding_y } + /// Calculate padding to spread it evenly around the terminal content. #[inline] - pub fn screen_lines(&self) -> Line { - self.screen_lines + fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { + padding + ((dimension - 2. * padding) % cell_dimension) / 2. } +} +impl Dimensions for SizeInfo { #[inline] - pub fn cols(&self) -> Column { - self.cols + fn columns(&self) -> usize { + self.columns } - /// Calculate padding to spread it evenly around the terminal content. #[inline] - fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { - padding + ((dimension - 2. * padding) % cell_dimension) / 2. + fn screen_lines(&self) -> usize { + self.screen_lines + } + + #[inline] + fn total_lines(&self) -> usize { + self.screen_lines() } } @@ -286,19 +279,26 @@ impl<T> Term<T> { { self.grid.scroll_display(scroll); self.event_proxy.send_event(Event::MouseCursorDirty); + + // Clamp vi mode cursor to the viewport. + let viewport_start = -(self.grid.display_offset() as i32); + let viewport_end = viewport_start + self.bottommost_line().0; + let vi_cursor_line = &mut self.vi_mode_cursor.point.line.0; + *vi_cursor_line = min(viewport_end, max(viewport_start, *vi_cursor_line)); + self.vi_mode_recompute_selection(); } pub fn new<C>(config: &Config<C>, size: SizeInfo, event_proxy: T) -> Term<T> { - let num_cols = size.cols; + let num_cols = size.columns; let num_lines = size.screen_lines; let history_size = config.scrolling.history() as usize; let grid = Grid::new(num_lines, num_cols, history_size); let alt = Grid::new(num_lines, num_cols, 0); - let tabs = TabStops::new(grid.cols()); + let tabs = TabStops::new(grid.columns()); - let scroll_region = Line(0)..grid.screen_lines(); + let scroll_region = Line(0)..Line(grid.screen_lines() as i32); Term { grid, @@ -353,11 +353,11 @@ impl<T> Term<T> { let mut res = String::new(); if is_block { - for line in (end.line + 1..=start.line).rev() { + for line in (start.line.0..end.line.0).map(Line::from) { res += &self.line_to_string(line, start.column..end.column, start.column.0 != 0); // If the last column is included, newline is appended automatically. - if end.column != self.cols() - 1 { + if end.column != self.columns() - 1 { res += "\n"; } } @@ -370,12 +370,12 @@ impl<T> Term<T> { } /// Convert range between two points to a String. - pub fn bounds_to_string(&self, start: Point<usize>, end: Point<usize>) -> String { + pub fn bounds_to_string(&self, start: Point, end: Point) -> String { let mut res = String::new(); - for line in (end.line..=start.line).rev() { + for line in (start.line.0..=end.line.0).map(Line::from) { let start_col = if line == start.line { start.column } else { Column(0) }; - let end_col = if line == end.line { end.column } else { self.cols() - 1 }; + let end_col = if line == end.line { end.column } else { self.last_column() }; res += &self.line_to_string(line, start_col..end_col, line == end.line); } @@ -386,7 +386,7 @@ impl<T> Term<T> { /// Convert a single line in the grid to a String. fn line_to_string( &self, - line: usize, + line: Line, mut cols: Range<Column>, include_wrapped_wide: bool, ) -> String { @@ -401,12 +401,12 @@ impl<T> Term<T> { } let mut tab_mode = false; - for col in IndexRange::from(cols.start..line_length) { - let cell = &grid_line[col]; + for column in (cols.start.0..line_length.0).map(Column::from) { + let cell = &grid_line[column]; // Skip over cells until next tab-stop once a tab was found. if tab_mode { - if self.tabs[col] { + if self.tabs[column] { tab_mode = false; } else { continue; @@ -428,7 +428,7 @@ impl<T> Term<T> { } } - if cols.end >= self.cols() - 1 + if cols.end >= self.columns() - 1 && (line_length.0 == 0 || !self.grid[line][line_length - 1].flags.contains(Flags::WRAPLINE)) { @@ -436,22 +436,17 @@ impl<T> Term<T> { } // If wide char is not part of the selection, but leading spacer is, include it. - if line_length == self.cols() + if line_length == self.columns() && line_length.0 >= 2 && grid_line[line_length - 1].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) && include_wrapped_wide { - text.push(self.grid[line - 1][Column(0)].c); + text.push(self.grid[line - 1i32][Column(0)].c); } text } - #[inline] - pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { - self.grid.visible_to_buffer(point) - } - /// Terminal content required for rendering. #[inline] pub fn renderable_content(&self) -> RenderableContent<'_> @@ -480,10 +475,10 @@ impl<T> Term<T> { self.cell_width = size.cell_width as usize; self.cell_height = size.cell_height as usize; - let old_cols = self.cols(); + let old_cols = self.columns(); let old_lines = self.screen_lines(); - let num_cols = size.cols; + let num_cols = size.columns; let num_lines = size.screen_lines; if old_cols == num_cols && old_lines == num_lines { @@ -493,6 +488,13 @@ impl<T> Term<T> { debug!("New num_cols is {} and num_lines is {}", num_cols, num_lines); + // Move vi mode cursor with the content. + let history_size = self.history_size(); + let mut delta = num_lines as i32 - old_lines as i32; + let min_delta = min(0, num_lines as i32 - self.grid.cursor.point.line.0 - 1); + delta = min(max(delta, min_delta), history_size as i32); + self.vi_mode_cursor.point.line += delta; + // Invalidate selection and tabs only when necessary. if old_cols != num_cols { self.selection = None; @@ -500,27 +502,21 @@ impl<T> Term<T> { // Recreate tabs list. self.tabs.resize(num_cols); } else if let Some(selection) = self.selection.take() { - // Move the selection if only number of lines changed. - let delta = if num_lines > old_lines { - (num_lines - old_lines.0).saturating_sub(self.history_size()) as isize - } else { - let cursor_line = self.grid.cursor.point.line; - -(min(old_lines - cursor_line - 1, old_lines - num_lines).0 as isize) - }; - self.selection = selection.rotate(self, &(Line(0)..num_lines), delta); + let range = Line(0)..Line(num_lines as i32); + self.selection = selection.rotate(self, &range, -delta); } let is_alt = self.mode.contains(TermMode::ALT_SCREEN); - self.grid.resize(!is_alt, num_lines, num_cols); self.inactive_grid.resize(is_alt, num_lines, num_cols); // Clamp vi cursor to viewport. - self.vi_mode_cursor.point.column = min(self.vi_mode_cursor.point.column, num_cols - 1); - self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1); + let vi_point = self.vi_mode_cursor.point; + self.vi_mode_cursor.point.column = min(vi_point.column, Column(num_cols - 1)); + self.vi_mode_cursor.point.line = min(vi_point.line, Line(num_lines as i32 - 1)); // Reset scrolling region. - self.scroll_region = Line(0)..self.screen_lines(); + self.scroll_region = Line(0)..Line(self.screen_lines() as i32); } /// Active terminal modes. @@ -547,49 +543,22 @@ impl<T> Term<T> { self.selection = None; } - /// Get the selection within the viewport. - fn visible_selection(&self) -> Option<SelectionRange<Line>> { - let selection = self.selection.as_ref()?.to_range(self)?; - - // Set horizontal limits for block selection. - let (limit_start, limit_end) = if selection.is_block { - (selection.start.column, selection.end.column) - } else { - (Column(0), self.cols() - 1) - }; - - let range = self.grid.clamp_buffer_range_to_visible(&(selection.start..=selection.end))?; - let mut start = *range.start(); - let mut end = *range.end(); - - // Trim start/end with partially visible block selection. - start.column = max(limit_start, start.column); - end.column = min(limit_end, end.column); - - Some(SelectionRange::new(start, end, selection.is_block)) - } - /// Scroll screen down. /// /// Text moves down; clear at bottom /// Expects origin to be in scroll range. #[inline] - fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) { + fn scroll_down_relative(&mut self, origin: Line, mut lines: usize) { trace!("Scrolling down relative: origin={}, lines={}", origin, lines); - let num_lines = self.screen_lines(); - - lines = min(lines, self.scroll_region.end - self.scroll_region.start); - lines = min(lines, self.scroll_region.end - origin); + lines = min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize); + lines = min(lines, (self.scroll_region.end - origin).0 as usize); 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(self, &absolute_region, -(lines.0 as isize))); + self.selection = + self.selection.take().and_then(|s| s.rotate(self, ®ion, -(lines as i32))); // Scroll between origin and bottom self.grid.scroll_down(®ion, lines); @@ -600,19 +569,15 @@ 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, mut lines: Line) { + fn scroll_up_relative(&mut self, origin: Line, mut lines: usize) { trace!("Scrolling up relative: origin={}, lines={}", origin, lines); - let num_lines = self.screen_lines(); - - lines = min(lines, self.scroll_region.end - self.scroll_region.start); + lines = min(lines, (self.scroll_region.end - self.scroll_region.start).0 as usize); 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(self, &absolute_region, lines.0 as isize)); + self.selection = self.selection.take().and_then(|s| s.rotate(self, ®ion, lines as i32)); // Scroll from origin to bottom less number of lines. self.grid.scroll_up(®ion, lines); @@ -648,9 +613,7 @@ impl<T> Term<T> { if self.mode.contains(TermMode::VI) { // Reset vi mode cursor position to match primary cursor. - let cursor = self.grid.cursor.point; - let line = min(cursor.line + self.grid.display_offset(), self.screen_lines() - 1); - self.vi_mode_cursor = ViModeCursor::new(Point::new(line, cursor.column)); + self.vi_mode_cursor = ViModeCursor::new(self.grid.cursor.point); } // Update UI about cursor blinking state changes. @@ -673,9 +636,9 @@ impl<T> Term<T> { self.vi_mode_recompute_selection(); } - /// Move vi cursor to absolute point in grid. + /// Move vi cursor to a point in the grid. #[inline] - pub fn vi_goto_point(&mut self, point: Point<usize>) + pub fn vi_goto_point(&mut self, point: Point) where T: EventListener, { @@ -683,7 +646,7 @@ impl<T> Term<T> { self.scroll_to_point(point); // Move vi cursor to the point. - self.vi_mode_cursor.point = self.grid.clamp_buffer_to_visible(point); + self.vi_mode_cursor.point = point; self.vi_mode_recompute_selection(); } @@ -696,43 +659,38 @@ impl<T> Term<T> { return; } - let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point); - // Update only if non-empty selection is present. - let selection = match &mut self.selection { - Some(selection) if !selection.is_empty() => selection, - _ => return, - }; - - selection.update(viewport_point, Side::Left); - selection.include_all(); + if let Some(selection) = self.selection.as_mut().filter(|s| !s.is_empty()) { + selection.update(self.vi_mode_cursor.point, Side::Left); + selection.include_all(); + } } /// Scroll display to point if it is outside of viewport. - pub fn scroll_to_point(&mut self, point: Point<usize>) + pub fn scroll_to_point(&mut self, point: Point) where T: EventListener, { - let display_offset = self.grid.display_offset(); - let num_lines = self.screen_lines().0; + let display_offset = self.grid.display_offset() as i32; + let screen_lines = self.grid.screen_lines() as i32; - if point.line >= display_offset + num_lines { - let lines = point.line.saturating_sub(display_offset + num_lines - 1); - self.scroll_display(Scroll::Delta(lines as isize)); - } else if point.line < display_offset { - let lines = display_offset.saturating_sub(point.line); - self.scroll_display(Scroll::Delta(-(lines as isize))); + if point.line < -display_offset { + let lines = point.line + display_offset; + self.scroll_display(Scroll::Delta(-lines.0)); + } else if point.line >= (screen_lines - display_offset) { + let lines = point.line + display_offset - screen_lines + 1i32; + self.scroll_display(Scroll::Delta(-lines.0)); } } /// Jump to the end of a wide cell. - pub fn expand_wide(&self, mut point: Point<usize>, direction: Direction) -> Point<usize> { + pub fn expand_wide(&self, mut point: Point, direction: Direction) -> Point { let flags = self.grid[point.line][point.column].flags; match direction { Direction::Right if flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) => { point.column = Column(1); - point.line -= 1; + point.line += 1; }, Direction::Right if flags.contains(Flags::WIDE_CHAR) => point.column += 1, Direction::Left if flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) => { @@ -740,7 +698,7 @@ impl<T> Term<T> { point.column -= 1; } - let prev = point.sub_absolute(self, Boundary::Clamp, 1); + let prev = point.sub(self, Boundary::Grid, 1); if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) { point = prev; } @@ -784,7 +742,7 @@ impl<T> Term<T> { self.grid.cursor_cell().flags.insert(Flags::WRAPLINE); - if (self.grid.cursor.point.line + 1) >= self.scroll_region.end { + if self.grid.cursor.point.line + 1 >= self.scroll_region.end { self.linefeed(); } else { self.grid.cursor.point.line += 1; @@ -817,12 +775,12 @@ impl<T> Term<T> { impl<T> Dimensions for Term<T> { #[inline] - fn cols(&self) -> Column { - self.grid.cols() + fn columns(&self) -> usize { + self.grid.columns() } #[inline] - fn screen_lines(&self) -> Line { + fn screen_lines(&self) -> usize { self.grid.screen_lines() } @@ -865,7 +823,7 @@ impl<T: EventListener> Handler for Term<T> { self.wrapline(); } - let num_cols = self.cols(); + let num_cols = self.columns(); // If in insert mode, first shift cells to the right. if self.mode.contains(TermMode::INSERT) && self.grid.cursor.point.column + width < num_cols @@ -874,7 +832,7 @@ impl<T: EventListener> Handler for Term<T> { let col = self.grid.cursor.point.column; let row = &mut self.grid[line][..]; - for col in (col.0..(num_cols - width).0).rev() { + for col in (col.0..(num_cols - width)).rev() { row.swap(col + width, col); } } @@ -913,8 +871,8 @@ impl<T: EventListener> Handler for Term<T> { fn decaln(&mut self) { trace!("Decalnning"); - for line in 0..self.screen_lines().0 { - for column in 0..self.cols().0 { + for line in (0..self.screen_lines()).map(Line::from) { + for column in 0..self.columns() { let cell = &mut self.grid[line][Column(column)]; *cell = Cell::default(); cell.c = 'E'; @@ -928,11 +886,11 @@ impl<T: EventListener> Handler for Term<T> { let (y_offset, max_y) = if self.mode.contains(TermMode::ORIGIN) { (self.scroll_region.start, self.scroll_region.end - 1) } else { - (Line(0), self.screen_lines() - 1) + (Line(0), self.bottommost_line()) }; - self.grid.cursor.point.line = min(line + y_offset, max_y); - self.grid.cursor.point.column = min(col, self.cols() - 1); + self.grid.cursor.point.line = max(min(line + y_offset, max_y), Line(0)); + self.grid.cursor.point.column = min(col, self.last_column()); self.grid.cursor.input_needs_wrap = false; } @@ -949,50 +907,48 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn insert_blank(&mut self, count: Column) { + fn insert_blank(&mut self, count: usize) { let cursor = &self.grid.cursor; let bg = cursor.template.bg; // Ensure inserting within terminal bounds - let count = min(count, self.cols() - cursor.point.column); + let count = min(count, self.columns() - cursor.point.column.0); let source = cursor.point.column; - let destination = cursor.point.column + count; - let num_cells = (self.cols() - destination).0; + let destination = cursor.point.column.0 + count; + let num_cells = self.columns() - destination; let line = cursor.point.line; let row = &mut self.grid[line][..]; for offset in (0..num_cells).rev() { - row.swap(destination.0 + offset, source.0 + offset); + row.swap(destination + offset, source.0 + offset); } // Cells were just moved out toward the end of the line; // fill in between source and dest with blanks. - for cell in &mut row[source.0..destination.0] { + for cell in &mut row[source.0..destination] { *cell = bg.into(); } } #[inline] - fn move_up(&mut self, lines: Line) { + fn move_up(&mut self, lines: usize) { trace!("Moving up: {}", lines); - let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, self.grid.cursor.point.column) + self.goto(self.grid.cursor.point.line - lines, self.grid.cursor.point.column) } #[inline] - fn move_down(&mut self, lines: Line) { + fn move_down(&mut self, lines: usize) { trace!("Moving down: {}", lines); - let move_to = self.grid.cursor.point.line + lines; - self.goto(move_to, self.grid.cursor.point.column) + self.goto(self.grid.cursor.point.line + lines, self.grid.cursor.point.column) } #[inline] fn move_forward(&mut self, cols: Column) { trace!("Moving forward: {}", cols); - let num_cols = self.cols(); - self.grid.cursor.point.column = min(self.grid.cursor.point.column + cols, num_cols - 1); + let last_column = self.last_column(); + self.grid.cursor.point.column = min(self.grid.cursor.point.column + cols, last_column); self.grid.cursor.input_needs_wrap = false; } @@ -1037,17 +993,15 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn move_down_and_cr(&mut self, lines: Line) { + fn move_down_and_cr(&mut self, lines: usize) { trace!("Moving down and cr: {}", lines); - let move_to = self.grid.cursor.point.line + lines; - self.goto(move_to, Column(0)) + self.goto(self.grid.cursor.point.line + lines, Column(0)) } #[inline] - fn move_up_and_cr(&mut self, lines: Line) { + fn move_up_and_cr(&mut self, lines: usize) { trace!("Moving up and cr: {}", lines); - let move_to = Line(self.grid.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, Column(0)) + self.goto(self.grid.cursor.point.line - lines, Column(0)) } /// Insert tab at cursor position. @@ -1059,7 +1013,7 @@ impl<T: EventListener> Handler for Term<T> { return; } - while self.grid.cursor.point.column < self.cols() && count != 0 { + while self.grid.cursor.point.column < self.columns() && count != 0 { count -= 1; let c = self.grid.cursor.charsets[self.active_charset].map('\t'); @@ -1069,7 +1023,7 @@ impl<T: EventListener> Handler for Term<T> { } loop { - if (self.grid.cursor.point.column + 1) == self.cols() { + if (self.grid.cursor.point.column + 1) == self.columns() { break; } @@ -1107,7 +1061,7 @@ impl<T: EventListener> Handler for Term<T> { trace!("Linefeed"); let next = self.grid.cursor.point.line + 1; if next == self.scroll_region.end { - self.scroll_up(Line(1)); + self.scroll_up(1); } else if next < self.screen_lines() { self.grid.cursor.point.line += 1; } @@ -1163,19 +1117,19 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn scroll_up(&mut self, lines: Line) { + fn scroll_up(&mut self, lines: usize) { let origin = self.scroll_region.start; self.scroll_up_relative(origin, lines); } #[inline] - fn scroll_down(&mut self, lines: Line) { + fn scroll_down(&mut self, lines: usize) { let origin = self.scroll_region.start; self.scroll_down_relative(origin, lines); } #[inline] - fn insert_blank_lines(&mut self, lines: Line) { + fn insert_blank_lines(&mut self, lines: usize) { trace!("Inserting blank {} lines", lines); let origin = self.grid.cursor.point.line; @@ -1185,13 +1139,13 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn delete_lines(&mut self, lines: Line) { + fn delete_lines(&mut self, lines: usize) { let origin = self.grid.cursor.point.line; - let lines = min(self.screen_lines() - origin, lines); + let lines = min(self.screen_lines() - origin.0 as usize, lines); trace!("Deleting {} lines", lines); - if lines.0 > 0 && self.scroll_region.contains(&self.grid.cursor.point.line) { + if lines > 0 && self.scroll_region.contains(&origin) { self.scroll_up_relative(origin, lines); } } @@ -1203,7 +1157,7 @@ impl<T: EventListener> Handler for Term<T> { trace!("Erasing chars: count={}, col={}", count, cursor.point.column); let start = cursor.point.column; - let end = min(start + count, self.cols()); + let end = min(start + count, Column(self.columns())); // Cleared cells have current background color set. let bg = self.grid.cursor.template.bg; @@ -1215,28 +1169,28 @@ impl<T: EventListener> Handler for Term<T> { } #[inline] - fn delete_chars(&mut self, count: Column) { - let cols = self.cols(); + fn delete_chars(&mut self, count: usize) { + let columns = self.columns(); let cursor = &self.grid.cursor; let bg = cursor.template.bg; // Ensure deleting within terminal bounds. - let count = min(count, cols); + let count = min(count, columns); - let start = cursor.point.column; - let end = min(start + count, cols - 1); - let num_cells = (cols - end).0; + let start = cursor.point.column.0; + let end = min(start + count, columns - 1); + let num_cells = columns - end; let line = cursor.point.line; let row = &mut self.grid[line][..]; for offset in 0..num_cells { - row.swap(start.0 + offset, end.0 + offset); + row.swap(start + offset, end + offset); } // Clear last `count` cells in the row. If deleting 1 char, need to delete // 1 cell. - let end = (cols - count).0; + let end = columns - count; for cell in &mut row[end..] { *cell = bg.into(); } @@ -1305,11 +1259,8 @@ impl<T: EventListener> Handler for Term<T> { }, } - let cursor_buffer_line = (self.screen_lines() - self.grid.cursor.point.line - 1).0; - self.selection = self - .selection - .take() - .filter(|s| !s.intersects_range(cursor_buffer_line..=cursor_buffer_line)); + let range = self.grid.cursor.point.line..=self.grid.cursor.point.line; + self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); } /// Set the indexed color value. @@ -1384,29 +1335,26 @@ impl<T: EventListener> Handler for Term<T> { trace!("Clearing screen: {:?}", mode); let bg = self.grid.cursor.template.bg; - let num_lines = self.screen_lines().0; - let cursor_buffer_line = num_lines - self.grid.cursor.point.line.0 - 1; + let screen_lines = self.screen_lines(); match mode { ansi::ClearMode::Above => { let cursor = self.grid.cursor.point; // If clearing more than one line. - if cursor.line > Line(1) { + if cursor.line > 1 { // Fully clear all lines before the current line. self.grid.reset_region(..cursor.line); } // Clear up to the current column in the current line. - let end = min(cursor.column + 1, self.cols()); + let end = min(cursor.column + 1, Column(self.columns())); for cell in &mut self.grid[cursor.line][..end] { *cell = bg.into(); } - self.selection = self - .selection - .take() - .filter(|s| !s.intersects_range(cursor_buffer_line..num_lines)); + let range = Line(0)..=cursor.line; + self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); }, ansi::ClearMode::Below => { let cursor = self.grid.cursor.point; @@ -1414,12 +1362,12 @@ impl<T: EventListener> Handler for Term<T> { *cell = bg.into(); } - if cursor.line.0 < num_lines - 1 { + if (cursor.line.0 as usize) < screen_lines - 1 { self.grid.reset_region((cursor.line + 1)..); } - self.selection = - self.selection.take().filter(|s| !s.intersects_range(..=cursor_buffer_line)); + let range = cursor.line..Line(screen_lines as i32); + self.selection = self.selection.take().filter(|s| !s.intersects_range(range)); }, ansi::ClearMode::All => { if self.mode.contains(TermMode::ALT_SCREEN) { @@ -1428,12 +1376,12 @@ impl<T: EventListener> Handler for Term<T> { self.grid.clear_viewport(); } - self.selection = self.selection.take().filter(|s| !s.intersects_range(..num_lines)); + self.selection = None; }, ansi::ClearMode::Saved if self.history_size() > 0 => { self.grid.clear_history(); - self.selection = self.selection.take().filter(|s| !s.intersects_range(num_lines..)); + self.selection = self.selection.take().filter(|s| !s.intersects_range(..Line(0))); }, // We have no history to clear. ansi::ClearMode::Saved => (), @@ -1463,8 +1411,8 @@ impl<T: EventListener> Handler for Term<T> { self.cursor_style = None; self.grid.reset(); self.inactive_grid.reset(); - self.scroll_region = Line(0)..self.screen_lines(); - self.tabs = TabStops::new(self.cols()); + self.scroll_region = Line(0)..Line(self.screen_lines() as i32); + self.tabs = TabStops::new(self.columns()); self.title_stack = Vec::new(); self.title = None; self.selection = None; @@ -1482,9 +1430,9 @@ impl<T: EventListener> Handler for Term<T> { trace!("Reversing index"); // If cursor is at the top. if self.grid.cursor.point.line == self.scroll_region.start { - self.scroll_down(Line(1)); + self.scroll_down(1); } else { - self.grid.cursor.point.line = Line(self.grid.cursor.point.line.saturating_sub(1)); + self.grid.cursor.point.line = max(self.grid.cursor.point.line - 1, Line(0)); } } @@ -1628,7 +1576,7 @@ impl<T: EventListener> Handler for Term<T> { #[inline] fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) { // Fallback to the last line as default. - let bottom = bottom.unwrap_or_else(|| self.screen_lines().0); + let bottom = bottom.unwrap_or_else(|| self.screen_lines()); if top >= bottom { debug!("Invalid scrolling region: ({};{})", top, bottom); @@ -1639,13 +1587,14 @@ impl<T: EventListener> Handler for Term<T> { // usually included. One option would be to use an inclusive // range, but instead we just let the open range end be 1 // higher. - let start = Line(top - 1); - let end = Line(bottom); + let start = Line(top as i32 - 1); + let end = Line(bottom as i32); trace!("Setting scrolling region: ({};{})", start, end); - self.scroll_region.start = min(start, self.screen_lines()); - self.scroll_region.end = min(end, self.screen_lines()); + let screen_lines = Line(self.screen_lines() as i32); + self.scroll_region.start = min(start, screen_lines); + self.scroll_region.end = min(end, screen_lines); self.goto(Line(0), Column(0)); } @@ -1732,14 +1681,14 @@ impl<T: EventListener> Handler for Term<T> { #[inline] fn text_area_size_pixels<W: io::Write>(&mut self, writer: &mut W) { - let width = self.cell_width * self.cols().0; - let height = self.cell_height * self.screen_lines().0; + let width = self.cell_width * self.columns(); + let height = self.cell_height * self.screen_lines(); let _ = write!(writer, "\x1b[4;{};{}t", height, width); } #[inline] fn text_area_size_chars<W: io::Write>(&mut self, writer: &mut W) { - let _ = write!(writer, "\x1b[8;{};{}t", self.screen_lines(), self.cols()); + let _ = write!(writer, "\x1b[8;{};{}t", self.screen_lines(), self.columns()); } } @@ -1776,12 +1725,8 @@ struct TabStops { impl TabStops { #[inline] - fn new(num_cols: Column) -> TabStops { - TabStops { - tabs: IndexRange::from(Column(0)..num_cols) - .map(|i| (*i as usize) % INITIAL_TABSTOPS == 0) - .collect::<Vec<bool>>(), - } + fn new(columns: usize) -> TabStops { + TabStops { tabs: (0..columns).map(|i| i % INITIAL_TABSTOPS == 0).collect() } } /// Remove all tabstops. @@ -1794,9 +1739,9 @@ impl TabStops { /// Increase tabstop capacity. #[inline] - fn resize(&mut self, num_cols: Column) { + fn resize(&mut self, columns: usize) { let mut index = self.tabs.len(); - self.tabs.resize_with(num_cols.0, || { + self.tabs.resize_with(columns, || { let is_tabstop = index % INITIAL_TABSTOPS == 0; index += 1; is_tabstop @@ -1829,19 +1774,10 @@ impl RenderableCursor { fn new<T>(term: &Term<T>) -> Self { // Cursor position. let vi_mode = term.mode().contains(TermMode::VI); - let mut point = if vi_mode { - term.vi_mode_cursor.point - } else { - let mut point = term.grid.cursor.point; - point.line += term.grid.display_offset(); - point - }; + let point = if vi_mode { term.vi_mode_cursor.point } else { term.grid.cursor.point }; // Cursor shape. - let shape = if !vi_mode - && (!term.mode().contains(TermMode::SHOW_CURSOR) || point.line >= term.screen_lines()) - { - point.line = Line(0); + let shape = if !vi_mode && !term.mode().contains(TermMode::SHOW_CURSOR) { CursorShape::Hidden } else { term.cursor_style().shape @@ -1856,7 +1792,7 @@ impl RenderableCursor { /// This contains all content required to render the current terminal view. pub struct RenderableContent<'a> { pub display_iter: DisplayIter<'a, Cell>, - pub selection: Option<SelectionRange<Line>>, + pub selection: Option<SelectionRange>, pub cursor: RenderableCursor, pub display_offset: usize, pub colors: &'a color::Colors, @@ -1869,7 +1805,7 @@ impl<'a> RenderableContent<'a> { display_iter: term.grid().display_iter(), display_offset: term.grid().display_offset(), cursor: RenderableCursor::new(term), - selection: term.visible_selection(), + selection: term.selection.as_ref().and_then(|s| s.to_range(term)), colors: &term.colors, mode: *term.mode(), } @@ -1917,8 +1853,9 @@ pub mod test { let mut term = Term::new(&Config::<()>::default(), size, ()); // Fill terminal with content. - for (line, text) in lines.iter().rev().enumerate() { - if !text.ends_with('\r') && line != 0 { + for (line, text) in lines.iter().enumerate() { + let line = Line(line as i32); + if !text.ends_with('\r') && line + 1 != lines.len() { term.grid[line][Column(num_cols - 1)].flags.insert(Flags::WRAPLINE); } @@ -1950,15 +1887,15 @@ mod tests { use crate::ansi::{self, CharsetIndex, Handler, StandardCharset}; use crate::config::MockConfig; use crate::grid::{Grid, Scroll}; - use crate::index::{Column, Line, Point, Side}; + use crate::index::{Column, Point, Side}; use crate::selection::{Selection, SelectionType}; use crate::term::cell::{Cell, Flags}; #[test] fn semantic_selection_works() { - let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); + let size = SizeInfo::new(5., 3., 1.0, 1.0, 0.0, 0.0, false); let mut term = Term::new(&MockConfig::default(), size, ()); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0); + let mut grid: Grid<Cell> = Grid::new(3, 5, 0); for i in 0..5 { for j in 0..2 { grid[Line(j)][Column(i)].c = 'a'; @@ -1977,7 +1914,7 @@ mod tests { { term.selection = Some(Selection::new( SelectionType::Semantic, - Point { line: 2, column: Column(1) }, + Point { line: Line(0), column: Column(1) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("aa"))); @@ -1986,7 +1923,7 @@ mod tests { { term.selection = Some(Selection::new( SelectionType::Semantic, - Point { line: 2, column: Column(4) }, + Point { line: Line(0), column: Column(4) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); @@ -1995,7 +1932,7 @@ mod tests { { term.selection = Some(Selection::new( SelectionType::Semantic, - Point { line: 1, column: Column(1) }, + Point { line: Line(1), column: Column(1) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); @@ -2004,9 +1941,9 @@ mod tests { #[test] fn line_selection_works() { - let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); + let size = SizeInfo::new(5., 1., 1.0, 1.0, 0.0, 0.0, false); let mut term = Term::new(&MockConfig::default(), size, ()); - let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0); + let mut grid: Grid<Cell> = Grid::new(1, 5, 0); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; } @@ -2017,7 +1954,7 @@ mod tests { term.selection = Some(Selection::new( SelectionType::Lines, - Point { line: 0, column: Column(3) }, + Point { line: Line(0), column: Column(3) }, Side::Left, )); assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n"))); @@ -2025,9 +1962,9 @@ mod tests { #[test] fn selecting_empty_line() { - let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); + let size = SizeInfo::new(3.0, 3.0, 1.0, 1.0, 0.0, 0.0, false); let mut term = Term::new(&MockConfig::default(), size, ()); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0); + let mut grid: Grid<Cell> = Grid::new(3, 3, 0); for l in 0..3 { if l != 1 { for c in 0..3 { @@ -2038,9 +1975,12 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - let mut selection = - Selection::new(SelectionType::Simple, Point { line: 2, column: Column(0) }, Side::Left); - selection.update(Point { line: 0, column: Column(2) }, Side::Right); + let mut selection = Selection::new( + SelectionType::Simple, + Point { line: Line(0), column: Column(0) }, + Side::Left, + ); + selection.update(Point { line: Line(2), column: Column(2) }, Side::Right); term.selection = Some(selection); assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into())); } @@ -2051,7 +1991,7 @@ mod tests { /// test this property with a T=Cell. #[test] fn grid_serde() { - let grid: Grid<Cell> = Grid::new(Line(24), Column(80), 0); + let grid: Grid<Cell> = Grid::new(24, 80, 0); let serialized = serde_json::to_string(&grid).expect("ser"); let deserialized = serde_json::from_str::<Grid<Cell>>(&serialized).expect("de"); @@ -2075,7 +2015,7 @@ mod tests { let mut term = Term::new(&MockConfig::default(), size, ()); // Add one line of scrollback. - term.grid.scroll_up(&(Line(0)..Line(1)), Line(1)); + term.grid.scroll_up(&(Line(0)..Line(1)), 1); // Clear the history. term.clear_screen(ansi::ClearMode::Saved); @@ -2104,7 +2044,7 @@ mod tests { assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Increase visible lines. - size.screen_lines.0 = 30; + size.screen_lines = 30; term.resize(size); assert_eq!(term.history_size(), 0); @@ -2127,7 +2067,7 @@ mod tests { term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); // Increase visible lines. - size.screen_lines.0 = 30; + size.screen_lines = 30; term.resize(size); // Leave alt screen. @@ -2150,7 +2090,7 @@ mod tests { assert_eq!(term.grid.cursor.point, Point::new(Line(9), Column(0))); // Increase visible lines. - size.screen_lines.0 = 5; + size.screen_lines = 5; term.resize(size); assert_eq!(term.history_size(), 15); @@ -2173,7 +2113,7 @@ mod tests { term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); // Increase visible lines. - size.screen_lines.0 = 5; + size.screen_lines = 5; term.resize(size); // Leave alt screen. diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index 0eba7567..638df670 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -1,4 +1,4 @@ -use std::cmp::min; +use std::cmp::max; use std::mem; use std::ops::RangeInclusive; @@ -12,7 +12,7 @@ use crate::term::Term; /// Used to match equal brackets, when performing a bracket-pair selection. const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')]; -pub type Match = RangeInclusive<Point<usize>>; +pub type Match = RangeInclusive<Point>; /// Terminal regex search state. #[derive(Clone, Debug)] @@ -53,7 +53,7 @@ impl<T> Term<T> { pub fn search_next( &self, dfas: &RegexSearch, - mut origin: Point<usize>, + mut origin: Point, direction: Direction, side: Side, mut max_lines: Option<usize>, @@ -72,7 +72,7 @@ impl<T> Term<T> { fn next_match_right( &self, dfas: &RegexSearch, - origin: Point<usize>, + origin: Point, side: Side, max_lines: Option<usize>, ) -> Option<Match> { @@ -80,13 +80,12 @@ impl<T> Term<T> { let mut end = start; // Limit maximum number of lines searched. - let total_lines = self.total_lines(); end = match max_lines { Some(max_lines) => { - let line = (start.line + total_lines - max_lines) % total_lines; - Point::new(line, self.cols() - 1) + let line = (start.line + max_lines).grid_clamp(self, Boundary::Grid); + Point::new(line, self.last_column()) }, - _ => end.sub_absolute(self, Boundary::Wrap, 1), + _ => end.sub(self, Boundary::None, 1), }; let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self, dfas).peekable(); @@ -99,8 +98,8 @@ impl<T> Term<T> { let match_point = Self::match_side(®ex_match, side); // If the match's point is beyond the origin, we're done. - match_point.line > start.line - || match_point.line < origin.line + match_point.line < start.line + || match_point.line > origin.line || (match_point.line == origin.line && match_point.column >= origin.column) }) .unwrap_or(first_match); @@ -112,7 +111,7 @@ impl<T> Term<T> { fn next_match_left( &self, dfas: &RegexSearch, - origin: Point<usize>, + origin: Point, side: Side, max_lines: Option<usize>, ) -> Option<Match> { @@ -121,8 +120,11 @@ impl<T> Term<T> { // Limit maximum number of lines searched. end = match max_lines { - Some(max_lines) => Point::new((start.line + max_lines) % self.total_lines(), Column(0)), - _ => end.add_absolute(self, Boundary::Wrap, 1), + Some(max_lines) => { + let line = (start.line - max_lines).grid_clamp(self, Boundary::Grid); + Point::new(line, Column(0)) + }, + _ => end.add(self, Boundary::None, 1), }; let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self, dfas).peekable(); @@ -135,8 +137,8 @@ impl<T> Term<T> { let match_point = Self::match_side(®ex_match, side); // If the match's point is beyond the origin, we're done. - match_point.line < start.line - || match_point.line > origin.line + match_point.line > start.line + || match_point.line < origin.line || (match_point.line == origin.line && match_point.column <= origin.column) }) .unwrap_or(first_match); @@ -145,7 +147,7 @@ impl<T> Term<T> { } /// Get the side of a match. - fn match_side(regex_match: &Match, side: Side) -> Point<usize> { + fn match_side(regex_match: &Match, side: Side) -> Point { match side { Side::Right => *regex_match.end(), Side::Left => *regex_match.start(), @@ -155,12 +157,7 @@ impl<T> Term<T> { /// Find the next regex match to the left of the origin point. /// /// The origin is always included in the regex. - pub fn regex_search_left( - &self, - dfas: &RegexSearch, - start: Point<usize>, - end: Point<usize>, - ) -> Option<Match> { + pub fn regex_search_left(&self, dfas: &RegexSearch, start: Point, end: Point) -> Option<Match> { // Find start and end of match. let match_start = self.regex_search(start, end, Direction::Left, &dfas.left_fdfa)?; let match_end = self.regex_search(match_start, start, Direction::Right, &dfas.left_rdfa)?; @@ -174,8 +171,8 @@ impl<T> Term<T> { pub fn regex_search_right( &self, dfas: &RegexSearch, - start: Point<usize>, - end: Point<usize>, + start: Point, + end: Point, ) -> Option<Match> { // Find start and end of match. let match_end = self.regex_search(start, end, Direction::Right, &dfas.right_fdfa)?; @@ -189,13 +186,14 @@ impl<T> Term<T> { /// This will always return the side of the first match which is farthest from the start point. fn regex_search( &self, - start: Point<usize>, - end: Point<usize>, + start: Point, + end: Point, direction: Direction, dfa: &impl DFA, - ) -> Option<Point<usize>> { - let last_line = self.total_lines() - 1; - let last_col = self.cols() - 1; + ) -> Option<Point> { + let topmost_line = self.topmost_line(); + let screen_lines = self.screen_lines() as i32; + let last_column = self.last_column(); // Advance the iterator. let next = match direction { @@ -250,7 +248,8 @@ impl<T> Term<T> { Some(Indexed { cell, .. }) => cell, None => { // Wrap around to other end of the scrollback buffer. - let start = Point::new(last_line - point.line, last_col - point.column); + let line = topmost_line - point.line + screen_lines - 1; + let start = Point::new(line, last_column - point.column); iter = self.grid.iter_from(start); iter.cell() }, @@ -262,8 +261,8 @@ impl<T> Term<T> { let last_point = mem::replace(&mut point, iter.point()); // Handle linebreaks. - if (last_point.column == last_col && point.column == Column(0) && !last_wrapped) - || (last_point.column == Column(0) && point.column == last_col && !wrapped) + if (last_point.column == last_column && point.column == Column(0) && !last_wrapped) + || (last_point.column == Column(0) && point.column == last_column && !wrapped) { match regex_match { Some(_) => break, @@ -299,7 +298,7 @@ impl<T> Term<T> { *cell = new_cell; } - let prev = iter.point().sub_absolute(self, Boundary::Clamp, 1); + let prev = iter.point().sub(self, Boundary::Grid, 1); if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) { iter.prev(); } @@ -309,8 +308,8 @@ impl<T> Term<T> { } /// Find next matching bracket. - pub fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> { - let start_char = self.grid[point.line][point.column].c; + pub fn bracket_search(&self, point: Point) -> Option<Point> { + let start_char = self.grid[point].c; // Find the matching bracket we're looking for let (forward, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| { @@ -353,12 +352,12 @@ impl<T> Term<T> { } /// Find left end of semantic block. - pub fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> { + pub fn semantic_search_left(&self, mut point: Point) -> Point { // Limit the starting point to the last line in the history - point.line = min(point.line, self.total_lines() - 1); + point.line = max(point.line, self.topmost_line()); let mut iter = self.grid.iter_from(point); - let last_col = self.cols() - Column(1); + let last_column = self.columns() - 1; let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; while let Some(cell) = iter.prev() { @@ -366,7 +365,7 @@ impl<T> Term<T> { break; } - if cell.point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) { + if cell.point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } @@ -377,12 +376,12 @@ impl<T> Term<T> { } /// Find right end of semantic block. - pub fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> { + pub fn semantic_search_right(&self, mut point: Point) -> Point { // Limit the starting point to the last line in the history - point.line = min(point.line, self.total_lines() - 1); + point.line = max(point.line, self.topmost_line()); let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; - let last_col = self.cols() - 1; + let last_column = self.columns() - 1; for cell in self.grid.iter_from(point) { if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) { @@ -391,7 +390,7 @@ impl<T> Term<T> { point = cell.point; - if point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) { + if point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } } @@ -400,11 +399,11 @@ impl<T> Term<T> { } /// Find the beginning of the current line across linewraps. - pub fn line_search_left(&self, mut point: Point<usize>) -> Point<usize> { - while point.line + 1 < self.total_lines() - && self.grid[point.line + 1][self.cols() - 1].flags.contains(Flags::WRAPLINE) + pub fn line_search_left(&self, mut point: Point) -> Point { + while point.line > self.topmost_line() + && self.grid[point.line - 1i32][self.last_column()].flags.contains(Flags::WRAPLINE) { - point.line += 1; + point.line -= 1; } point.column = Column(0); @@ -413,14 +412,14 @@ impl<T> Term<T> { } /// Find the end of the current line across linewraps. - pub fn line_search_right(&self, mut point: Point<usize>) -> Point<usize> { - while point.line > 0 - && self.grid[point.line][self.cols() - 1].flags.contains(Flags::WRAPLINE) + pub fn line_search_right(&self, mut point: Point) -> Point { + while point.line + 1 < self.screen_lines() + && self.grid[point.line][self.last_column()].flags.contains(Flags::WRAPLINE) { - point.line -= 1; + point.line += 1; } - point.column = self.cols() - 1; + point.column = self.last_column(); point } @@ -428,8 +427,8 @@ impl<T> Term<T> { /// Iterator over regex matches. pub struct RegexIter<'a, T> { - point: Point<usize>, - end: Point<usize>, + point: Point, + end: Point, direction: Direction, dfas: &'a RegexSearch, term: &'a Term<T>, @@ -438,8 +437,8 @@ pub struct RegexIter<'a, T> { impl<'a, T> RegexIter<'a, T> { pub fn new( - start: Point<usize>, - end: Point<usize>, + start: Point, + end: Point, direction: Direction, term: &'a Term<T>, dfas: &'a RegexSearch, @@ -452,8 +451,8 @@ impl<'a, T> RegexIter<'a, T> { self.point = self.term.expand_wide(self.point, self.direction); self.point = match self.direction { - Direction::Right => self.point.add_absolute(self.term, Boundary::Wrap, 1), - Direction::Left => self.point.sub_absolute(self.term, Boundary::Wrap, 1), + Direction::Right => self.point.add(self.term, Boundary::None, 1), + Direction::Left => self.point.sub(self.term, Boundary::None, 1), }; } @@ -498,7 +497,7 @@ impl<'a, T> Iterator for RegexIter<'a, T> { mod tests { use super::*; - use crate::index::Column; + use crate::index::{Column, Line}; use crate::term::test::mock_term; #[test] @@ -514,10 +513,10 @@ mod tests { // Check regex across wrapped and unwrapped lines. let dfas = RegexSearch::new("Ala.*123").unwrap(); - let start = Point::new(3, Column(0)); - let end = Point::new(0, Column(2)); - let match_start = Point::new(3, Column(0)); - let match_end = Point::new(2, Column(2)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(4), Column(2)); + let match_start = Point::new(Line(1), Column(0)); + let match_end = Point::new(Line(2), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); } @@ -534,10 +533,10 @@ mod tests { // Check regex across wrapped and unwrapped lines. let dfas = RegexSearch::new("Ala.*123").unwrap(); - let start = Point::new(0, Column(2)); - let end = Point::new(3, Column(0)); - let match_start = Point::new(3, Column(0)); - let match_end = Point::new(2, Column(2)); + let start = Point::new(Line(4), Column(2)); + let end = Point::new(Line(1), Column(0)); + let match_start = Point::new(Line(1), Column(0)); + let match_end = Point::new(Line(2), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -551,14 +550,14 @@ mod tests { // Greedy stopped at linebreak. let dfas = RegexSearch::new("Ala.*critty").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(1, Column(25)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(25)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); // Greedy stopped at dead state. let dfas = RegexSearch::new("Ala[^y]*critty").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(1, Column(15)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(15)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); } @@ -572,8 +571,8 @@ mod tests { "); let dfas = RegexSearch::new("nothing").unwrap(); - let start = Point::new(2, Column(0)); - let end = Point::new(0, Column(4)); + let start = Point::new(Line(2), Column(0)); + let end = Point::new(Line(0), Column(4)); assert_eq!(term.regex_search_right(&dfas, start, end), None); } @@ -587,8 +586,8 @@ mod tests { "); let dfas = RegexSearch::new("nothing").unwrap(); - let start = Point::new(0, Column(4)); - let end = Point::new(2, Column(0)); + let start = Point::new(Line(0), Column(4)); + let end = Point::new(Line(2), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), None); } @@ -602,10 +601,10 @@ mod tests { // Make sure the cell containing the linebreak is not skipped. let dfas = RegexSearch::new("te.*123").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(1, Column(0)); - let match_start = Point::new(1, Column(0)); - let match_end = Point::new(1, Column(9)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(9)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -619,9 +618,9 @@ mod tests { // Make sure the cell containing the linebreak is not skipped. let dfas = RegexSearch::new("te.*123").unwrap(); - let start = Point::new(1, Column(2)); - let end = Point::new(0, Column(9)); - let match_start = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(2)); + let end = Point::new(Line(1), Column(9)); + let match_start = Point::new(Line(1), Column(0)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end)); } @@ -631,8 +630,8 @@ mod tests { // Make sure dead state cell is skipped when reversing. let dfas = RegexSearch::new("alacrit").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(6)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(6)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); } @@ -642,10 +641,10 @@ mod tests { // Make sure the reverse DFA operates the same as a forward DFA. let dfas = RegexSearch::new("zoo").unwrap(); - let start = Point::new(0, Column(9)); - let end = Point::new(0, Column(0)); - let match_start = Point::new(0, Column(0)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(0), Column(9)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -654,13 +653,13 @@ mod tests { let term = mock_term("testвосибing"); let dfas = RegexSearch::new("te.*ing").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(11)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(11)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); let dfas = RegexSearch::new("te.*ing").unwrap(); - let start = Point::new(0, Column(11)); - let end = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(11)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } @@ -669,13 +668,13 @@ mod tests { let term = mock_term("a🦇x🦇"); let dfas = RegexSearch::new("[^ ]*").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(5)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(5)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); let dfas = RegexSearch::new("[^ ]*").unwrap(); - let start = Point::new(0, Column(5)); - let end = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(5)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } @@ -684,13 +683,13 @@ mod tests { let term = mock_term("🦇"); let dfas = RegexSearch::new("🦇").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(0, Column(1)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(1)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end)); let dfas = RegexSearch::new("🦇").unwrap(); - let start = Point::new(0, Column(1)); - let end = Point::new(0, Column(0)); + let start = Point::new(Line(0), Column(1)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start)); } @@ -703,15 +702,15 @@ mod tests { "); let dfas = RegexSearch::new("xxx").unwrap(); - let start = Point::new(0, Column(2)); - let end = Point::new(1, Column(2)); - let match_start = Point::new(1, Column(0)); + let start = Point::new(Line(0), Column(2)); + let end = Point::new(Line(1), Column(2)); + let match_start = Point::new(Line(1), Column(0)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end)); let dfas = RegexSearch::new("xxx").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(0, Column(0)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=match_end)); } @@ -724,17 +723,17 @@ mod tests { "); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(0, Column(0)); - let end = Point::new(1, Column(3)); - let match_start = Point::new(1, Column(0)); - let match_end = Point::new(1, Column(2)); + let start = Point::new(Line(1), Column(0)); + let end = Point::new(Line(0), Column(3)); + let match_start = Point::new(Line(0), Column(0)); + let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(1, Column(2)); - let end = Point::new(0, Column(0)); - let match_start = Point::new(0, Column(1)); - let match_end = Point::new(0, Column(3)); + let start = Point::new(Line(0), Column(2)); + let end = Point::new(Line(1), Column(0)); + let match_start = Point::new(Line(1), Column(1)); + let match_end = Point::new(Line(1), Column(3)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } @@ -745,34 +744,34 @@ mod tests { xxx \n\ 🦇xx\ "); - term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER); + term.grid[Line(0)][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(0, Column(3)); - let match_start = Point::new(1, Column(3)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(1), Column(3)); + let match_start = Point::new(Line(0), Column(3)); + let match_end = Point::new(Line(1), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(0, Column(3)); - let end = Point::new(1, Column(0)); - let match_start = Point::new(1, Column(3)); - let match_end = Point::new(0, Column(2)); + let start = Point::new(Line(1), Column(3)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(3)); + let match_end = Point::new(Line(1), Column(2)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(1, Column(0)); - let end = Point::new(0, Column(3)); - let match_start = Point::new(1, Column(2)); - let match_end = Point::new(0, Column(1)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(1), Column(3)); + let match_start = Point::new(Line(0), Column(2)); + let match_end = Point::new(Line(1), Column(1)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(0, Column(3)); - let end = Point::new(1, Column(0)); - let match_start = Point::new(1, Column(2)); - let match_end = Point::new(0, Column(1)); + let start = Point::new(Line(1), Column(3)); + let end = Point::new(Line(0), Column(0)); + let match_start = Point::new(Line(0), Column(2)); + let match_end = Point::new(Line(1), Column(1)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } } |