use std::sync::Arc;
use crate::{editor::style::Style, utils::RingBuffer};
pub type GridCell = (String, Option<Arc<Style>>);
#[macro_export]
macro_rules! default_cell {
() => {
(" ".to_owned(), None)
};
}
#[derive(Clone)]
struct GridLine {
characters: Vec<GridCell>,
}
impl GridLine {
pub fn new(length: usize) -> GridLine {
GridLine { characters: vec![default_cell!(); length] }
}
}
pub struct CharacterGrid {
pub width: usize,
pub height: usize,
lines: RingBuffer<GridLine>,
}
impl CharacterGrid {
pub fn new((width, height): (usize, usize)) -> CharacterGrid {
CharacterGrid { width, height, lines: RingBuffer::new(height, GridLine::new(width)) }
}
pub fn resize(&mut self, (width, height): (usize, usize)) {
self.lines.resize(height, GridLine::new(width));
for line in &mut self.lines {
line.characters.resize(width, default_cell!());
}
self.width = width;
self.height = height;
}
pub fn clear(&mut self) {
self.set_all_characters(default_cell!());
}
pub fn get_cell(&self, x: usize, y: usize) -> Option<&GridCell> {
self.lines[y].characters.get(x)
}
pub fn get_cell_mut(&mut self, x: usize, y: usize) -> Option<&mut GridCell> {
self.lines[y].characters.get_mut(x)
}
pub fn set_all_characters(&mut self, value: GridCell) {
for line in &mut self.lines {
for ch in &mut line.characters {
*ch = value.clone()
}
}
}
pub fn row(&self, row_index: usize) -> Option<&[GridCell]> {
if row_index < self.height { Some(&self.lines[row_index].characters[..]) } else { None }
}
pub fn scroll_region(
&mut self,
top: usize,
bottom: usize,
left: usize,
right: usize,
rows: isize,
cols: isize,
) -> bool {
if top == 0 && bottom == self.height && left == 0 && right == self.width && cols == 0 {
self.lines.rotate(rows);
return true;
}
let mut top_to_bottom;
let mut bottom_to_top;
let y_iter: &mut dyn Iterator<Item = usize> = if rows > 0 {
top_to_bottom = (top as isize + rows) as usize..bottom;
&mut top_to_bottom
} else {
bottom_to_top = (top..(bottom as isize + rows) as usize).rev();
&mut bottom_to_top
};
for y in y_iter {
let dest_y = y as isize - rows;
let mut cols_left;
let mut cols_right;
if dest_y >= 0 && dest_y < self.height as isize {
let x_iter: &mut dyn Iterator<Item = usize> = if cols > 0 {
cols_left = (left as isize + cols) as usize..right;
&mut cols_left
} else {
cols_right = (left..(right as isize + cols) as usize).rev();
&mut cols_right
};
for x in x_iter {
let dest_x = ((x as isize) - cols) as usize;
let cell_data = self.get_cell(x, y).cloned();
if let Some(cell_data) = cell_data {
if let Some(dest_cell) = self.get_cell_mut(dest_x, dest_y as usize) {
*dest_cell = cell_data;
}
}
}
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::style::Colors;
use rand::*;
#[derive(Debug)]
struct Context {
none_colors: Colors,
size: (usize, usize),
x: usize,
y: usize,
}
impl Context {
fn new() -> Self {
let size = (rng().random_range(1..=500), rng().random_range(1..=500));
let (x, y) = (rng().random_range(0..size.0), rng().random_range(0..size.1));
Self {
none_colors: Colors { foreground: None, background: None, special: None },
size,
x,
y,
}
}
}
fn set_grid_line_to_chars(grid: &mut CharacterGrid, row: usize, value: &str) {
assert_eq!(value.len(), grid.width);
for (col_nr, chr) in value.chars().enumerate() {
*grid.get_cell_mut(col_nr, row).unwrap() = (chr.to_string(), None);
}
}
fn assert_all_cells_equal_to(context: &Context, grid: &CharacterGrid, cell: &GridCell) {
for x in 0..context.size.0 {
for y in 0..context.size.1 {
assert_eq!(grid.get_cell(x, y), Some(cell));
}
}
}
fn assert_grid_cell_contents(grid: &CharacterGrid, x: usize, y: usize, char: &str) {
let char = char.to_string();
let value = (char, None);
let cell = Some(&value);
assert_eq!(grid.get_cell(x, y), cell);
}
fn create_initialized_grid(lines: &[&str]) -> CharacterGrid {
let num_lines = lines.len();
assert_ne!(num_lines, 0);
let line_lengths: Vec<usize> = lines.iter().map(|s| s.len()).collect();
let num_columns = line_lengths[0];
assert_eq!(line_lengths, vec![num_columns; num_lines]);
let mut grid = CharacterGrid::new((num_columns, num_lines));
for (row_nr, line) in lines.iter().enumerate() {
for (col_nr, chr) in line.chars().enumerate() {
*grid.get_cell_mut(col_nr, row_nr).unwrap() = (chr.to_string(), None);
}
}
grid
}
#[test]
fn new_constructs_grid() {
let context = Context::new();
let character_grid = CharacterGrid::new(context.size);
assert_eq!(character_grid.width, context.size.0);
assert_eq!(character_grid.height, context.size.1);
assert_all_cells_equal_to(&context, &character_grid, &default_cell!());
}
#[test]
fn get_cell_returns_expected_cell() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
character_grid.lines[context.y].characters[context.x] =
("foo".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
let result = ("foo".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
assert_eq!(character_grid.get_cell(context.x, context.y).unwrap(), &result);
}
#[test]
fn get_cell_mut_modifiers_grid_properly() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
character_grid.lines[context.y].characters[context.x] =
("foo".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
let result = ("bar".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
let cell = character_grid.get_cell_mut(context.x, context.y).unwrap();
*cell = ("bar".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
assert_eq!(character_grid.get_cell_mut(context.x, context.y).unwrap(), &result);
}
#[test]
fn set_all_characters_sets_all_cells_to_given_character() {
let context = Context::new();
let grid_cell =
("foo".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
let mut character_grid = CharacterGrid::new(context.size);
character_grid.set_all_characters(grid_cell.clone());
assert_all_cells_equal_to(&context, &character_grid, &grid_cell);
}
#[test]
fn clear_empties_buffer() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
let grid_cell =
("foo".to_string(), Some(Arc::new(Style::new(context.none_colors.clone()))));
character_grid.set_all_characters(grid_cell.clone());
character_grid.clear();
assert_eq!(character_grid.width, context.size.0);
assert_eq!(character_grid.height, context.size.1);
assert_all_cells_equal_to(&context, &character_grid, &default_cell!());
}
#[test]
fn resize_clears_and_resizes_grid() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
let (width, height) = (rng().random_range(1..=500), rng().random_range(1..=500));
let grid_cell = ("foo".to_string(), Some(Arc::new(Style::new(context.none_colors))));
character_grid.set_all_characters(grid_cell.clone());
character_grid.resize((width, height));
assert_eq!(character_grid.width, width);
assert_eq!(character_grid.height, height);
let (original_width, original_height) = context.size;
for x in 0..original_width.min(width) {
for y in 0..original_height.min(height) {
assert_eq!(character_grid.get_cell(x, y).unwrap(), &grid_cell);
}
}
for x in original_width..width {
for y in original_height..height {
assert_eq!(character_grid.get_cell(x, y).unwrap(), &default_cell!());
}
}
}
#[test]
fn scroll_down_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(0, 4, 0, 4, 2, 0);
assert_grid_cell_contents(&grid, 0, 0, "i");
assert_grid_cell_contents(&grid, 3, 0, "l");
assert_grid_cell_contents(&grid, 0, 1, "m");
}
#[test]
fn scroll_up_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(0, 4, 0, 4, -2, 0);
assert_grid_cell_contents(&grid, 0, 2, "a");
assert_grid_cell_contents(&grid, 0, 3, "e");
assert_grid_cell_contents(&grid, 3, 3, "h");
}
#[test]
fn partial_scroll_lines_down_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(1, 3, 0, 4, 1, 0);
assert_grid_cell_contents(&grid, 0, 0, "a");
assert_grid_cell_contents(&grid, 0, 1, "i");
assert_grid_cell_contents(&grid, 3, 1, "l");
assert_grid_cell_contents(&grid, 0, 3, "m");
}
#[test]
fn partial_scroll_lines_up_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(1, 3, 0, 4, -1, 0);
assert_grid_cell_contents(&grid, 0, 0, "a");
assert_grid_cell_contents(&grid, 0, 2, "e");
assert_grid_cell_contents(&grid, 3, 2, "h");
assert_grid_cell_contents(&grid, 0, 3, "m");
}
#[test]
fn scroll_left_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(0, 4, 0, 4, 0, 1);
assert_grid_cell_contents(&grid, 0, 0, "b");
assert_grid_cell_contents(&grid, 2, 2, "l");
}
#[test]
fn scroll_right_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(0, 4, 0, 4, 0, -3);
assert_grid_cell_contents(&grid, 3, 0, "a");
assert_grid_cell_contents(&grid, 3, 3, "m");
}
#[test]
fn scroll_inner_box_diagonally_moves_the_grid_correctly() {
let mut grid = create_initialized_grid(["abcd", "efgh", "ijkl", "mnop"].as_ref());
grid.scroll_region(1, 3, 1, 3, 1, 1);
assert_grid_cell_contents(&grid, 0, 0, "a");
assert_grid_cell_contents(&grid, 1, 0, "b");
assert_grid_cell_contents(&grid, 0, 1, "e");
assert_grid_cell_contents(&grid, 1, 1, "k");
assert_grid_cell_contents(&grid, 3, 1, "h");
assert_grid_cell_contents(&grid, 0, 3, "m");
}
#[test]
fn scrolling_one_screen_down_works() {
let mut grid = create_initialized_grid(["1", "2", "3", "4"].as_ref());
grid.scroll_region(0, 4, 0, 1, 4, 0);
set_grid_line_to_chars(&mut grid, 0, "5");
set_grid_line_to_chars(&mut grid, 1, "6");
set_grid_line_to_chars(&mut grid, 2, "7");
set_grid_line_to_chars(&mut grid, 3, "8");
}
#[test]
fn scrolling_more_than_one_screen_down_works_makes_a_small_jump() {
let mut grid = create_initialized_grid(["1", "2", "3", "4"].as_ref());
grid.scroll_region(0, 4, 0, 1, 4, 0);
set_grid_line_to_chars(&mut grid, 0, "5");
set_grid_line_to_chars(&mut grid, 1, "6");
set_grid_line_to_chars(&mut grid, 2, "7");
set_grid_line_to_chars(&mut grid, 3, "8");
}
#[test]
fn scrolling_one_screen_up_works() {
let mut grid = create_initialized_grid(["5", "6", "7", "8"].as_ref());
grid.scroll_region(0, 4, 0, 1, -4, 0);
set_grid_line_to_chars(&mut grid, 0, "1");
set_grid_line_to_chars(&mut grid, 1, "2");
set_grid_line_to_chars(&mut grid, 2, "3");
set_grid_line_to_chars(&mut grid, 3, "4");
}
#[test]
fn scrolling_more_than_one_screen_up_works_makes_a_small_jump() {
let mut grid = create_initialized_grid(["5", "6", "7", "8"].as_ref());
grid.scroll_region(0, 4, 0, 1, -4, 0);
set_grid_line_to_chars(&mut grid, 0, "1");
set_grid_line_to_chars(&mut grid, 1, "2");
set_grid_line_to_chars(&mut grid, 2, "3");
set_grid_line_to_chars(&mut grid, 3, "4");
}
}