1use core::num::NonZeroU16;
2
3use compact_str::CompactString;
4
5use crate::buffer::cell_width::CellWidth;
6use crate::style::{Color, Modifier, Style};
7use crate::symbols::merge::MergeStrategy;
8
9#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum CellDiffOption {
13 #[default]
14 None,
16 Skip,
21 AlwaysUpdate,
27 ForcedWidth(NonZeroU16),
32}
33
34#[derive(Debug, Default, Clone)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct Cell {
38 symbol: Option<CompactString>,
47
48 pub fg: Color,
50
51 pub bg: Color,
53
54 #[cfg(feature = "underline-color")]
56 pub underline_color: Color,
57
58 pub modifier: Modifier,
60
61 pub diff_option: CellDiffOption,
63
64 #[deprecated(
68 since = "0.30.1",
69 note = "use `set_diff_option(CellDiffOption::Skip)` instead"
70 )]
71 pub skip: bool,
72}
73
74impl Cell {
75 #[allow(deprecated)]
77 pub const EMPTY: Self = Self {
78 symbol: None,
79 fg: Color::Reset,
80 bg: Color::Reset,
81 #[cfg(feature = "underline-color")]
82 underline_color: Color::Reset,
83 modifier: Modifier::empty(),
84 diff_option: CellDiffOption::None,
85 skip: false,
86 };
87
88 pub const fn new(symbol: &'static str) -> Self {
95 Self {
96 symbol: Some(CompactString::const_new(symbol)),
97 ..Self::EMPTY
98 }
99 }
100
101 #[must_use]
105 pub fn symbol(&self) -> &str {
106 self.symbol.as_ref().map_or(" ", |s| s.as_str())
107 }
108
109 pub fn merge_symbol(&mut self, symbol: &str, strategy: MergeStrategy) -> &mut Self {
147 let merged_symbol = self
148 .symbol
149 .as_ref()
150 .map_or(symbol, |s| strategy.merge(s, symbol));
151 self.symbol = Some(CompactString::new(merged_symbol));
152 self
153 }
154
155 pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
157 self.symbol = Some(CompactString::new(symbol));
158 self
159 }
160
161 pub(crate) fn append_symbol(&mut self, symbol: &str) -> &mut Self {
165 self.symbol.get_or_insert_default().push_str(symbol);
166 self
167 }
168
169 pub fn set_char(&mut self, ch: char) -> &mut Self {
171 let mut buf = [0; 4];
172 self.symbol = Some(CompactString::new(ch.encode_utf8(&mut buf)));
173 self
174 }
175
176 pub const fn set_fg(&mut self, color: Color) -> &mut Self {
178 self.fg = color;
179 self
180 }
181
182 pub const fn set_bg(&mut self, color: Color) -> &mut Self {
184 self.bg = color;
185 self
186 }
187
188 pub fn set_style<S: Into<Style>>(&mut self, style: S) -> &mut Self {
193 let style = style.into();
194 if let Some(c) = style.fg {
195 self.fg = c;
196 }
197 if let Some(c) = style.bg {
198 self.bg = c;
199 }
200 #[cfg(feature = "underline-color")]
201 if let Some(c) = style.underline_color {
202 self.underline_color = c;
203 }
204 self.modifier.insert(style.add_modifier);
205 self.modifier.remove(style.sub_modifier);
206 self
207 }
208
209 #[must_use]
211 pub const fn style(&self) -> Style {
212 Style {
213 fg: Some(self.fg),
214 bg: Some(self.bg),
215 #[cfg(feature = "underline-color")]
216 underline_color: Some(self.underline_color),
217 add_modifier: self.modifier,
218 sub_modifier: Modifier::empty(),
219 }
220 }
221
222 #[deprecated(
227 since = "0.30.1",
228 note = "use `set_diff_option(CellDiffOption::Skip)` instead"
229 )]
230 #[allow(deprecated)]
231 pub const fn set_skip(&mut self, skip: bool) -> &mut Self {
232 self.skip = skip;
233 self
234 }
235
236 pub const fn set_diff_option(&mut self, diff_option: CellDiffOption) -> &mut Self {
242 self.diff_option = diff_option;
243 self
244 }
245
246 #[allow(deprecated)]
248 pub fn reset(&mut self) {
249 *self = Self::EMPTY;
250 }
251}
252
253impl PartialEq for Cell {
254 fn eq(&self, other: &Self) -> bool {
260 let symbols_eq = self.symbol() == other.symbol();
262
263 #[cfg(feature = "underline-color")]
264 let underline_color_eq = self.underline_color == other.underline_color;
265 #[cfg(not(feature = "underline-color"))]
266 let underline_color_eq = true;
267
268 #[allow(deprecated)]
269 let skip_eq = self.skip == other.skip;
270
271 symbols_eq
272 && underline_color_eq
273 && skip_eq
274 && self.fg == other.fg
275 && self.bg == other.bg
276 && self.modifier == other.modifier
277 && self.diff_option == other.diff_option
278 }
279}
280
281impl Eq for Cell {}
282
283impl core::hash::Hash for Cell {
284 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
289 self.symbol().hash(state);
290 self.fg.hash(state);
291 self.bg.hash(state);
292 #[cfg(feature = "underline-color")]
293 self.underline_color.hash(state);
294 self.modifier.hash(state);
295 self.diff_option.hash(state);
296 #[allow(deprecated)]
297 self.skip.hash(state);
298 }
299}
300
301impl From<char> for Cell {
302 fn from(ch: char) -> Self {
303 let mut cell = Self::EMPTY;
304 cell.set_char(ch);
305 cell
306 }
307}
308
309impl CellWidth for Cell {
310 fn cell_width(&self) -> u16 {
313 match self.diff_option {
314 CellDiffOption::ForcedWidth(w) => w.get(),
315 _ => self.symbol().cell_width(),
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 #[allow(deprecated)]
326 fn new() {
327 let cell = Cell::new("あ");
328 assert_eq!(
329 cell,
330 Cell {
331 symbol: Some(CompactString::const_new("あ")),
332 fg: Color::Reset,
333 bg: Color::Reset,
334 #[cfg(feature = "underline-color")]
335 underline_color: Color::Reset,
336 modifier: Modifier::empty(),
337 diff_option: CellDiffOption::None,
338 skip: false,
339 }
340 );
341 }
342
343 #[test]
344 fn empty() {
345 let cell = Cell::EMPTY;
346 assert_eq!(cell.symbol(), " ");
347 }
348
349 #[test]
350 fn set_symbol() {
351 let mut cell = Cell::EMPTY;
352 cell.set_symbol("あ"); assert_eq!(cell.symbol(), "あ");
354 cell.set_symbol("👨👩👧👦"); assert_eq!(cell.symbol(), "👨👩👧👦");
356 }
357
358 #[test]
359 fn append_symbol() {
360 let mut cell = Cell::EMPTY;
361 cell.set_symbol("あ"); cell.append_symbol("\u{200B}"); assert_eq!(cell.symbol(), "あ\u{200B}");
364 }
365
366 #[test]
367 fn set_char() {
368 let mut cell = Cell::EMPTY;
369 cell.set_char('あ'); assert_eq!(cell.symbol(), "あ");
371 }
372
373 #[test]
374 fn set_fg() {
375 let mut cell = Cell::EMPTY;
376 cell.set_fg(Color::Red);
377 assert_eq!(cell.fg, Color::Red);
378 }
379
380 #[test]
381 fn set_bg() {
382 let mut cell = Cell::EMPTY;
383 cell.set_bg(Color::Red);
384 assert_eq!(cell.bg, Color::Red);
385 }
386
387 #[test]
388 fn set_style() {
389 let mut cell = Cell::EMPTY;
390 cell.set_style(Style::new().fg(Color::Red).bg(Color::Blue));
391 assert_eq!(cell.fg, Color::Red);
392 assert_eq!(cell.bg, Color::Blue);
393 }
394
395 #[test]
396 fn set_skip() {
397 let mut cell = Cell::EMPTY;
398 cell.set_diff_option(CellDiffOption::Skip);
399 assert_eq!(cell.diff_option, CellDiffOption::Skip);
400 }
401
402 #[test]
403 fn set_always_update() {
404 let mut cell = Cell::EMPTY;
405 cell.set_diff_option(CellDiffOption::AlwaysUpdate);
406 assert_eq!(cell.diff_option, CellDiffOption::AlwaysUpdate);
407 }
408
409 #[test]
410 fn reset() {
411 let mut cell = Cell::EMPTY;
412 cell.set_symbol("あ");
413 cell.set_fg(Color::Red);
414 cell.set_bg(Color::Blue);
415 cell.set_diff_option(CellDiffOption::Skip);
416 cell.reset();
417 assert_eq!(cell.symbol(), " ");
418 assert_eq!(cell.fg, Color::Reset);
419 assert_eq!(cell.bg, Color::Reset);
420 assert_eq!(cell.diff_option, CellDiffOption::None);
421 }
422
423 #[test]
424 fn style() {
425 let cell = Cell::EMPTY;
426 assert_eq!(
427 cell.style(),
428 Style {
429 fg: Some(Color::Reset),
430 bg: Some(Color::Reset),
431 #[cfg(feature = "underline-color")]
432 underline_color: Some(Color::Reset),
433 add_modifier: Modifier::empty(),
434 sub_modifier: Modifier::empty(),
435 }
436 );
437 }
438
439 #[test]
440 fn default() {
441 let cell = Cell::default();
442 assert_eq!(cell.symbol(), " ");
443 }
444
445 #[test]
446 fn cell_eq() {
447 let cell1 = Cell::new("あ");
448 let cell2 = Cell::new("あ");
449 assert_eq!(cell1, cell2);
450 }
451
452 #[test]
453 fn cell_ne() {
454 let cell1 = Cell::new("あ");
455 let cell2 = Cell::new("い");
456 assert_ne!(cell1, cell2);
457 }
458}