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}