use crate::Cell;
pub const SHARED_STATE_MAGIC: u64 = 0x5343_4152_4142_5348;
pub trait TerminalStateReader {
fn cell(&self, row: usize, col: usize) -> Option<&Cell>;
fn cells(&self) -> &[Cell];
fn cursor_pos(&self) -> (u16, u16);
fn sequence(&self) -> u64;
fn is_valid(&self) -> bool;
fn dimensions(&self) -> (usize, usize);
fn is_dirty(&self) -> bool;
fn is_error_mode(&self) -> bool;
fn cell_index(&self, row: usize, col: usize) -> Option<usize> {
let (width, height) = self.dimensions();
if row >= height || col >= width {
None
} else {
Some(row * width + col)
}
}
fn iter_cells(&self) -> CellIterator<'_, Self>
where
Self: Sized,
{
CellIterator {
reader: self,
index: 0,
}
}
}
pub struct CellIterator<'a, R: TerminalStateReader> {
reader: &'a R,
index: usize,
}
impl<'a, R: TerminalStateReader> Iterator for CellIterator<'a, R> {
type Item = (usize, usize, &'a Cell);
fn next(&mut self) -> Option<Self::Item> {
let (width, _height) = self.reader.dimensions();
let cells = self.reader.cells();
if self.index >= cells.len() {
return None;
}
let row = self.index / width;
let col = self.index % width;
let cell = &cells[self.index];
self.index += 1;
Some((row, col, cell))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockState {
cells: alloc::vec::Vec<Cell>,
width: usize,
height: usize,
cursor: (u16, u16),
sequence: u64,
error_mode: bool,
}
impl TerminalStateReader for MockState {
fn cell(&self, row: usize, col: usize) -> Option<&Cell> {
self.cell_index(row, col)
.and_then(|idx| self.cells.get(idx))
}
fn cells(&self) -> &[Cell] {
&self.cells
}
fn cursor_pos(&self) -> (u16, u16) {
self.cursor
}
fn sequence(&self) -> u64 {
self.sequence
}
fn is_valid(&self) -> bool {
self.cells.len() == self.width * self.height
&& (self.cursor.0 as usize) < self.width
&& (self.cursor.1 as usize) < self.height
}
fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
fn is_dirty(&self) -> bool {
false
}
fn is_error_mode(&self) -> bool {
self.error_mode
}
}
#[test]
fn test_bounds_checking() {
let mock = MockState {
cells: alloc::vec![Cell::default(); 100],
width: 10,
height: 10,
cursor: (5, 5),
sequence: 42,
error_mode: false,
};
assert!(mock.cell(0, 0).is_some());
assert!(mock.cell(9, 9).is_some());
assert!(mock.cell(10, 0).is_none());
assert!(mock.cell(0, 10).is_none());
assert!(mock.cell(100, 100).is_none());
}
#[test]
fn test_validation() {
let valid = MockState {
cells: alloc::vec![Cell::default(); 100],
width: 10,
height: 10,
cursor: (5, 5),
sequence: 42,
error_mode: false,
};
assert!(valid.is_valid());
let invalid_cursor = MockState {
cells: alloc::vec![Cell::default(); 100],
width: 10,
height: 10,
cursor: (20, 5), sequence: 42,
error_mode: false,
};
assert!(!invalid_cursor.is_valid());
let invalid_size = MockState {
cells: alloc::vec![Cell::default(); 50], width: 10,
height: 10,
cursor: (5, 5),
sequence: 42,
error_mode: false,
};
assert!(!invalid_size.is_valid());
}
#[test]
fn test_iterator() {
let mut cells = alloc::vec![Cell::default(); 6];
for i in 0..6 {
cells[i].char_codepoint = (b'A' + i as u8) as u32;
}
let mock = MockState {
cells,
width: 3,
height: 2,
cursor: (0, 0),
sequence: 1,
error_mode: false,
};
let collected: alloc::vec::Vec<_> = mock.iter_cells().collect();
assert_eq!(collected.len(), 6);
assert_eq!(collected[0].0, 0); assert_eq!(collected[0].1, 0); assert_eq!(collected[0].2.char_codepoint, b'A' as u32);
assert_eq!(collected[5].0, 1); assert_eq!(collected[5].1, 2); assert_eq!(collected[5].2.char_codepoint, b'F' as u32);
}
}
#[cfg(test)]
extern crate alloc;