diff options
Diffstat (limited to 'alacritty_terminal/src/grid/mod.rs')
-rw-r--r-- | alacritty_terminal/src/grid/mod.rs | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs new file mode 100644 index 00000000..3a6bacf8 --- /dev/null +++ b/alacritty_terminal/src/grid/mod.rs @@ -0,0 +1,886 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A specialized 2d grid implementation optimized for use in a terminal. + +use std::cmp::{max, min, Ordering}; +use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo}; + +use crate::index::{self, Column, IndexRange, Line, Point}; +use crate::selection::Selection; + +mod row; +pub use self::row::Row; + +#[cfg(test)] +mod tests; + +mod storage; +use self::storage::Storage; + +const MIN_INIT_SIZE: usize = 1_000; + +/// Bidirection iterator +pub trait BidirectionalIterator: Iterator { + fn prev(&mut self) -> Option<Self::Item>; +} + +/// An item in the grid along with its Line and Column. +pub struct Indexed<T> { + pub inner: T, + pub line: Line, + pub column: Column, +} + +impl<T> Deref for Indexed<T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.inner + } +} + +impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> { + fn eq(&self, other: &Self) -> bool { + // Compare struct fields and check result of grid comparison + self.raw.eq(&other.raw) + && self.cols.eq(&other.cols) + && self.lines.eq(&other.lines) + && self.display_offset.eq(&other.display_offset) + && self.scroll_limit.eq(&other.scroll_limit) + && self.selection.eq(&other.selection) + && self.url_highlight.eq(&other.url_highlight) + } +} + +pub trait GridCell { + fn is_empty(&self) -> bool; + fn is_wrap(&self) -> bool; + fn set_wrap(&mut self, wrap: bool); +} + +/// Represents the terminal display contents +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Grid<T> { + /// Lines in the grid. Each row holds a list of cells corresponding to the + /// columns in that row. + raw: Storage<T>, + + /// Number of columns + cols: index::Column, + + /// Number of lines. + /// + /// Invariant: lines is equivalent to raw.len() + lines: index::Line, + + /// Offset of displayed area + /// + /// If the displayed region isn't at the bottom of the screen, it stays + /// stationary while more text is emitted. The scrolling implementation + /// updates this offset accordingly. + #[serde(default)] + display_offset: usize, + + /// An limit on how far back it's possible to scroll + #[serde(default)] + scroll_limit: usize, + + /// Selected region + #[serde(skip)] + pub selection: Option<Selection>, + + #[serde(default)] + max_scroll_limit: usize, + + /// Range for URL hover highlights + #[serde(default)] + pub url_highlight: Option<RangeInclusive<index::Linear>>, +} + +#[derive(Copy, Clone)] +pub enum Scroll { + Lines(isize), + PageUp, + PageDown, + Top, + Bottom, +} + +#[derive(Copy, Clone)] +pub enum ViewportPosition { + Visible(Line), + Above, + Below, +} + +impl<T: GridCell + Copy + Clone> Grid<T> { + pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> { + let raw = Storage::with_capacity(lines, Row::new(cols, &template)); + Grid { + raw, + cols, + lines, + display_offset: 0, + scroll_limit: 0, + selection: None, + max_scroll_limit: scrollback, + url_highlight: None, + } + } + + pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { + Point { line: self.visible_line_to_buffer(point.line), col: point.col } + } + + pub fn buffer_line_to_visible(&self, line: usize) -> ViewportPosition { + let offset = line.saturating_sub(self.display_offset); + if line < self.display_offset { + ViewportPosition::Below + } else if offset >= *self.num_lines() { + ViewportPosition::Above + } else { + ViewportPosition::Visible(self.lines - offset - 1) + } + } + + pub fn visible_line_to_buffer(&self, line: Line) -> usize { + self.line_to_offset(line) + self.display_offset + } + + /// Update the size of the scrollback history + pub fn update_history(&mut self, history_size: usize, template: &T) { + self.raw.update_history(history_size, Row::new(self.cols, &template)); + self.max_scroll_limit = history_size; + self.scroll_limit = min(self.scroll_limit, history_size); + self.display_offset = min(self.display_offset, self.scroll_limit); + } + + pub fn scroll_display(&mut self, scroll: Scroll) { + match scroll { + Scroll::Lines(count) => { + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit, + ); + }, + Scroll::PageUp => { + self.display_offset = min(self.display_offset + self.lines.0, self.scroll_limit); + }, + Scroll::PageDown => { + self.display_offset -= min(self.display_offset, self.lines.0); + }, + Scroll::Top => self.display_offset = self.scroll_limit, + Scroll::Bottom => self.display_offset = 0, + } + } + + pub fn resize( + &mut self, + lines: index::Line, + cols: index::Column, + cursor_pos: &mut Point, + template: &T, + ) { + // Check that there's actually work to do and return early if not + if lines == self.lines && cols == self.cols { + return; + } + + match self.lines.cmp(&lines) { + Ordering::Less => self.grow_lines(lines, template), + Ordering::Greater => self.shrink_lines(lines), + Ordering::Equal => (), + } + + match self.cols.cmp(&cols) { + Ordering::Less => self.grow_cols(cols, cursor_pos, template), + Ordering::Greater => self.shrink_cols(cols, template), + Ordering::Equal => (), + } + } + + fn increase_scroll_limit(&mut self, count: usize, template: &T) { + self.scroll_limit = min(self.scroll_limit + count, self.max_scroll_limit); + + // Initialize new lines when the history buffer is smaller than the scroll limit + let history_size = self.raw.len().saturating_sub(*self.lines); + if history_size < self.scroll_limit { + let new = min( + max(self.scroll_limit - history_size, MIN_INIT_SIZE), + self.max_scroll_limit - history_size, + ); + self.raw.initialize(new, Row::new(self.cols, template)); + } + } + + fn decrease_scroll_limit(&mut self, count: usize) { + self.scroll_limit = self.scroll_limit.saturating_sub(count); + } + + /// Add lines to the visible area + /// + /// Alacritty keeps the cursor at the bottom of the terminal as long as there + /// is scrollback available. Once scrollback is exhausted, new lines are + /// simply added to the bottom of the screen. + fn grow_lines(&mut self, new_line_count: index::Line, template: &T) { + let lines_added = new_line_count - self.lines; + + // Need to "resize" before updating buffer + self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); + self.lines = new_line_count; + + // Move existing lines up if there is no scrollback to fill new lines + if lines_added.0 > self.scroll_limit { + let scroll_lines = lines_added - self.scroll_limit; + self.scroll_up(&(Line(0)..new_line_count), scroll_lines, template); + } + + self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); + self.display_offset = self.display_offset.saturating_sub(*lines_added); + } + + fn grow_cols(&mut self, cols: index::Column, cursor_pos: &mut Point, template: &T) { + // Truncate all buffered lines + self.raw.grow_hidden(cols, template); + + let max_lines = self.lines.0 + self.max_scroll_limit; + + // Iterate backwards with indices for mutation during iteration + let mut i = self.raw.len(); + while i > 0 { + i -= 1; + + // Grow the current line if there's wrapped content available + while i >= 1 + && self.raw[i].len() < cols.0 + && self.raw[i].last().map(GridCell::is_wrap) == Some(true) + { + // Remove wrap flag before appending additional cells + if let Some(cell) = self.raw[i].last_mut() { + cell.set_wrap(false); + } + + // Append as many cells from the next line as possible + let len = min(self.raw[i - 1].len(), cols.0 - self.raw[i].len()); + let mut cells = self.raw[i - 1].front_split_off(len); + self.raw[i].append(&mut cells); + + if self.raw[i - 1].is_empty() { + // Remove following line if all cells have been drained + self.raw.remove(i - 1); + + if self.raw.len() < self.lines.0 || self.scroll_limit == 0 { + // Add new line and move lines up if we can't pull from history + self.raw.insert(0, Row::new(cols, template), max_lines); + cursor_pos.line = Line(cursor_pos.line.saturating_sub(1)); + } else { + // Make sure viewport doesn't move if line is outside of the visible area + if i < self.display_offset { + self.display_offset = self.display_offset.saturating_sub(1); + } + + // Remove one line from scrollback, since we just moved it to the viewport + self.scroll_limit = self.scroll_limit.saturating_sub(1); + self.display_offset = min(self.display_offset, self.scroll_limit); + i -= 1; + } + } else if let Some(cell) = self.raw[i].last_mut() { + // Set wrap flag if next line still has cells + cell.set_wrap(true); + } + } + + // Fill remaining cells + if self.raw[i].len() < cols.0 { + self.raw[i].grow(cols, template); + } + } + + self.cols = cols; + } + + fn shrink_cols(&mut self, cols: index::Column, template: &T) { + // Truncate all buffered lines + self.raw.shrink_hidden(cols); + + let max_lines = self.lines.0 + self.max_scroll_limit; + + // Iterate backwards with indices for mutation during iteration + let mut i = self.raw.len(); + while i > 0 { + i -= 1; + + if let Some(mut new_row) = self.raw[i].shrink(cols) { + // Set line as wrapped if cells got removed + if let Some(cell) = self.raw[i].last_mut() { + cell.set_wrap(true); + } + + if Some(true) == new_row.last().map(|c| c.is_wrap() && i >= 1) + && new_row.len() < cols.0 + { + // Make sure previous wrap flag doesn't linger around + if let Some(cell) = new_row.last_mut() { + cell.set_wrap(false); + } + + // Add removed cells to start of next row + self.raw[i - 1].append_front(new_row); + } else { + // Make sure viewport doesn't move if line is outside of the visible area + if i < self.display_offset { + self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); + } + + // Make sure new row is at least as long as new width + let occ = new_row.len(); + if occ < cols.0 { + new_row.append(&mut vec![*template; cols.0 - occ]); + } + let row = Row::from_vec(new_row, occ); + + // Add new row with all removed cells + self.raw.insert(i, row, max_lines); + + // Increase scrollback history + self.scroll_limit = min(self.scroll_limit + 1, self.max_scroll_limit); + + // Since inserted might exceed cols, we need to check the same line again + i += 1; + } + } + } + + self.cols = cols; + } + + /// Remove lines from the visible area + /// + /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the + /// bottom of the screen. This is achieved by pushing history "out the top" + /// of the terminal window. + /// + /// Alacritty takes the same approach. + fn shrink_lines(&mut self, target: index::Line) { + let prev = self.lines; + + self.selection = None; + self.url_highlight = None; + self.raw.rotate(*prev as isize - *target as isize); + self.raw.shrink_visible_lines(target); + self.lines = target; + } + + /// Convert a Line index (active region) to a buffer offset + /// + /// # Panics + /// + /// This method will panic if `Line` is larger than the grid dimensions + pub fn line_to_offset(&self, line: index::Line) -> usize { + assert!(line < self.num_lines()); + + *(self.num_lines() - line - 1) + } + + #[inline] + pub fn scroll_down( + &mut self, + region: &Range<index::Line>, + positions: index::Line, + template: &T, + ) { + // Whether or not there is a scrolling region active, as long as it + // starts at the top, we can do a full rotation which just involves + // changing the start index. + // + // To accomodate scroll regions, rows are reordered at the end. + if region.start == Line(0) { + // Rotate the entire line buffer. If there's a scrolling region + // active, the bottom lines are restored in the next step. + self.raw.rotate_up(*positions); + if let Some(ref mut selection) = self.selection { + selection.rotate(-(*positions as isize)); + } + self.url_highlight = None; + + self.decrease_scroll_limit(*positions); + + // Now, restore any scroll region lines + let lines = self.lines; + for i in IndexRange(region.end..lines) { + self.raw.swap_lines(i, i + positions); + } + + // Finally, reset recycled lines + for i in IndexRange(Line(0)..positions) { + self.raw[i].reset(&template); + } + } else { + // Subregion rotation + for line in IndexRange((region.start + positions)..region.end).rev() { + self.raw.swap_lines(line, line - positions); + } + + for line in IndexRange(region.start..(region.start + positions)) { + self.raw[line].reset(&template); + } + } + } + + /// scroll_up moves lines at the bottom towards the top + /// + /// This is the performance-sensitive part of scrolling. + pub fn scroll_up(&mut self, region: &Range<index::Line>, positions: index::Line, template: &T) { + if region.start == Line(0) { + // Update display offset when not pinned to active area + if self.display_offset != 0 { + self.display_offset = + min(self.display_offset + *positions, self.len() - self.num_lines().0); + } + + self.increase_scroll_limit(*positions, template); + + // Rotate the entire line buffer. If there's a scrolling region + // active, the bottom lines are restored in the next step. + self.raw.rotate(-(*positions as isize)); + if let Some(ref mut selection) = self.selection { + selection.rotate(*positions as isize); + } + self.url_highlight = None; + + // // This next loop swaps "fixed" lines outside of a scroll region + // // back into place after the rotation. The work is done in buffer- + // // space rather than terminal-space to avoid redundant + // // transformations. + let fixed_lines = *self.num_lines() - *region.end; + + for i in 0..fixed_lines { + self.raw.swap(i, i + *positions); + } + + // Finally, reset recycled lines + // + // Recycled lines are just above the end of the scrolling region. + for i in 0..*positions { + self.raw[i + fixed_lines].reset(&template); + } + } else { + // Subregion rotation + for line in IndexRange(region.start..(region.end - positions)) { + self.raw.swap_lines(line, line + positions); + } + + // Clear reused lines + for line in IndexRange((region.end - positions)..region.end) { + self.raw[line].reset(&template); + } + } + } + + // Completely reset the grid state + pub fn reset(&mut self, template: &T) { + // Explicitly purge all lines from history + let shrinkage = self.raw.len() - self.lines.0; + self.raw.shrink_lines(shrinkage); + self.clear_history(); + + // Reset all visible lines + for row in 0..self.raw.len() { + self.raw[row].reset(template); + } + + self.display_offset = 0; + self.selection = None; + self.url_highlight = None; + } +} + +#[allow(clippy::len_without_is_empty)] +impl<T> Grid<T> { + #[inline] + pub fn num_lines(&self) -> index::Line { + self.lines + } + + pub fn display_iter(&self) -> DisplayIter<'_, T> { + DisplayIter::new(self) + } + + #[inline] + pub fn num_cols(&self) -> index::Column { + self.cols + } + + pub fn clear_history(&mut self) { + self.scroll_limit = 0; + } + + #[inline] + pub fn scroll_limit(&self) -> usize { + self.scroll_limit + } + + /// Total number of lines in the buffer, this includes scrollback + visible lines + #[inline] + pub fn len(&self) -> usize { + self.raw.len() + } + + #[inline] + pub fn history_size(&self) -> usize { + self.raw.len().saturating_sub(*self.lines) + } + + /// This is used only for initializing after loading ref-tests + pub fn initialize_all(&mut self, template: &T) + where + T: Copy, + { + let history_size = self.raw.len().saturating_sub(*self.lines); + self.raw.initialize(self.max_scroll_limit - history_size, Row::new(self.cols, template)); + } + + /// This is used only for truncating before saving ref-tests + pub fn truncate(&mut self) { + self.raw.truncate(); + } + + pub fn iter_from(&self, point: Point<usize>) -> GridIterator<'_, T> { + GridIterator { grid: self, cur: point } + } + + #[inline] + pub fn contains(&self, point: &Point) -> bool { + self.lines > point.line && self.cols > point.col + } + + #[inline] + pub fn display_offset(&self) -> usize { + self.display_offset + } +} + +pub struct GridIterator<'a, T> { + /// Immutable grid reference + grid: &'a Grid<T>, + + /// Current position of the iterator within the grid. + pub cur: Point<usize>, +} + +impl<'a, T> Iterator for GridIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<Self::Item> { + let last_col = self.grid.num_cols() - Column(1); + match self.cur { + Point { line, col } if line == 0 && col == last_col => None, + Point { col, .. } if (col == last_col) => { + self.cur.line -= 1; + self.cur.col = Column(0); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + _ => { + self.cur.col += Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + } + } +} + +impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { + fn prev(&mut self) -> Option<Self::Item> { + let num_cols = self.grid.num_cols(); + + match self.cur { + Point { line, col: Column(0) } if line == self.grid.len() - 1 => None, + Point { col: Column(0), .. } => { + self.cur.line += 1; + self.cur.col = num_cols - Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + _ => { + self.cur.col -= Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + } + } +} + +/// Index active region by line +impl<T> Index<index::Line> for Grid<T> { + type Output = Row<T>; + + #[inline] + fn index(&self, index: index::Line) -> &Row<T> { + &self.raw[index] + } +} + +/// Index with buffer offset +impl<T> Index<usize> for Grid<T> { + type Output = Row<T>; + + #[inline] + fn index(&self, index: usize) -> &Row<T> { + &self.raw[index] + } +} + +impl<T> IndexMut<index::Line> for Grid<T> { + #[inline] + fn index_mut(&mut self, index: index::Line) -> &mut Row<T> { + &mut self.raw[index] + } +} + +impl<T> IndexMut<usize> for Grid<T> { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Row<T> { + &mut self.raw[index] + } +} + +impl<'point, T> Index<&'point Point> for Grid<T> { + type Output = T; + + #[inline] + fn index<'a>(&'a self, point: &Point) -> &'a T { + &self[point.line][point.col] + } +} + +impl<'point, T> IndexMut<&'point Point> for Grid<T> { + #[inline] + fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { + &mut self[point.line][point.col] + } +} + +// ------------------------------------------------------------------------------------------------- +// REGIONS +// ------------------------------------------------------------------------------------------------- + +/// A subset of lines in the grid +/// +/// May be constructed using Grid::region(..) +pub struct Region<'a, T> { + start: Line, + end: Line, + raw: &'a Storage<T>, +} + +/// A mutable subset of lines in the grid +/// +/// May be constructed using Grid::region_mut(..) +pub struct RegionMut<'a, T> { + start: Line, + end: Line, + raw: &'a mut Storage<T>, +} + +impl<'a, T> RegionMut<'a, T> { + /// Call the provided function for every item in this region + pub fn each<F: Fn(&mut T)>(self, func: F) { + for row in self { + for item in row { + func(item) + } + } + } +} + +pub trait IndexRegion<I, T> { + /// Get an immutable region of Self + fn region(&self, _: I) -> Region<'_, T>; + + /// Get a mutable region of Self + fn region_mut(&mut self, _: I) -> RegionMut<'_, T>; +} + +impl<T> IndexRegion<Range<Line>, T> for Grid<T> { + fn region(&self, index: Range<Line>) -> Region<'_, T> { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + Region { start: index.start, end: index.end, raw: &self.raw } + } + + fn region_mut(&mut self, index: Range<Line>) -> RegionMut<'_, T> { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + RegionMut { start: index.start, end: index.end, raw: &mut self.raw } + } +} + +impl<T> IndexRegion<RangeTo<Line>, T> for Grid<T> { + fn region(&self, index: RangeTo<Line>) -> Region<'_, T> { + assert!(index.end <= self.num_lines()); + Region { start: Line(0), end: index.end, raw: &self.raw } + } + + fn region_mut(&mut self, index: RangeTo<Line>) -> RegionMut<'_, T> { + assert!(index.end <= self.num_lines()); + RegionMut { start: Line(0), end: index.end, raw: &mut self.raw } + } +} + +impl<T> IndexRegion<RangeFrom<Line>, T> for Grid<T> { + fn region(&self, index: RangeFrom<Line>) -> Region<'_, T> { + assert!(index.start < self.num_lines()); + Region { start: index.start, end: self.num_lines(), raw: &self.raw } + } + + fn region_mut(&mut self, index: RangeFrom<Line>) -> RegionMut<'_, T> { + assert!(index.start < self.num_lines()); + RegionMut { start: index.start, end: self.num_lines(), raw: &mut self.raw } + } +} + +impl<T> IndexRegion<RangeFull, T> for Grid<T> { + fn region(&self, _: RangeFull) -> Region<'_, T> { + Region { start: Line(0), end: self.num_lines(), raw: &self.raw } + } + + fn region_mut(&mut self, _: RangeFull) -> RegionMut<'_, T> { + RegionMut { start: Line(0), end: self.num_lines(), raw: &mut self.raw } + } +} + +pub struct RegionIter<'a, T> { + end: Line, + cur: Line, + raw: &'a Storage<T>, +} + +pub struct RegionIterMut<'a, T> { + end: Line, + cur: Line, + raw: &'a mut Storage<T>, +} + +impl<'a, T> IntoIterator for Region<'a, T> { + type IntoIter = RegionIter<'a, T>; + type Item = &'a Row<T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIter { end: self.end, cur: self.start, raw: self.raw } + } +} + +impl<'a, T> IntoIterator for RegionMut<'a, T> { + type IntoIter = RegionIterMut<'a, T>; + type Item = &'a mut Row<T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIterMut { end: self.end, cur: self.start, raw: self.raw } + } +} + +impl<'a, T> Iterator for RegionIter<'a, T> { + type Item = &'a Row<T>; + + fn next(&mut self) -> Option<Self::Item> { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + Some(&self.raw[index]) + } else { + None + } + } +} + +impl<'a, T> Iterator for RegionIterMut<'a, T> { + type Item = &'a mut Row<T>; + + fn next(&mut self) -> Option<Self::Item> { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + unsafe { Some(&mut *(&mut self.raw[index] as *mut _)) } + } else { + None + } + } +} + +// ------------------------------------------------------------------------------------------------- +// DISPLAY ITERATOR +// ------------------------------------------------------------------------------------------------- + +/// Iterates over the visible area accounting for buffer transform +pub struct DisplayIter<'a, T> { + grid: &'a Grid<T>, + offset: usize, + limit: usize, + col: Column, + line: Line, +} + +impl<'a, T: 'a> DisplayIter<'a, T> { + pub fn new(grid: &'a Grid<T>) -> DisplayIter<'a, T> { + let offset = grid.display_offset + *grid.num_lines() - 1; + let limit = grid.display_offset; + let col = Column(0); + let line = Line(0); + + DisplayIter { grid, offset, col, limit, line } + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub fn column(&self) -> Column { + self.col + } + + pub fn line(&self) -> Line { + self.line + } +} + +impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { + type Item = Indexed<T>; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + // Return None if we've reached the end. + if self.offset == self.limit && self.grid.num_cols() == self.col { + return None; + } + + // Get the next item. + let item = Some(Indexed { + inner: self.grid.raw[self.offset][self.col], + line: self.line, + column: self.col, + }); + + // Update line/col to point to next item + self.col += 1; + if self.col == self.grid.num_cols() && self.offset != self.limit { + self.offset -= 1; + + self.col = Column(0); + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); + } + + item + } +} |