use compact_str::{CompactString, CompactStringExt};
use crate::gl::TerminalGrid;
#[derive(Debug, Clone, Copy, Default)]
pub struct CellQuery {
pub(crate) mode: SelectionMode,
pub(crate) start: Option<(u16, u16)>,
pub(crate) end: Option<(u16, u16)>,
pub(crate) trim_trailing_whitespace: bool,
pub(crate) content_hash: Option<u64>,
}
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub enum SelectionMode {
#[default]
Block,
Linear,
}
#[derive(Debug)]
pub enum CellIterator {
Block(BlockCellIterator),
Linear(LinearCellIterator),
}
#[derive(Debug)]
pub struct BlockCellIterator {
cols: u16,
start: (u16, u16),
end: (u16, u16),
current: (u16, u16),
finished: bool,
}
#[derive(Debug)]
pub struct LinearCellIterator {
cols: u16,
current_idx: usize,
end_idx: usize,
finished: bool,
}
#[must_use]
pub fn select(mode: SelectionMode) -> CellQuery {
CellQuery { mode, ..CellQuery::default() }
}
impl CellQuery {
#[must_use]
pub fn start(mut self, start: (u16, u16)) -> Self {
self.start = Some(start);
self
}
#[must_use]
pub fn end(mut self, end: (u16, u16)) -> Self {
self.end = Some(end);
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.start.is_none() && self.end.is_none()
}
#[must_use]
pub fn range(&self) -> Option<((u16, u16), (u16, u16))> {
if let (Some(start), Some(end)) = (self.start, self.end) {
Some((
(start.0.min(end.0), start.1.min(end.1)),
(start.0.max(end.0), start.1.max(end.1)),
))
} else {
None
}
}
#[must_use]
pub fn trim_trailing_whitespace(mut self, enabled: bool) -> Self {
self.trim_trailing_whitespace = enabled;
self
}
pub(crate) fn with_content_hash(mut self, hash: u64) -> Self {
self.content_hash = Some(hash);
self
}
}
impl Iterator for CellIterator {
type Item = (usize, bool);
fn next(&mut self) -> Option<Self::Item> {
match self {
CellIterator::Block(iter) => iter.next(),
CellIterator::Linear(iter) => iter.next(),
}
}
}
impl Iterator for BlockCellIterator {
type Item = (usize, bool);
fn next(&mut self) -> Option<Self::Item> {
if self.finished || self.current.1 > self.end.1 {
return None;
}
let idx = self.current.1 as usize * self.cols as usize + self.current.0 as usize;
let is_end_of_row = self.current.0 == self.end.0;
let is_last_row = self.current.1 == self.end.1;
let needs_newline = is_end_of_row && !is_last_row;
if self.current.0 < self.end.0 {
self.current.0 += 1;
} else {
self.current.0 = self.start.0;
self.current.1 += 1;
if self.current.1 > self.end.1 {
self.finished = true;
}
}
Some((idx, needs_newline))
}
}
impl Iterator for LinearCellIterator {
type Item = (usize, bool);
fn next(&mut self) -> Option<Self::Item> {
if self.finished || self.current_idx > self.end_idx {
return None;
}
let idx = self.current_idx;
self.current_idx += 1;
if self.current_idx > self.end_idx {
self.finished = true;
}
let needs_newline_after = if self.current_idx <= self.end_idx {
self.current_idx
.is_multiple_of(self.cols as usize)
} else {
false
};
Some((idx, needs_newline_after))
}
}
impl BlockCellIterator {
fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
let start = (
start.0.min(cols.saturating_sub(1)),
start
.1
.min((max_cells / cols as usize).saturating_sub(1) as u16),
);
let end = (
end.0.min(cols.saturating_sub(1)),
end.1
.min((max_cells / cols as usize).saturating_sub(1) as u16),
);
let (start, end) = if start > end { (end, start) } else { (start, end) };
Self {
cols,
start,
end,
current: start,
finished: start.1 > end.1,
}
}
}
impl LinearCellIterator {
fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
let cols_usize = cols as usize;
let start = (
start.0.min(cols.saturating_sub(1)),
start
.1
.min((max_cells / cols_usize).saturating_sub(1) as u16),
);
let end = (
end.0.min(cols.saturating_sub(1)),
end.1
.min((max_cells / cols_usize).saturating_sub(1) as u16),
);
let (start, end) = if start > end { (end, start) } else { (start, end) };
let start_idx = start.1 as usize * cols_usize + start.0 as usize;
let end_idx = end.1 as usize * cols_usize + end.0 as usize;
let end_idx = end_idx.min(max_cells.saturating_sub(1));
let start_idx = start_idx.min(end_idx);
Self {
cols,
current_idx: start_idx,
end_idx,
finished: start_idx > end_idx,
}
}
}
impl TerminalGrid {
#[must_use]
pub fn cell_iter(&self, selection: CellQuery) -> CellIterator {
let cols = self.terminal_size().cols;
let max_cells = self.cell_count();
let (start, end) = selection.range().unwrap_or_default();
match selection.mode {
SelectionMode::Block => {
CellIterator::Block(BlockCellIterator::new(cols, start, end, max_cells))
},
SelectionMode::Linear => {
CellIterator::Linear(LinearCellIterator::new(cols, start, end, max_cells))
},
}
}
pub fn get_text(&self, selection: CellQuery) -> CompactString {
if selection.is_empty() {
return CompactString::const_new("");
}
let text = self.get_symbols(self.cell_iter(selection));
if selection.trim_trailing_whitespace {
text.lines().map(str::trim_end).join_compact("\n")
} else {
text
}
}
}