blob: 64de5492dcd8670a9c55a6f94046ca8c829dedb0 [file] [log] [blame]
use std::boxed::Box;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use crate::ansi::{Color, NamedColor};
use crate::grid::{self, GridCell};
use crate::index::Column;
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct Flags: u16 {
const INVERSE = 0b0000_0000_0000_0001;
const BOLD = 0b0000_0000_0000_0010;
const ITALIC = 0b0000_0000_0000_0100;
const BOLD_ITALIC = 0b0000_0000_0000_0110;
const UNDERLINE = 0b0000_0000_0000_1000;
const WRAPLINE = 0b0000_0000_0001_0000;
const WIDE_CHAR = 0b0000_0000_0010_0000;
const WIDE_CHAR_SPACER = 0b0000_0000_0100_0000;
const DIM = 0b0000_0000_1000_0000;
const DIM_BOLD = 0b0000_0000_1000_0010;
const HIDDEN = 0b0000_0001_0000_0000;
const STRIKEOUT = 0b0000_0010_0000_0000;
const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000;
const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000;
}
}
/// Trait for determining if a reset should be performed.
pub trait ResetDiscriminant<T> {
/// Value based on which equality for the reset will be determined.
fn discriminant(&self) -> T;
}
impl<T: Copy> ResetDiscriminant<T> for T {
fn discriminant(&self) -> T {
*self
}
}
impl ResetDiscriminant<Color> for Cell {
fn discriminant(&self) -> Color {
self.bg
}
}
/// Dynamically allocated cell content.
///
/// This storage is reserved for cell attributes which are rarely set. This allows reducing the
/// allocation required ahead of time for every cell, with some additional overhead when the extra
/// storage is actually required.
#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
struct CellExtra {
zerowidth: Vec<char>,
}
/// Content and attributes of a single cell in the terminal grid.
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct Cell {
pub c: char,
pub fg: Color,
pub bg: Color,
pub flags: Flags,
#[serde(default)]
extra: Option<Box<CellExtra>>,
}
impl Default for Cell {
#[inline]
fn default() -> Cell {
Cell {
c: ' ',
bg: Color::Named(NamedColor::Background),
fg: Color::Named(NamedColor::Foreground),
flags: Flags::empty(),
extra: None,
}
}
}
impl Cell {
/// Zerowidth characters stored in this cell.
#[inline]
pub fn zerowidth(&self) -> Option<&[char]> {
self.extra.as_ref().map(|extra| extra.zerowidth.as_slice())
}
/// Write a new zerowidth character to this cell.
#[inline]
pub fn push_zerowidth(&mut self, c: char) {
self.extra.get_or_insert_with(Default::default).zerowidth.push(c);
}
/// Free all dynamically allocated cell storage.
#[inline]
pub fn drop_extra(&mut self) {
if self.extra.is_some() {
self.extra = None;
}
}
/// Remove all wide char data from a cell.
#[inline(never)]
pub fn clear_wide(&mut self) {
self.flags.remove(Flags::WIDE_CHAR);
self.drop_extra();
self.c = ' ';
}
}
impl GridCell for Cell {
#[inline]
fn is_empty(&self) -> bool {
(self.c == ' ' || self.c == '\t')
&& self.bg == Color::Named(NamedColor::Background)
&& self.fg == Color::Named(NamedColor::Foreground)
&& !self.flags.intersects(
Flags::INVERSE
| Flags::UNDERLINE
| Flags::DOUBLE_UNDERLINE
| Flags::STRIKEOUT
| Flags::WRAPLINE
| Flags::WIDE_CHAR_SPACER
| Flags::LEADING_WIDE_CHAR_SPACER,
)
&& self.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) != Some(false)
}
#[inline]
fn flags(&self) -> &Flags {
&self.flags
}
#[inline]
fn flags_mut(&mut self) -> &mut Flags {
&mut self.flags
}
#[inline]
fn reset(&mut self, template: &Self) {
*self = Cell { bg: template.bg, ..Cell::default() };
}
}
impl From<Color> for Cell {
#[inline]
fn from(color: Color) -> Self {
Self { bg: color, ..Cell::default() }
}
}
/// Get the length of occupied cells in a line.
pub trait LineLength {
/// Calculate the occupied line length.
fn line_length(&self) -> Column;
}
impl LineLength for grid::Row<Cell> {
fn line_length(&self) -> Column {
let mut length = Column(0);
if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) {
return Column(self.len());
}
for (index, cell) in self[..].iter().rev().enumerate() {
if cell.c != ' '
|| cell.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) == Some(false)
{
length = Column(self.len() - index);
break;
}
}
length
}
}
#[cfg(test)]
mod tests {
use super::{Cell, LineLength};
use crate::grid::Row;
use crate::index::Column;
#[test]
fn line_length_works() {
let mut row = Row::<Cell>::new(10);
row[Column(5)].c = 'a';
assert_eq!(row.line_length(), Column(6));
}
#[test]
fn line_length_works_with_wrapline() {
let mut row = Row::<Cell>::new(10);
row[Column(9)].flags.insert(super::Flags::WRAPLINE);
assert_eq!(row.line_length(), Column(10));
}
}