pub mod resize;
pub mod row;
pub mod storage;
#[cfg(test)]
mod tests;
use crate::crosswords::pos::Pos;
use crate::crosswords::Cursor;
use crate::crosswords::{Column, Line};
use row::Row;
use std::cmp::{max, min};
use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds};
use storage::Storage;
#[derive(Debug, Copy, Clone)]
pub enum Scroll {
Delta(i32),
PageUp,
PageDown,
Top,
Bottom,
}
pub trait GridSquare: Sized {
fn is_empty(&self) -> bool;
fn reset(&mut self, template: &Self);
}
#[derive(Debug, Clone)]
pub struct Grid<T> {
pub cursor: Cursor<T>,
pub saved_cursor: Cursor<T>,
pub raw: Storage<T>,
columns: usize,
lines: usize,
display_offset: usize,
max_scroll_limit: usize,
total_lines_scrolled: u64,
pub style_set: crate::crosswords::style::StyleSet,
pub extras_table: ExtrasTable,
pub track_reflow_remap: bool,
pub reflow_remap: Option<ReflowRemap>,
}
#[derive(Debug, Clone)]
pub struct ReflowRemap {
pub base_abs: u64,
pub new_pos: Vec<i64>,
}
impl ReflowRemap {
pub fn remap_abs(&self, abs: i64) -> Option<i64> {
let pos = abs - self.base_abs as i64;
if pos < 0 {
return Some(abs);
}
let new = *self.new_pos.get(pos as usize)?;
if new < 0 {
return None;
}
Some(self.base_abs as i64 + new)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ExtrasTable {
slots: Vec<Option<crate::crosswords::square::Extras>>,
free: Vec<u16>,
allocs_since_reclaim: usize,
}
const EXTRAS_RECLAIM_CADENCE: usize = 4096;
impl ExtrasTable {
pub fn new() -> Self {
Self {
slots: vec![None],
free: Vec::new(),
allocs_since_reclaim: 0,
}
}
pub fn get(
&self,
id: crate::crosswords::square::ExtrasId,
) -> Option<&crate::crosswords::square::Extras> {
self.slots.get(id as usize)?.as_ref()
}
pub fn get_mut(
&mut self,
id: crate::crosswords::square::ExtrasId,
) -> Option<&mut crate::crosswords::square::Extras> {
self.slots.get_mut(id as usize)?.as_mut()
}
pub fn alloc(
&mut self,
extras: crate::crosswords::square::Extras,
) -> crate::crosswords::square::ExtrasId {
self.allocs_since_reclaim += 1;
if let Some(id) = self.free.pop() {
self.slots[id as usize] = Some(extras);
return id;
}
if self.slots.len() >= u16::MAX as usize {
tracing::warn!("ExtrasTable hit u16::MAX slots; dropping new extras");
return 0;
}
let id = self.slots.len() as u16;
self.slots.push(Some(extras));
id
}
pub fn under_pressure(&self) -> bool {
self.slots.len() >= u16::MAX as usize && self.free.len() < 256
}
pub fn should_reclaim(&self) -> bool {
self.allocs_since_reclaim >= EXTRAS_RECLAIM_CADENCE || self.under_pressure()
}
pub(crate) fn reset_reclaim_cadence(&mut self) {
self.allocs_since_reclaim = 0;
}
pub fn sweep_unmarked(&mut self, live: &[u64]) {
for id in 1..self.slots.len() {
let marked = live[id / 64] & (1 << (id % 64)) != 0;
if !marked && self.slots[id].is_some() {
self.slots[id] = None;
self.free.push(id as u16);
}
}
}
pub fn free(&mut self, id: crate::crosswords::square::ExtrasId) {
if id == 0 {
return;
}
if let Some(slot) = self.slots.get_mut(id as usize) {
if slot.take().is_some() {
self.free.push(id);
}
}
}
pub fn clear(&mut self) {
self.slots.clear();
self.slots.push(None);
self.free.clear();
}
}
impl<T: GridSquare + Default + PartialEq + Clone> Grid<T> {
pub fn new(lines: usize, columns: usize, max_scroll_limit: usize) -> Grid<T> {
Grid {
raw: Storage::with_capacity(lines, columns),
max_scroll_limit,
total_lines_scrolled: 0,
track_reflow_remap: false,
reflow_remap: None,
display_offset: 0,
saved_cursor: Cursor::default(),
cursor: Cursor::default(),
lines,
columns,
style_set: crate::crosswords::style::StyleSet::new(),
extras_table: ExtrasTable::new(),
}
}
pub fn update_history(&mut self, history_size: usize) {
let current_history_size = self.history_size();
if current_history_size > history_size {
let dropped = current_history_size - history_size;
self.total_lines_scrolled += dropped as u64;
self.raw.shrink_lines(dropped);
}
self.display_offset = min(self.display_offset, history_size);
self.max_scroll_limit = history_size;
}
pub fn scroll_display(&mut self, scroll: Scroll) {
self.display_offset = match scroll {
Scroll::Delta(count) => min(
max((self.display_offset as i32) + count, 0) as usize,
self.history_size(),
),
Scroll::PageUp => min(self.display_offset + self.lines, self.history_size()),
Scroll::PageDown => self.display_offset.saturating_sub(self.lines),
Scroll::Top => self.history_size(),
Scroll::Bottom => 0,
};
}
fn increase_scroll_limit(&mut self, count: usize) {
let count = min(count, self.max_scroll_limit - self.history_size());
if count != 0 {
self.raw.initialize(count, self.columns);
}
}
fn decrease_scroll_limit(&mut self, count: usize) {
let count = min(count, self.history_size());
if count != 0 {
self.raw.shrink_lines(min(count, self.history_size()));
self.display_offset = min(self.display_offset, self.history_size());
}
}
#[inline]
pub fn scroll_down(&mut self, region: &Range<Line>, positions: usize) {
if region.end - region.start <= positions {
for i in (region.start.0..region.end.0).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
return;
}
if self.max_scroll_limit == 0 {
let screen_lines = self.screen_lines() as i32;
for i in (region.end.0..screen_lines).map(Line::from) {
self.raw.swap(i, i - positions as i32);
}
self.raw.rotate_down(positions);
for i in (0..positions).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
for i in (0..region.start.0).map(Line::from) {
self.raw.swap(i, i + positions);
}
} else {
let range = (region.start + positions).0..region.end.0;
for line in range.rev().map(Line::from) {
self.raw.swap(line, line - positions);
}
let range = region.start.0..(region.start + positions).0;
for line in range.rev().map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
for i in (region.start.0..region.end.0).map(Line::from) {
self.raw[i].dirty = true;
}
}
pub fn cursor_square(&mut self) -> &mut T {
let pos = &self.cursor.pos;
&mut self.raw[pos.row][pos.col]
}
pub fn scroll_up(&mut self, region: &Range<Line>, positions: usize) {
if region.end - region.start <= positions && region.start != 0 {
for i in (region.start.0..region.end.0).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
return;
}
if self.display_offset != 0 {
self.display_offset =
min(self.display_offset + positions, self.max_scroll_limit);
}
if region.start == 0 {
let grown = min(positions, self.max_scroll_limit - self.history_size());
self.total_lines_scrolled += (positions - grown) as u64;
self.increase_scroll_limit(positions);
for i in (0..region.start.0).rev().map(Line::from) {
self.raw.swap(i, i + positions);
}
self.raw.rotate(-(positions as isize));
let screen_lines = self.screen_lines() as i32;
for i in (region.end.0..screen_lines).rev().map(Line::from) {
self.raw.swap(i, i - positions);
}
} else {
for i in (region.start.0..region.end.0 - positions as i32).map(Line::from) {
self.raw.swap(i, i + positions);
}
}
for i in (region.end.0 - positions as i32..region.end.0).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
for i in (region.start.0..region.end.0).map(Line::from) {
self.raw[i].dirty = true;
}
}
pub fn clear_viewport(&mut self) {
let end = Pos::new(Line(self.lines as i32 - 1), Column(self.columns()));
let mut iter = self.iter_from(end);
while let Some(square) = iter.prev() {
if !square.is_empty() || square.pos.row < 0 {
break;
}
}
debug_assert!(iter.current.row >= -1);
let positions = (iter.current.row.0 + 1) as usize;
let region = Line(0)..Line(self.lines as i32);
self.scroll_up(®ion, positions);
for line in (0..(self.lines - positions)).map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
pub fn reset(&mut self) {
self.clear_history();
self.saved_cursor = Cursor::default();
self.cursor = Cursor::default();
self.display_offset = 0;
let range = self.topmost_line().0..(self.screen_lines() as i32);
for line in range.map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
}
impl<T> Grid<T> {
pub fn reset_region<R: RangeBounds<Line>>(&mut self, bounds: R)
where
T: GridSquare + Clone + Default,
{
let start = match bounds.start_bound() {
Bound::Included(line) => *line,
Bound::Excluded(line) => *line + 1,
Bound::Unbounded => Line(0),
};
let end = match bounds.end_bound() {
Bound::Included(line) => *line + 1,
Bound::Excluded(line) => *line,
Bound::Unbounded => Line(self.screen_lines() as i32),
};
debug_assert!(start < self.screen_lines() as i32);
debug_assert!(end <= self.screen_lines() as i32);
for line in (start.0..end.0).map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
#[inline]
pub fn lines_evicted(&self) -> u64 {
self.total_lines_scrolled
}
pub fn clear_history(&mut self) {
self.total_lines_scrolled += self.history_size() as u64;
self.raw.shrink_lines(self.history_size());
self.display_offset = 0;
}
#[inline]
#[allow(unused)]
pub fn initialize_all(&mut self)
where
T: GridSquare + Clone + Default,
{
self.truncate();
self.raw
.initialize(self.max_scroll_limit - self.history_size(), self.columns);
}
#[inline]
#[allow(unused)]
pub fn truncate(&mut self) {
self.raw.truncate();
}
#[inline]
pub fn iter_from(&self, current: Pos) -> GridIterator<'_, T> {
let end = Pos::new(self.bottommost_line(), self.last_column());
GridIterator {
grid: self,
current,
end,
}
}
#[inline]
#[allow(unused)]
pub fn display_iter(&self) -> GridIterator<'_, T> {
let last_column = self.last_column();
let start = Pos::new(Line(-(self.display_offset() as i32) - 1), last_column);
let end_line = min(start.row + self.screen_lines(), self.bottommost_line());
let end = Pos::new(end_line, last_column);
GridIterator {
grid: self,
current: start,
end,
}
}
#[inline]
pub fn display_offset(&self) -> usize {
self.display_offset
}
#[inline]
pub fn cursor_cell(&mut self) -> &mut T {
let point = self.cursor.pos;
&mut self[point.row][point.col]
}
}
use crate::crosswords::square::Square;
use crate::crosswords::style::{Style, StyleId};
impl Grid<Square> {
pub fn alloc_extras(
&mut self,
extras: crate::crosswords::square::Extras,
) -> crate::crosswords::square::ExtrasId {
if self.extras_table.should_reclaim() {
self.reclaim_extras();
}
self.extras_table.alloc(extras)
}
pub fn reclaim_extras(&mut self) {
self.extras_table.reset_reclaim_cadence();
#[inline]
fn mark(live: &mut [u64], sq: &Square) {
if matches!(
sq.content_tag(),
crate::crosswords::square::ContentTag::Codepoint
) {
if let Some(eid) = sq.extras_id() {
live[eid as usize / 64] |= 1 << (eid % 64);
}
}
}
let mut live = vec![0u64; (u16::MAX as usize).div_ceil(64)];
for l in self.topmost_line().0..=self.bottommost_line().0 {
let row = &self.raw[Line(l)];
if !row.has_extras {
continue;
}
for sq in &row.inner {
mark(&mut live, sq);
}
}
mark(&mut live, &self.cursor.template);
mark(&mut live, &self.saved_cursor.template);
self.extras_table.sweep_unmarked(&live);
}
#[inline]
pub fn style_of(&self, square: &Square) -> Style {
self.style_set.get(square.style_id())
}
#[inline]
pub fn template_style_id(&self) -> StyleId {
self.cursor.template.style_id()
}
#[inline]
pub fn set_template_style_id(&mut self, id: StyleId) {
self.cursor.template.set_style_id(id);
}
#[inline]
pub fn update_template_style(&mut self, f: impl FnOnce(&mut Style)) {
let mut s = self.style_set.get(self.cursor.template.style_id());
f(&mut s);
let id = self.style_set.intern(s);
self.cursor.template.set_style_id(id);
}
#[inline]
pub fn set_template_style(&mut self, style: Style) {
let id = self.style_set.intern(style);
self.cursor.template.set_style_id(id);
}
#[inline]
pub fn blank_with_bg(&mut self, bg: crate::config::colors::AnsiColor) -> Square {
use crate::config::colors::{AnsiColor, NamedColor};
let mut cell = Square::default();
match bg {
AnsiColor::Named(NamedColor::Background) => return cell,
AnsiColor::Indexed(idx) => {
cell.set_bg_palette(idx);
return cell;
}
AnsiColor::Spec(rgb) => {
cell.set_bg_rgb(rgb.r, rgb.g, rgb.b);
return cell;
}
AnsiColor::Named(named) => {
let n = named as u16;
if n < 16 {
cell.set_bg_palette(n as u8);
return cell;
}
}
}
let style = Style {
bg,
..Style::default()
};
let id = self.style_set.intern(style);
Square::default().with_style_id(id)
}
}
impl<T: PartialEq> PartialEq for Grid<T> {
fn eq(&self, other: &Self) -> bool {
self.raw.eq(&other.raw)
&& self.columns.eq(&other.columns)
&& self.lines.eq(&other.lines)
&& self.display_offset.eq(&other.display_offset)
}
}
impl<T> Index<Line> for Grid<T> {
type Output = Row<T>;
#[inline]
fn index(&self, index: Line) -> &Row<T> {
&self.raw[index]
}
}
impl<T> IndexMut<Line> for Grid<T> {
#[inline]
fn index_mut(&mut self, index: Line) -> &mut Row<T> {
&mut self.raw[index]
}
}
impl<T> Index<Pos> for Grid<T> {
type Output = T;
#[inline]
fn index(&self, pos: Pos) -> &T {
&self[pos.row][pos.col]
}
}
impl<T> IndexMut<Pos> for Grid<T> {
#[inline]
fn index_mut(&mut self, pos: Pos) -> &mut T {
&mut self[pos.row][pos.col]
}
}
pub trait Dimensions {
fn total_lines(&self) -> usize;
fn screen_lines(&self) -> usize;
fn columns(&self) -> usize;
#[inline]
fn last_column(&self) -> Column {
Column(self.columns() - 1)
}
#[inline]
fn topmost_line(&self) -> Line {
Line(-(self.history_size() as i32))
}
#[inline]
fn bottommost_line(&self) -> Line {
Line(self.screen_lines() as i32 - 1)
}
#[inline]
fn history_size(&self) -> usize {
self.total_lines().saturating_sub(self.screen_lines())
}
#[inline]
fn square_height(&self) -> f32 {
0.0
}
#[inline]
fn square_width(&self) -> f32 {
0.0
}
}
impl<G> Dimensions for Grid<G> {
#[inline]
fn total_lines(&self) -> usize {
self.raw.len()
}
#[inline]
fn screen_lines(&self) -> usize {
self.lines
}
#[inline]
fn columns(&self) -> usize {
self.columns
}
#[inline]
fn square_width(&self) -> f32 {
0.
}
#[inline]
fn square_height(&self) -> f32 {
0.
}
}
#[cfg(test)]
impl Dimensions for (usize, usize) {
fn total_lines(&self) -> usize {
self.0
}
fn screen_lines(&self) -> usize {
self.0
}
fn columns(&self) -> usize {
self.1
}
fn square_width(&self) -> f32 {
0.
}
fn square_height(&self) -> f32 {
0.
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Indexed<T> {
pub pos: Pos,
pub square: T,
}
impl<T> Deref for Indexed<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.square
}
}
pub struct GridIterator<'a, T> {
grid: &'a Grid<T>,
current: Pos,
end: Pos,
}
impl<'a, T> GridIterator<'a, T> {
#[allow(unused)]
pub fn pos(&self) -> Pos {
self.current
}
#[allow(unused)]
pub fn square(&self) -> &'a T {
&self.grid[self.current]
}
}
impl<'a, T> Iterator for GridIterator<'a, T> {
type Item = Indexed<&'a T>;
fn next(&mut self) -> Option<Self::Item> {
if self.current >= self.end {
return None;
}
match self.current {
Pos { col, .. } if col == self.grid.last_column() => {
self.current.col = Column(0);
self.current.row += 1;
}
_ => self.current.col += Column(1),
}
let screen_lines = self.grid.screen_lines() as i32;
let history = (self.grid.total_lines() - self.grid.screen_lines()) as i32;
if self.current.row.0 >= screen_lines || self.current.row.0 < -history {
return None;
}
let row = &self.grid[self.current.row];
if self.current.col.0 >= row.len() {
return None;
}
Some(Indexed {
square: &self.grid[self.current],
pos: self.current,
})
}
}
pub trait BidirectionalIterator: Iterator {
fn prev(&mut self) -> Option<Self::Item>;
}
impl<T> BidirectionalIterator for GridIterator<'_, T> {
fn prev(&mut self) -> Option<Self::Item> {
let topmost_line = self.grid.topmost_line();
let last_column = self.grid.last_column();
if self.current == Pos::new(topmost_line, Column(0)) {
return None;
}
match self.current {
Pos { col: Column(0), .. } => {
self.current.col = last_column;
self.current.row -= 1;
}
_ => self.current.col -= Column(1),
}
Some(Indexed {
square: &self.grid[self.current],
pos: self.current,
})
}
}