gpui_cn/terminal/
theme.rs1use ghostty_vt::style::{Palette, RgbColor};
9
10use crate::terminal::frame::{Rgb, TerminalColors};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct ThemeError {
16 line: usize,
17 text: String,
18}
19
20impl ThemeError {
21 pub fn line(&self) -> usize {
23 self.line
24 }
25
26 pub fn text(&self) -> &str {
28 &self.text
29 }
30}
31
32impl std::fmt::Display for ThemeError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 write!(f, "line {}: cannot parse {:?}", self.line, self.text)
35 }
36}
37
38impl std::error::Error for ThemeError {}
39
40fn rgb(color: RgbColor) -> Rgb {
41 Rgb(color.r, color.g, color.b)
42}
43
44impl TerminalColors {
45 pub fn from_ghostty_theme(text: &str) -> Result<Self, ThemeError> {
52 let mut colors = Self::default();
53 let mut palette = Palette(colors.palette.map(|c| RgbColor {
54 r: c.0,
55 g: c.1,
56 b: c.2,
57 }));
58 for (index, raw) in text.lines().enumerate() {
59 let line = raw.trim();
60 if line.is_empty() || line.starts_with('#') {
61 continue;
62 }
63 let Some((key, value)) = line.split_once('=') else {
64 continue;
65 };
66 let key = key.trim();
67 let value = value.trim();
68 let error = || ThemeError {
69 line: index + 1,
70 text: raw.to_owned(),
71 };
72 let color = || RgbColor::parse(value).ok();
73 match key {
74 "palette" => {
75 let (slot, color) = Palette::parse_palette_entry(value).map_err(|_| error())?;
76 palette.set(slot, color);
77 }
78 "background" => colors.background = rgb(color().ok_or_else(error)?),
79 "foreground" => colors.foreground = rgb(color().ok_or_else(error)?),
80 "cursor-color" => colors.cursor = color().map(rgb),
81 "selection-background" => colors.selection_background = color().map(rgb),
82 "selection-foreground" => colors.selection_foreground = color().map(rgb),
83 _ => {}
84 }
85 }
86 colors.palette = palette.0.map(rgb);
87 Ok(colors)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn a_ghostty_theme_sets_palette_and_special_colors() {
97 let theme = "\
98# Dracula
99palette = 0=#21222c
100palette = 1=#ff5555
101background = #282a36
102foreground = f8f8f2
103cursor-color = #f8f8f2
104cursor-text = #282a36
105selection-background = cell-foreground
106window-padding-x = 10
107";
108 let colors = TerminalColors::from_ghostty_theme(theme).unwrap();
109 assert_eq!(colors.palette[0], Rgb(0x21, 0x22, 0x2c));
110 assert_eq!(colors.palette[1], Rgb(0xff, 0x55, 0x55));
111 assert_eq!(colors.palette[2], TerminalColors::default().palette[2]);
112 assert_eq!(colors.background, Rgb(0x28, 0x2a, 0x36));
113 assert_eq!(colors.foreground, Rgb(0xf8, 0xf8, 0xf2));
114 assert_eq!(colors.cursor, Some(Rgb(0xf8, 0xf8, 0xf2)));
115 assert_eq!(colors.selection_background, None);
116 }
117
118 #[test]
119 fn a_bad_line_reports_its_number() {
120 let error =
121 TerminalColors::from_ghostty_theme("background = #fff\npalette = x\n").unwrap_err();
122 assert_eq!(error.line(), 2);
123 }
124}