#[derive(Clone, Debug)]
pub(crate) struct Row {
cells: Vec<crate::cell::Cell>,
wrapped: bool,
dirty: bool,
semantic_prompt: Option<crate::screen::SemanticPrompt>,
occ: u16,
}
impl Row {
pub(crate) fn new(cols: u16) -> Self {
Self {
cells: vec![crate::cell::Cell::new(); usize::from(cols)],
wrapped: false,
dirty: false,
semantic_prompt: None,
occ: 0,
}
}
pub(crate) fn clear(&mut self, attrs: crate::attrs::Attrs) {
if attrs == crate::attrs::Attrs::default() {
for cell in &mut self.cells[..usize::from(self.occ)] {
cell.clear(attrs);
}
self.occ = 0;
} else {
for cell in &mut self.cells {
cell.clear(attrs);
}
self.occ = self.cols();
}
self.wrapped = false;
self.dirty = true;
self.semantic_prompt = None;
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "row-level OSC 133 accessor; part of the row API surface"
)
)]
pub(crate) fn semantic_prompt(&self) -> Option<crate::screen::SemanticPrompt> {
self.semantic_prompt
}
pub(crate) fn set_semantic_prompt(&mut self, mark: crate::screen::SemanticPrompt) {
self.semantic_prompt = Some(mark);
}
pub(crate) fn get(&self, col: u16) -> Option<&crate::cell::Cell> {
self.cells.get(usize::from(col))
}
pub(crate) fn get_mut(&mut self, col: u16) -> Option<&mut crate::cell::Cell> {
let cell = self.cells.get_mut(usize::from(col));
if cell.is_some() {
self.dirty = true;
self.occ = self.occ.max(col.saturating_add(1));
}
cell
}
pub(crate) fn insert(&mut self, i: u16, cell: crate::cell::Cell) {
self.cells.insert(usize::from(i), cell);
self.wrapped = false;
self.dirty = true;
self.occ = self.cols();
}
pub(crate) fn remove(&mut self, i: u16) {
self.clear_wide(i);
self.cells.remove(usize::from(i));
self.wrapped = false;
self.dirty = true;
self.occ = self.cols();
}
pub(crate) fn erase(&mut self, i: u16, attrs: crate::attrs::Attrs) {
let wide = self.cells[usize::from(i)].is_wide();
self.clear_wide(i);
self.cells[usize::from(i)].clear(attrs);
let cols = self.cols();
let last = cols.saturating_sub(if wide { 2 } else { 1 });
if i == last {
self.wrapped = false;
}
self.dirty = true;
self.occ = self.occ.max(i.saturating_add(1));
}
pub(crate) fn truncate(&mut self, len: u16) {
self.cells.truncate(usize::from(len));
self.wrapped = false;
self.dirty = true;
self.occ = self.occ.min(len);
if len == 0 {
return;
}
let last_cell = &mut self.cells[usize::from(len) - 1];
if last_cell.is_wide() {
last_cell.clear(*last_cell.attrs());
}
}
pub(crate) fn resize(&mut self, len: u16, cell: crate::cell::Cell) {
let old_len = self.cells.len();
let new_len = usize::from(len);
let grow_with_non_default = new_len > old_len && cell != crate::cell::Cell::new();
self.cells.resize(new_len, cell);
self.wrapped = false;
self.dirty = true;
if new_len < old_len && new_len > 0 {
let last = &mut self.cells[new_len - 1];
if last.is_wide() {
last.clear(*last.attrs());
}
}
self.occ = if grow_with_non_default {
len
} else {
self.occ.min(len)
};
}
pub(crate) fn wrap(&mut self, wrap: bool) {
if self.wrapped != wrap {
self.dirty = true;
}
self.wrapped = wrap;
}
pub(crate) fn is_dirty(&self) -> bool {
self.dirty
}
pub(crate) fn mark_dirty(&mut self) {
self.dirty = true;
}
pub(crate) fn clear_dirty(&mut self) {
self.dirty = false;
}
pub(crate) fn wrapped(&self) -> bool {
self.wrapped
}
pub(crate) fn text_contents(&self) -> String {
let mut text = String::new();
for cell in &self.cells {
if cell.is_wide_continuation() {
continue;
}
let c = cell.contents();
if c.is_empty() {
text.push(' ');
} else {
text.push_str(c);
}
}
text.truncate(text.trim_end().len());
text
}
fn cols(&self) -> u16 {
self.cells
.len()
.try_into()
.expect("row width bounded by u16::MAX at TerminalSize construction")
}
pub(crate) fn clear_wide(&mut self, col: u16) {
let col_idx = usize::from(col);
let cell = &self.cells[col_idx];
if cell.is_wide() {
if let Some(other) = self.cells.get_mut(col_idx + 1) {
other.clear(*other.attrs());
self.dirty = true;
}
} else if cell.is_wide_continuation() && col > 0 {
let other = &mut self.cells[col_idx - 1];
other.clear(*other.attrs());
self.dirty = true;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attrs::Attrs;
#[test]
fn new_row() {
let row = Row::new(80);
assert!(!row.wrapped());
assert!(row.get(0).is_some());
assert!(row.get(79).is_some());
assert!(row.get(80).is_none());
}
#[test]
fn clear_row() {
let mut row = Row::new(10);
row.get_mut(0).unwrap().set('A', Attrs::default());
row.clear(Attrs::default());
assert!(!row.get(0).unwrap().has_contents());
}
#[test]
fn insert_and_truncate() {
let mut row = Row::new(5);
row.get_mut(0).unwrap().set('A', Attrs::default());
row.insert(0, crate::cell::Cell::new());
assert!(!row.get(0).unwrap().has_contents());
assert_eq!(row.get(1).unwrap().contents(), "A");
row.truncate(5);
}
#[test]
fn wrap_flag() {
let mut row = Row::new(10);
assert!(!row.wrapped());
row.wrap(true);
assert!(row.wrapped());
row.wrap(false);
assert!(!row.wrapped());
}
#[test]
fn resize_clears_orphaned_wide_char() {
let mut row = Row::new(10);
row.get_mut(4).unwrap().set('漢', Attrs::default());
row.get_mut(4).unwrap().set_wide(true);
row.get_mut(5).unwrap().set_wide_continuation(true);
assert!(row.get(4).unwrap().is_wide());
assert!(row.get(5).unwrap().is_wide_continuation());
row.resize(5, crate::cell::Cell::new());
assert!(!row.get(4).unwrap().is_wide());
assert!(!row.get(4).unwrap().has_contents());
}
#[test]
fn truncate_zero_does_not_panic() {
let mut row = Row::new(5);
row.truncate(0);
assert_eq!(row.get(0), None);
}
#[test]
fn clear_wide_at_last_col() {
let mut row = Row::new(5);
row.get_mut(3).unwrap().set('漢', Attrs::default());
row.get_mut(3).unwrap().set_wide(true);
row.get_mut(4).unwrap().set_wide_continuation(true);
row.clear_wide(3);
assert!(!row.get(4).unwrap().is_wide_continuation());
}
#[test]
fn clear_wide_continuation_at_col_zero() {
let mut row = Row::new(5);
row.get_mut(0).unwrap().set_wide_continuation(true);
row.clear_wide(0); }
#[test]
fn new_row_is_clean() {
let row = Row::new(10);
assert!(!row.is_dirty());
}
#[test]
fn cell_write_marks_dirty() {
let mut row = Row::new(10);
row.clear_dirty();
assert!(!row.is_dirty());
row.get_mut(3).unwrap().set('A', Attrs::default());
assert!(row.is_dirty());
}
#[test]
fn out_of_bounds_get_mut_does_not_mark_dirty() {
let mut row = Row::new(10);
row.clear_dirty();
assert!(row.get_mut(99).is_none());
assert!(!row.is_dirty());
}
#[test]
fn clear_marks_dirty() {
let mut row = Row::new(10);
row.clear_dirty();
row.clear(Attrs::default());
assert!(row.is_dirty());
}
#[test]
fn wrap_change_marks_dirty() {
let mut row = Row::new(10);
row.clear_dirty();
row.wrap(false); assert!(!row.is_dirty());
row.wrap(true);
assert!(row.is_dirty());
}
#[test]
fn clear_dirty_resets() {
let mut row = Row::new(10);
row.get_mut(0).unwrap().set('A', Attrs::default());
assert!(row.is_dirty());
row.clear_dirty();
assert!(!row.is_dirty());
}
#[test]
fn resize_preserves_complete_wide_char() {
let mut row = Row::new(10);
row.get_mut(3).unwrap().set('漢', Attrs::default());
row.get_mut(3).unwrap().set_wide(true);
row.get_mut(4).unwrap().set_wide_continuation(true);
row.resize(6, crate::cell::Cell::new());
assert!(row.get(3).unwrap().is_wide());
assert_eq!(row.get(3).unwrap().contents(), "漢");
assert!(row.get(4).unwrap().is_wide_continuation());
}
}