term_model/term/
cell.rs
1use bitflags::bitflags;
15
16use serde::{Deserialize, Serialize};
17
18use crate::ansi::{Color, NamedColor};
19use crate::grid::{self, GridCell};
20use crate::index::Column;
21
22pub const MAX_ZEROWIDTH_CHARS: usize = 5;
24
25bitflags! {
26 #[derive(Serialize, Deserialize)]
27 pub struct Flags: u16 {
28 const INVERSE = 0b00_0000_0001;
29 const BOLD = 0b00_0000_0010;
30 const ITALIC = 0b00_0000_0100;
31 const BOLD_ITALIC = 0b00_0000_0110;
32 const UNDERLINE = 0b00_0000_1000;
33 const WRAPLINE = 0b00_0001_0000;
34 const WIDE_CHAR = 0b00_0010_0000;
35 const WIDE_CHAR_SPACER = 0b00_0100_0000;
36 const DIM = 0b00_1000_0000;
37 const DIM_BOLD = 0b00_1000_0010;
38 const HIDDEN = 0b01_0000_0000;
39 const STRIKEOUT = 0b10_0000_0000;
40 }
41}
42
43const fn default_extra() -> [char; MAX_ZEROWIDTH_CHARS] {
44 [' '; MAX_ZEROWIDTH_CHARS]
45}
46
47#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
48pub struct Cell {
49 pub c: char,
50 pub fg: Color,
51 pub bg: Color,
52 pub flags: Flags,
53 #[serde(default = "default_extra")]
54 pub extra: [char; MAX_ZEROWIDTH_CHARS],
55}
56
57impl Default for Cell {
58 fn default() -> Cell {
59 Cell::new(' ', Color::Named(NamedColor::Foreground), Color::Named(NamedColor::Background))
60 }
61}
62
63impl GridCell for Cell {
64 #[inline]
65 fn is_empty(&self) -> bool {
66 (self.c == ' ' || self.c == '\t')
67 && self.extra[0] == ' '
68 && self.bg == Color::Named(NamedColor::Background)
69 && self.fg == Color::Named(NamedColor::Foreground)
70 && !self.flags.intersects(
71 Flags::INVERSE
72 | Flags::UNDERLINE
73 | Flags::STRIKEOUT
74 | Flags::WRAPLINE
75 | Flags::WIDE_CHAR_SPACER,
76 )
77 }
78
79 #[inline]
80 fn flags(&self) -> &Flags {
81 &self.flags
82 }
83
84 #[inline]
85 fn flags_mut(&mut self) -> &mut Flags {
86 &mut self.flags
87 }
88
89 #[inline]
90 fn fast_eq(&self, other: Self) -> bool {
91 self.bg == other.bg
92 }
93}
94
95pub trait LineLength {
97 fn line_length(&self) -> Column;
99}
100
101impl LineLength for grid::Row<Cell> {
102 fn line_length(&self) -> Column {
103 let mut length = Column(0);
104
105 if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) {
106 return Column(self.len());
107 }
108
109 for (index, cell) in self[..].iter().rev().enumerate() {
110 if cell.c != ' ' || cell.extra[0] != ' ' {
111 length = Column(self.len() - index);
112 break;
113 }
114 }
115
116 length
117 }
118}
119
120impl Cell {
121 #[inline]
122 pub fn bold(&self) -> bool {
123 self.flags.contains(Flags::BOLD)
124 }
125
126 #[inline]
127 pub fn inverse(&self) -> bool {
128 self.flags.contains(Flags::INVERSE)
129 }
130
131 #[inline]
132 pub fn dim(&self) -> bool {
133 self.flags.contains(Flags::DIM)
134 }
135
136 pub fn new(c: char, fg: Color, bg: Color) -> Cell {
137 Cell { extra: [' '; MAX_ZEROWIDTH_CHARS], c, bg, fg, flags: Flags::empty() }
138 }
139
140 #[inline]
141 pub fn reset(&mut self, template: &Cell) {
142 *self = Cell { c: template.c, bg: template.bg, ..Cell::default() };
144 }
145
146 #[inline]
147 pub fn chars(&self) -> [char; MAX_ZEROWIDTH_CHARS + 1] {
148 unsafe {
149 let mut chars = [std::mem::MaybeUninit::uninit(); MAX_ZEROWIDTH_CHARS + 1];
150 std::ptr::write(chars[0].as_mut_ptr(), self.c);
151 std::ptr::copy_nonoverlapping(
152 self.extra.as_ptr() as *mut std::mem::MaybeUninit<char>,
153 chars.as_mut_ptr().offset(1),
154 self.extra.len(),
155 );
156 std::mem::transmute(chars)
157 }
158 }
159
160 #[inline]
161 pub fn push_extra(&mut self, c: char) {
162 for elem in self.extra.iter_mut() {
163 if elem == &' ' {
164 *elem = c;
165 break;
166 }
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::{Cell, LineLength};
174
175 use crate::grid::Row;
176 use crate::index::Column;
177
178 #[test]
179 fn line_length_works() {
180 let template = Cell::default();
181 let mut row = Row::new(Column(10), &template);
182 row[Column(5)].c = 'a';
183
184 assert_eq!(row.line_length(), Column(6));
185 }
186
187 #[test]
188 fn line_length_works_with_wrapline() {
189 let template = Cell::default();
190 let mut row = Row::new(Column(10), &template);
191 row[Column(9)].flags.insert(super::Flags::WRAPLINE);
192
193 assert_eq!(row.line_length(), Column(10));
194 }
195}
196
197#[cfg(all(test, feature = "bench"))]
198mod benches {
199 extern crate test;
200 use super::Cell;
201
202 #[bench]
203 fn cell_reset(b: &mut test::Bencher) {
204 b.iter(|| {
205 let mut cell = Cell::default();
206
207 for _ in 0..100 {
208 cell.reset(test::black_box(&Cell::default()));
209 }
210
211 test::black_box(cell);
212 });
213 }
214}