use crate::cell::Cell; /// A line of terminal cells. #[derive(Clone, Debug, Default, PartialEq)] pub struct Line { pub cells: Vec, pub dirty: bool, pub wrapped: bool, } impl Line { pub fn new(cols: usize) -> Self { Self { cells: vec![Cell::default(); cols], dirty: false, wrapped: false, } } pub fn clear(&mut self) { for cell in &mut self.cells { cell.reset(); } self.dirty = true; self.wrapped = false; } pub fn resize(&mut self, cols: usize) { if self.cells.len() < cols { self.cells.resize(cols, Cell::default()); } else if self.cells.len() > cols { self.cells.truncate(cols); } } } /// Terminal grid with scrollback buffer. pub struct Grid { pub lines: Vec, pub scrollback: Vec, pub cols: usize, pub rows: usize, pub scrollback_limit: usize, pub cursor_row: usize, pub cursor_col: usize, pub saved_cursor_row: usize, pub saved_cursor_col: usize, pub top_margin: usize, pub bottom_margin: usize, } impl Grid { pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self { Self { lines: (0..rows).map(|_| Line::new(cols)).collect(), scrollback: Vec::new(), cols, rows, scrollback_limit, cursor_row: 0, cursor_col: 0, saved_cursor_row: 0, saved_cursor_col: 0, top_margin: 0, bottom_margin: rows.saturating_sub(1), } } pub fn resize(&mut self, cols: usize, rows: usize) { let old_rows = self.lines.len(); for line in &mut self.lines { line.resize(cols); } if rows > old_rows { self.lines.reserve(rows - old_rows); for _ in old_rows..rows { self.lines.push(Line::new(cols)); } } else if rows < old_rows { let excess = old_rows - rows; let limit = self.scrollback_limit; for line in self.lines.drain(0..excess) { if self.scrollback.len() >= limit { self.scrollback.remove(0); } self.scrollback.push(line); } } self.cols = cols; self.rows = rows; self.cursor_row = self.cursor_row.min(rows.saturating_sub(1)); self.cursor_col = self.cursor_col.min(cols.saturating_sub(1)); self.top_margin = 0; self.bottom_margin = rows.saturating_sub(1); } pub fn clear_screen(&mut self) { for line in &mut self.lines { line.clear(); } } pub fn clear_line(&mut self, row: usize) { if let Some(line) = self.lines.get_mut(row) { line.clear(); } } pub fn clear_line_right(&mut self, row: usize, col: usize) { if let Some(line) = self.lines.get_mut(row) { for cell in &mut line.cells[col..] { cell.reset(); } line.dirty = true; } } pub fn clear_line_left(&mut self, row: usize, col: usize) { if let Some(line) = self.lines.get_mut(row) { for cell in &mut line.cells[..=col] { cell.reset(); } line.dirty = true; } } pub fn scroll_up(&mut self, n: usize) { let top = self.top_margin; let bottom = self.bottom_margin; let n = n.min(bottom.saturating_sub(top) + 1); for _ in 0..n { let removed = self.lines.remove(top); if top == 0 && self.scrollback.len() < self.scrollback_limit { self.scrollback.push(removed); } let mut new_line = Line::new(self.cols); new_line.dirty = true; self.lines.insert(bottom, new_line); } for i in top..=bottom { if let Some(line) = self.lines.get_mut(i) { line.dirty = true; } } } pub fn scroll_down(&mut self, n: usize) { let top = self.top_margin; let bottom = self.bottom_margin; let n = n.min(bottom.saturating_sub(top) + 1); for _ in 0..n { self.lines.remove(bottom); let mut new_line = Line::new(self.cols); new_line.dirty = true; self.lines.insert(top, new_line); } for i in top..=bottom { if let Some(line) = self.lines.get_mut(i) { line.dirty = true; } } } pub fn line(&self, row: usize) -> Option<&Line> { self.lines.get(row) } pub fn line_mut(&mut self, row: usize) -> Option<&mut Line> { self.lines.get_mut(row) } pub fn cell(&self, row: usize, col: usize) -> Option<&Cell> { self.lines.get(row)?.cells.get(col) } pub fn cell_mut(&mut self, row: usize, col: usize) -> Option<&mut Cell> { self.lines.get_mut(row)?.cells.get_mut(col) } }