use crate::{
cell::{self, Cell},
term::{self, AsTermInput},
};
use anyhow::anyhow;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Line {
pub cells: Vec<Cell>,
pub is_wrapped: bool,
}
impl std::fmt::Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for cell in &self.cells {
write!(f, "{}", cell)?;
}
if self.is_wrapped {
writeln!(f, "⏎")?;
} else {
writeln!(f)?;
}
Ok(())
}
}
impl AsTermInput for Line {
fn term_input_into(&self, buf: &mut Vec<u8>) {
let blank_attrs = term::Attrs::default();
let mut current_attrs = &blank_attrs;
for cell in self.cells.iter() {
if cell.attrs() != current_attrs {
for code in current_attrs.transition_to(cell.attrs()) {
code.term_input_into(buf);
}
current_attrs = cell.attrs();
}
cell.term_input_into(buf);
}
if current_attrs != &blank_attrs {
for code in current_attrs.transition_to(&blank_attrs) {
code.term_input_into(buf);
}
}
}
}
impl Line {
pub fn new() -> Self {
Line { cells: vec![], is_wrapped: false }
}
#[allow(dead_code)]
pub fn get_cell(&self, width: usize, col: usize) -> Option<&Cell> {
if col >= width {
return None;
}
if col >= self.cells.len() {
return Some(cell::empty());
}
return Some(&self.cells[col]);
}
pub fn set_cell(&mut self, width: usize, col: usize, cell: Cell) -> anyhow::Result<()> {
if col >= width {
return Err(anyhow!("{} out of bounds (width={})", col, width));
}
if col >= self.cells.len() {
while self.cells.len() < col {
self.cells.push(Cell::empty())
}
self.cells.push(cell);
return Ok(());
}
self.cells[col] = cell;
Ok(())
}
pub fn truncate(&mut self, width: usize) {
self.cells.truncate(width);
}
pub fn erase(&mut self, section: Section) {
match section {
Section::StartTo(col) => {
for i in 0..std::cmp::min(col + 1, self.cells.len()) {
self.cells[i] = Cell::empty();
}
}
Section::ToEnd(col) => {
self.truncate(col);
self.is_wrapped = false;
}
Section::Whole => {
self.truncate(0);
self.is_wrapped = false;
}
}
}
pub fn insert_character(&mut self, width: usize, col: usize, n: usize) {
let empties = vec![Cell::empty(); n];
self.cells.splice(col..col, empties);
self.cells.truncate(width);
}
pub fn delete_character(&mut self, width: usize, col: usize, attrs: &term::Attrs, n: usize) {
let delete_to = std::cmp::min(self.cells.len(), col + n);
let num_to_delete = delete_to - col;
self.cells.drain(col..delete_to);
while self.cells.len() < width - num_to_delete {
self.cells.push(Cell::empty());
}
while self.cells.len() < width {
self.cells.push(Cell::empty_with_attrs(attrs.clone()));
}
}
}
pub enum Section {
StartTo(usize),
ToEnd(usize),
Whole,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new() {
let line = Line::new();
assert!(line.cells.is_empty());
assert!(!line.is_wrapped);
}
#[test]
fn set() -> anyhow::Result<()> {
let mut line = Line::new();
let width = 5;
let c1 = Cell::new('a', term::Attrs::default());
let c2 = Cell::new('b', term::Attrs::default());
line.set_cell(width, 0, c1.clone())?;
assert_eq!(line.get_cell(width, 0), Some(&c1));
line.set_cell(width, 2, c2.clone())?;
assert_eq!(line.get_cell(width, 0), Some(&c1));
assert!(line.get_cell(width, 1).unwrap().is_empty());
assert_eq!(line.get_cell(width, 2), Some(&c2));
Ok(())
}
#[test]
fn set_oob() -> anyhow::Result<()> {
let mut line = Line::new();
let width = 5;
match line.set_cell(width, 5, Cell::new('a', term::Attrs::default())) {
Err(e) => assert!(format!("{e:?}").contains("out of bounds")),
_ => assert!(false, "expected out of bounds error"),
}
Ok(())
}
}