Skip to main content

lattice_terminal/
cell.rs

1//! Cell + color + attribute types — the renderer-facing
2//! representation of a terminal grid position.
3//!
4//! These mirror `alacritty_terminal`'s internal types but
5//! re-exposed in Lattice's vocabulary so the rest of the
6//! editor doesn't depend on alacritty_terminal's API surface
7//! directly. A future libghostty swap (or another VT
8//! substrate) can preserve this contract.
9
10use std::fmt;
11
12/// One grid cell. `Cell::default()` is "space char on default
13/// fg/bg with no attributes" — what alacritty_terminal initialises
14/// empty cells to.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Cell {
17    pub ch: char,
18    pub fg: TerminalColor,
19    pub bg: TerminalColor,
20    pub attrs: CellAttrs,
21    /// True when this cell is the trailing placeholder of a
22    /// width-2 glyph — alacritty's `WIDE_CHAR_SPACER` (or
23    /// `LEADING_WIDE_CHAR_SPACER` for a wide glyph deferred off a
24    /// line's last column). Renderers MUST skip spacer cells: the
25    /// preceding wide glyph already occupies two display columns
26    /// via the renderer's own shaping, so emitting the spacer as a
27    /// character would make one grid pair span three display
28    /// columns and desync the column model (the auto-scroll
29    /// ghosting bug). See `docs/dev/audit/terminal-wide-char-ghosting.md`.
30    pub wide_spacer: bool,
31}
32
33impl Default for Cell {
34    fn default() -> Self {
35        Self {
36            ch: ' ',
37            fg: TerminalColor::Default,
38            bg: TerminalColor::Default,
39            attrs: CellAttrs::default(),
40            wide_spacer: false,
41        }
42    }
43}
44
45/// Per-cell text attributes (bold, italic, etc.). Bools rather
46/// than bitflags so the renderer's adapter can `if attrs.bold
47/// { … }` without bitwise math. Adding a new attribute is a
48/// struct-field append.
49#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
50pub struct CellAttrs {
51    pub bold: bool,
52    pub italic: bool,
53    pub underline: bool,
54    pub reverse: bool,
55    pub dim: bool,
56    pub strikethrough: bool,
57    pub blink: bool,
58}
59
60/// Terminal cell color. Covers every variant any VT/xterm
61/// emulator emits:
62///
63/// - `Default` — "use the terminal's default fg/bg" (cell
64///   carries no explicit color).
65/// - `Named` — one of the 16 ANSI named palette entries.
66/// - `Indexed` — 256-color palette index.
67/// - `Rgb` — 24-bit truecolor.
68///
69/// TUI renderers degrade `Rgb` to nearest-`Indexed` when the
70/// terminal lacks truecolor; GPUI consumes `Rgb` directly.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum TerminalColor {
73    Default,
74    Named(NamedColor),
75    Indexed(u8),
76    Rgb(u8, u8, u8),
77}
78
79/// The 16 ANSI named palette colors (plus their bright
80/// variants).
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum NamedColor {
83    Black,
84    Red,
85    Green,
86    Yellow,
87    Blue,
88    Magenta,
89    Cyan,
90    White,
91    BrightBlack,
92    BrightRed,
93    BrightGreen,
94    BrightYellow,
95    BrightBlue,
96    BrightMagenta,
97    BrightCyan,
98    BrightWhite,
99}
100
101/// Cursor shape (DECSCUSR + xterm SS3 style codes). Programs
102/// like vim, ssh, modern shells issue these to differentiate
103/// modes.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
105pub enum CursorShape {
106    #[default]
107    Block,
108    Underline,
109    Bar,
110    Hidden,
111}
112
113impl fmt::Display for CursorShape {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        match self {
116            CursorShape::Block => f.write_str("block"),
117            CursorShape::Underline => f.write_str("underline"),
118            CursorShape::Bar => f.write_str("bar"),
119            CursorShape::Hidden => f.write_str("hidden"),
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn cell_default_is_space_with_default_colors() {
130        let c = Cell::default();
131        assert_eq!(c.ch, ' ');
132        assert_eq!(c.fg, TerminalColor::Default);
133        assert_eq!(c.bg, TerminalColor::Default);
134        assert_eq!(c.attrs, CellAttrs::default());
135    }
136
137    #[test]
138    fn cell_attrs_default_is_all_off() {
139        let a = CellAttrs::default();
140        assert!(!a.bold);
141        assert!(!a.italic);
142        assert!(!a.underline);
143        assert!(!a.reverse);
144        assert!(!a.dim);
145        assert!(!a.strikethrough);
146        assert!(!a.blink);
147    }
148}