slt/cell.rs
1//! Single terminal cell — the smallest unit of the render buffer.
2
3use compact_str::CompactString;
4
5use crate::style::Style;
6
7// Compile-time size assertion for `Cell`.
8//
9// `Cell` is composed of `symbol: CompactString` + `style: Style` +
10// `hyperlink: Option<CompactString>`. Upstream changes to any of these
11// (e.g., `CompactString` inline-storage tweaks, `Style` field additions,
12// or hyperlink type swaps) can silently grow the struct until runtime.
13// A 64-byte budget keeps each cell within one cache line.
14//
15// If an intentional growth pushes us past 64 B, raise this bound and
16// document why — but do not silently let it drift.
17const _: () = assert!(
18 std::mem::size_of::<Cell>() <= 64,
19 "Cell exceeds one cache line (64 B). If the size increase is intentional, update this bound and document why."
20);
21
22/// A single terminal cell containing a character and style.
23///
24/// Each cell holds one grapheme cluster (stored as a [`CompactString`] for
25/// inline storage of short strings — no heap allocation for ≤24 bytes).
26/// Wide characters (e.g., CJK) occupy two adjacent cells; the second cell's
27/// `symbol` is left empty by the buffer layer.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Cell {
30 /// The grapheme cluster displayed in this cell. Defaults to a single space.
31 pub symbol: CompactString,
32 /// The visual style (colors and modifiers) for this cell.
33 pub style: Style,
34 /// Optional OSC 8 hyperlink URL. When set, the terminal renders this cell
35 /// as a clickable link.
36 pub hyperlink: Option<CompactString>,
37}
38
39impl Default for Cell {
40 fn default() -> Self {
41 Self {
42 symbol: CompactString::const_new(" "),
43 style: Style::new(),
44 hyperlink: None,
45 }
46 }
47}
48
49impl Cell {
50 /// Replace the cell's symbol with the given string slice.
51 pub fn set_symbol(&mut self, s: &str) -> &mut Self {
52 self.symbol.clear();
53 self.symbol.push_str(s);
54 self
55 }
56
57 /// Replace the cell's symbol with a single character.
58 pub fn set_char(&mut self, ch: char) -> &mut Self {
59 self.symbol.clear();
60 self.symbol.push(ch);
61 self
62 }
63
64 /// Set the cell's style.
65 pub fn set_style(&mut self, style: Style) -> &mut Self {
66 self.style = style;
67 self
68 }
69
70 /// Reset the cell to a blank space with default style.
71 pub fn reset(&mut self) {
72 self.symbol.clear();
73 self.symbol.push(' ');
74 self.style = Style::new();
75 self.hyperlink = None;
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn cell_size_within_cache_line() {
85 let size = std::mem::size_of::<Cell>();
86 assert!(
87 size <= 64,
88 "Cell size = {size}B; exceeds 64B cache-line budget. If intentional, update the const-assert and this test together."
89 );
90 }
91}