| 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)); |
| } |
| } |