返回 CodeWhale
glyphs.rs
根目录 / crates / tui / src / tui / glyphs.rs
1 //! Codewhale's terminal glyph charter.
2 //!
3 //! Renderers use semantic names from this module instead of choosing visual
4 //! punctuation ad hoc. The solid current marker (`●`) is the recurring
5 //! Codewhale anchor: it marks the active speaker or current human choice.
6 //! ASCII-safe terminals receive the semantic fallback from the same owner.
7
8 /// Current speaker or current human choice — the recurring identity anchor.
9 pub const CURRENT: &str = "●";
10 /// Available but not current.
11 pub const AVAILABLE: &str = "○";
12 /// Keyboard/list selection pointer.
13 pub const SELECTION: &str = "▸";
14 /// Finished user-authored message marker.
15 pub const USER: &str = "▎";
16 /// Transcript continuation rail, including its authored trailing space.
17 pub const TRANSCRIPT_RAIL: &str = "▏ ";
18 /// Settled successful state.
19 pub const DONE: &str = "✓";
20 /// Settled failed state.
21 pub const FAILED: &str = "✕";
22 /// State that needs human attention.
23 pub const ATTENTION: &str = "◆";
24 /// Ready but not active.
25 pub const READY: &str = "○";
26 /// Paused work.
27 pub const PAUSED: &str = "⏸";
28 /// Fleet role marks share the same charter while retaining distinct shapes.
29 pub const ROLE_MANAGER: &str = "◆";
30 pub const ROLE_BUILDER: &str = "■";
31 pub const ROLE_REVIEWER: &str = "◇";
32 pub const ROLE_VERIFIER: &str = CURRENT;
33 pub const ROLE_SYNTHESIZER: &str = "▲";
34 pub const NEUTRAL: &str = "·";
35
36 #[must_use]
37 pub const fn selection_marker(selected: bool) -> &'static str {
38 if selected { SELECTION } else { " " }
39 }
40
41 /// Reduce a single Codewhale-authored decorative glyph to narrow ASCII.
42 /// Language text and model/user content are intentionally outside this map.
43 #[must_use]
44 pub fn ascii_fallback(symbol: &str) -> Option<&'static str> {
45 match symbol {
46 "─" | "━" | "═" | "╌" | "╍" | "┄" | "┅" | "┈" | "┉" | "—" | "–" => {
47 Some("-")
48 }
49 "│" | "┃" | "║" | "╎" | "╏" | "▏" | "▎" | "▍" | "▌" | "▐" | "▕" => {
50 Some("|")
51 }
52 "┌" | "┐" | "└" | "┘" | "╭" | "╮" | "╰" | "╯" | "├" | "┤" | "┬" | "┴" | "┼" => {
53 Some("+")
54 }
55 "█" | "▉" | "▊" | "▋" | "▀" | "▄" | "▅" | "▆" | "▇" | "▙" | "▛" | "▜" | "▟" | "▰" => {
56 Some("#")
57 }
58 "▁" | "▂" | "▃" => Some("_"),
59 "▖" | "▗" | "▘" | "▝" => Some("."),
60 "▚" => Some("\\"),
61 "▞" => Some("/"),
62 "░" | "▒" | "▓" => Some(":"),
63 "▱" => Some("-"),
64 "▶" | "▷" | "▸" | "›" | "❯" | "→" | "↗" | "↘" | "»" => Some(">"),
65 "◀" | "◂" | "‹" | "❮" | "←" | "↖" | "↙" | "«" => Some("<"),
66 "▼" | "▾" | "▽" | "↓" => Some("v"),
67 "▲" | "△" | "↑" => Some("^"),
68 "◆" | "◇" | "♦" | "✦" | "◍" | "◉" | "★" | "☆" => Some("*"),
69 "■" | "□" | "▪" | "▫" | "◼" | "◻" => Some("#"),
70 "●" | "○" | "∘" | "•" | "·" | "☐" => Some("."),
71 "◌" | "˚" | "°" | "◦" => Some("o"),
72 "✓" | "✔" | "☑" => Some("Y"),
73 "✕" | "×" | "⊘" | "✗" | "✘" | "☒" => Some("X"),
74 "⏸" => Some("="),
75 "≈≈>" => Some("~>"),
76 "≈" | "~" => Some("~"),
77 "🐳" | "🐋" => Some("w"),
78 "…" => Some("."),
79 _ => None,
80 }
81 }
82
83 /// Preserve the working-bubble fill signal when Braille is unavailable.
84 #[must_use]
85 pub fn braille_ascii_fallback(ch: char) -> Option<&'static str> {
86 if !(('\u{2800}'..='\u{28FF}').contains(&ch)) {
87 return None;
88 }
89 let dots = ((ch as u32) - 0x2800).count_ones();
90 Some(match dots {
91 0 => " ",
92 1..=2 => ".",
93 3..=4 => ":",
94 5..=6 => "+",
95 _ => "#",
96 })
97 }
98
99 #[cfg(test)]
100 mod tests {
101 use super::*;
102
103 #[test]
104 fn charter_has_narrow_semantic_fallbacks() {
105 for (rich, safe) in [
106 (SELECTION, ">"),
107 ("▷", ">"),
108 (CURRENT, "."),
109 (USER, "|"),
110 (DONE, "Y"),
111 (FAILED, "X"),
112 (ATTENTION, "*"),
113 ("≈≈>", "~>"),
114 ("≈", "~"),
115 ("~", "~"),
116 ] {
117 assert_eq!(ascii_fallback(rich), Some(safe));
118 }
119 assert_eq!(braille_ascii_fallback('\u{2801}'), Some("."));
120 assert_eq!(braille_ascii_fallback('A'), None);
121 }
122 }
123
123 lines RUST