Skip to main content

vtcode_core/terminal_setup/features/
theme_sync.rs

1//! Theme synchronization feature configuration generator.
2//!
3//! Generates terminal-specific color scheme configuration to match VT Code themes.
4//! Supports dark and light theme variants.
5
6use crate::terminal_setup::detector::TerminalType;
7use anyhow::{Result, anyhow};
8
9/// Default VT Code dark theme colors.
10pub struct VTCodeDarkTheme {
11    /// Terminal background color in hex (`#RRGGBB`).
12    pub background: &'static str,
13    /// Terminal foreground (text) color in hex.
14    pub foreground: &'static str,
15    /// Cursor color in hex.
16    pub cursor: &'static str,
17    /// Selection background color in hex.
18    pub selection_bg: &'static str,
19    /// ANSI black (color index 0).
20    pub black: &'static str,
21    /// ANSI red (color index 1).
22    pub red: &'static str,
23    /// ANSI green (color index 2).
24    pub green: &'static str,
25    /// ANSI yellow (color index 3).
26    pub yellow: &'static str,
27    /// ANSI blue (color index 4).
28    pub blue: &'static str,
29    /// ANSI magenta (color index 5).
30    pub magenta: &'static str,
31    /// ANSI cyan (color index 6).
32    pub cyan: &'static str,
33    /// ANSI white (color index 7).
34    pub white: &'static str,
35    /// Bright black, a.k.a. dark gray (color index 8).
36    pub bright_black: &'static str,
37    /// Bright red (color index 9).
38    pub bright_red: &'static str,
39    /// Bright green (color index 10).
40    pub bright_green: &'static str,
41    /// Bright yellow (color index 11).
42    pub bright_yellow: &'static str,
43    /// Bright blue (color index 12).
44    pub bright_blue: &'static str,
45    /// Bright magenta (color index 13).
46    pub bright_magenta: &'static str,
47    /// Bright cyan (color index 14).
48    pub bright_cyan: &'static str,
49    /// Bright white (color index 15).
50    pub bright_white: &'static str,
51}
52
53impl Default for VTCodeDarkTheme {
54    fn default() -> Self {
55        Self {
56            background: "#1e1e1e",
57            foreground: "#d4d4d4",
58            cursor: "#ffffff",
59            selection_bg: "#264f78",
60            // ANSI colors
61            black: "#000000",
62            red: "#cd3131",
63            green: "#0dbc79",
64            yellow: "#e5e510",
65            blue: "#2472c8",
66            magenta: "#bc3fbc",
67            cyan: "#11a8cd",
68            white: "#e5e5e5",
69            // Bright variants
70            bright_black: "#666666",
71            bright_red: "#f14c4c",
72            bright_green: "#23d18b",
73            bright_yellow: "#f5f543",
74            bright_blue: "#3b8eea",
75            bright_magenta: "#d670d6",
76            bright_cyan: "#29b8db",
77            bright_white: "#ffffff",
78        }
79    }
80}
81
82impl VTCodeDarkTheme {
83    fn base16_colors(&self) -> [&'static str; 16] {
84        [
85            self.black,
86            self.red,
87            self.green,
88            self.yellow,
89            self.blue,
90            self.magenta,
91            self.cyan,
92            self.white,
93            self.bright_black,
94            self.bright_red,
95            self.bright_green,
96            self.bright_yellow,
97            self.bright_blue,
98            self.bright_magenta,
99            self.bright_cyan,
100            self.bright_white,
101        ]
102    }
103}
104
105#[derive(Clone, Copy, Debug)]
106struct Rgb {
107    r: u8,
108    g: u8,
109    b: u8,
110}
111
112#[derive(Clone, Copy, Debug)]
113struct Lab {
114    l: f64,
115    a: f64,
116    b: f64,
117}
118
119impl Rgb {
120    fn from_hex(hex: &str) -> Result<Self> {
121        let trimmed = hex.trim_start_matches('#');
122        if trimmed.len() != 6 {
123            return Err(anyhow!("Invalid hex color '{hex}': expected #RRGGBB"));
124        }
125        if !trimmed.is_ascii() {
126            return Err(anyhow!("Invalid hex color '{hex}': expected #RRGGBB"));
127        }
128
129        let r = u8::from_str_radix(&trimmed[0..2], 16).map_err(|e| anyhow!("Invalid red component in '{hex}': {e}"))?;
130        let g =
131            u8::from_str_radix(&trimmed[2..4], 16).map_err(|e| anyhow!("Invalid green component in '{hex}': {e}"))?;
132        let b =
133            u8::from_str_radix(&trimmed[4..6], 16).map_err(|e| anyhow!("Invalid blue component in '{hex}': {e}"))?;
134
135        Ok(Self { r, g, b })
136    }
137
138    fn to_hex(self) -> String {
139        format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
140    }
141
142    fn to_lab(self) -> Lab {
143        let r = srgb_to_linear(self.r as f64 / 255.0);
144        let g = srgb_to_linear(self.g as f64 / 255.0);
145        let b = srgb_to_linear(self.b as f64 / 255.0);
146
147        let x = r * 0.412_456_4 + g * 0.357_576_1 + b * 0.180_437_5;
148        let y = r * 0.212_672_9 + g * 0.715_152_2 + b * 0.072_175;
149        let z = r * 0.019_333_9 + g * 0.119_192 + b * 0.950_304_1;
150
151        let fx = lab_f(x / 0.95047);
152        let fy = lab_f(y);
153        let fz = lab_f(z / 1.08883);
154
155        Lab {
156            l: 116.0 * fy - 16.0,
157            a: 500.0 * (fx - fy),
158            b: 200.0 * (fy - fz),
159        }
160    }
161
162    fn from_lab(lab: Lab) -> Self {
163        let fy = (lab.l + 16.0) / 116.0;
164        let fx = fy + (lab.a / 500.0);
165        let fz = fy - (lab.b / 200.0);
166
167        let x = 0.95047 * lab_f_inv(fx);
168        let y = lab_f_inv(fy);
169        let z = 1.08883 * lab_f_inv(fz);
170
171        let r_linear = x * 3.240_454_2 + y * -1.537_138_5 + z * -0.498_531_4;
172        let g_linear = x * -0.969_266 + y * 1.876_010_8 + z * 0.041_556;
173        let b_linear = x * 0.055_643_4 + y * -0.204_025_9 + z * 1.057_225_2;
174
175        Self {
176            r: to_u8(linear_to_srgb(r_linear)),
177            g: to_u8(linear_to_srgb(g_linear)),
178            b: to_u8(linear_to_srgb(b_linear)),
179        }
180    }
181}
182
183fn srgb_to_linear(channel: f64) -> f64 {
184    if channel <= 0.04045 {
185        channel / 12.92
186    } else {
187        ((channel + 0.055) / 1.055).powf(2.4)
188    }
189}
190
191fn linear_to_srgb(channel: f64) -> f64 {
192    if channel <= 0.0031308 {
193        12.92 * channel
194    } else {
195        1.055 * channel.powf(1.0 / 2.4) - 0.055
196    }
197}
198
199fn lab_f(value: f64) -> f64 {
200    if value > 216.0 / 24389.0 {
201        value.cbrt()
202    } else {
203        (24389.0 / 27.0 * value + 16.0) / 116.0
204    }
205}
206
207fn lab_f_inv(value: f64) -> f64 {
208    let cube = value * value * value;
209    if cube > 216.0 / 24389.0 {
210        cube
211    } else {
212        (116.0 * value - 16.0) / (24389.0 / 27.0)
213    }
214}
215
216fn to_u8(value: f64) -> u8 {
217    #[allow(
218        clippy::cast_sign_loss,
219        reason = "Intentional compatibility, platform, or test-only suppression."
220    )]
221    {
222        (value.clamp(0.0, 1.0) * 255.0).round() as u8
223    }
224}
225
226fn lerp_lab(t: f64, start: Lab, end: Lab) -> Lab {
227    Lab {
228        l: start.l + t * (end.l - start.l),
229        a: start.a + t * (end.a - start.a),
230        b: start.b + t * (end.b - start.b),
231    }
232}
233
234fn generate_256_palette(theme: &VTCodeDarkTheme, harmonious: bool) -> Result<Vec<Rgb>> {
235    let base16 = theme
236        .base16_colors()
237        .iter()
238        .map(|color| Rgb::from_hex(color))
239        .collect::<Result<Vec<_>>>()?;
240
241    let background = Rgb::from_hex(theme.background)?;
242    let foreground = Rgb::from_hex(theme.foreground)?;
243
244    let mut base8_lab = [
245        background.to_lab(),
246        base16[1].to_lab(),
247        base16[2].to_lab(),
248        base16[3].to_lab(),
249        base16[4].to_lab(),
250        base16[5].to_lab(),
251        base16[6].to_lab(),
252        foreground.to_lab(),
253    ];
254
255    let is_light_theme = base8_lab[7].l < base8_lab[0].l;
256    if is_light_theme && !harmonious {
257        base8_lab.swap(0, 7);
258    }
259
260    let mut palette = base16;
261
262    for r in 0..6 {
263        let t_r = r as f64 / 5.0;
264        let c0 = lerp_lab(t_r, base8_lab[0], base8_lab[1]);
265        let c1 = lerp_lab(t_r, base8_lab[2], base8_lab[3]);
266        let c2 = lerp_lab(t_r, base8_lab[4], base8_lab[5]);
267        let c3 = lerp_lab(t_r, base8_lab[6], base8_lab[7]);
268
269        for g in 0..6 {
270            let t_g = g as f64 / 5.0;
271            let c4 = lerp_lab(t_g, c0, c1);
272            let c5 = lerp_lab(t_g, c2, c3);
273
274            for b in 0..6 {
275                let t_b = b as f64 / 5.0;
276                let color = lerp_lab(t_b, c4, c5);
277                palette.push(Rgb::from_lab(color));
278            }
279        }
280    }
281
282    for shade in 0..24 {
283        let t = (shade as f64 + 1.0) / 25.0;
284        let color = lerp_lab(t, base8_lab[0], base8_lab[7]);
285        palette.push(Rgb::from_lab(color));
286    }
287
288    Ok(palette)
289}
290
291fn ghostty_palette_lines(palette: &[Rgb]) -> String {
292    palette
293        .iter()
294        .enumerate()
295        .map(|(index, color)| format!("palette = {index}={}", color.to_hex()))
296        .collect::<Vec<_>>()
297        .join("\n")
298}
299
300fn kitty_palette_lines(palette: &[Rgb]) -> String {
301    palette
302        .iter()
303        .enumerate()
304        .map(|(index, color)| format!("color{index} {}", color.to_hex()))
305        .collect::<Vec<_>>()
306        .join("\n")
307}
308
309/// Generate theme configuration for the specified terminal
310pub fn generate_config(terminal: TerminalType) -> Result<String> {
311    let theme = VTCodeDarkTheme::default();
312    let generated_palette = generate_256_palette(&theme, false)?;
313
314    let config = match terminal {
315        TerminalType::Ghostty => {
316            let mut config = format!(
317                r#"# VT Code Dark Theme for Ghostty
318background = {background}
319foreground = {foreground}
320cursor-color = {cursor}
321selection-background = {selection_bg}
322"#,
323                background = theme.background,
324                foreground = theme.foreground,
325                cursor = theme.cursor,
326                selection_bg = theme.selection_bg,
327            );
328            config.push_str("\n# ANSI + 256-color palette generated from base colors\n");
329            config.push_str(&ghostty_palette_lines(&generated_palette));
330            config.push('\n');
331            config
332        }
333
334        TerminalType::Kitty => {
335            let mut config = format!(
336                r#"# VT Code Dark Theme for Kitty
337background {background}
338foreground {foreground}
339cursor {cursor}
340selection_background {selection_bg}
341"#,
342                background = theme.background,
343                foreground = theme.foreground,
344                cursor = theme.cursor,
345                selection_bg = theme.selection_bg,
346            );
347            config.push_str("\n# ANSI + 256-color palette generated from base colors\n");
348            config.push_str(&kitty_palette_lines(&generated_palette));
349            config.push('\n');
350            config
351        }
352
353        TerminalType::Alacritty => {
354            let mut config = format!(
355                r#"# VT Code Dark Theme for Alacritty
356[colors.primary]
357background = '{background}'
358foreground = '{foreground}'
359
360[colors.cursor]
361cursor = '{cursor}'
362
363[colors.selection]
364background = '{selection_bg}'
365
366[colors.normal]
367black = '{black}'
368red = '{red}'
369green = '{green}'
370yellow = '{yellow}'
371blue = '{blue}'
372magenta = '{magenta}'
373cyan = '{cyan}'
374white = '{white}'
375
376[colors.bright]
377black = '{bright_black}'
378red = '{bright_red}'
379green = '{bright_green}'
380yellow = '{bright_yellow}'
381blue = '{bright_blue}'
382magenta = '{bright_magenta}'
383cyan = '{bright_cyan}'
384white = '{bright_white}'
385"#,
386                background = theme.background,
387                foreground = theme.foreground,
388                cursor = theme.cursor,
389                selection_bg = theme.selection_bg,
390                black = theme.black,
391                red = theme.red,
392                green = theme.green,
393                yellow = theme.yellow,
394                blue = theme.blue,
395                magenta = theme.magenta,
396                cyan = theme.cyan,
397                white = theme.white,
398                bright_black = theme.bright_black,
399                bright_red = theme.bright_red,
400                bright_green = theme.bright_green,
401                bright_yellow = theme.bright_yellow,
402                bright_blue = theme.bright_blue,
403                bright_magenta = theme.bright_magenta,
404                bright_cyan = theme.bright_cyan,
405                bright_white = theme.bright_white,
406            );
407
408            config.push_str("\n# Extended indexed colors (16-255)\n");
409            for (index, color) in generated_palette.iter().enumerate().skip(16) {
410                config.push_str("[[colors.indexed_colors]]\n");
411                config.push_str(&format!("index = {index}\n"));
412                config.push_str(&format!("color = '{}'\n\n", color.to_hex()));
413            }
414
415            config
416        }
417
418        TerminalType::WezTerm => {
419            format!(
420                r#"-- VT Code Dark Theme for WezTerm
421return {{
422  colors = {{
423    background = "{background}",
424    foreground = "{foreground}",
425    cursor_bg = "{cursor}",
426    selection_bg = "{selection_bg}",
427  }},
428}}
429"#,
430                background = theme.background,
431                foreground = theme.foreground,
432                cursor = theme.cursor,
433                selection_bg = theme.selection_bg,
434            )
435        }
436
437        TerminalType::TerminalApp => r#"Terminal.app theme sync requires profile color configuration.
438Configure profile colors in Terminal → Settings → Profiles.
439"#
440        .to_string(),
441
442        TerminalType::Xterm => r#"xterm theme sync is configured via X resources (e.g. ~/.Xresources).
443"#
444        .to_string(),
445
446        TerminalType::Zed => {
447            format!(
448                r#"// VT Code Dark Theme for Zed
449{{
450  "theme": {{
451    "mode": "dark",
452    "terminal": {{
453      "background": "{background}",
454      "foreground": "{foreground}",
455      "cursor": "{cursor}",
456      "selectionBackground": "{selection_bg}",
457      "ansiBlack": "{black}",
458      "ansiRed": "{red}",
459      "ansiGreen": "{green}",
460      "ansiYellow": "{yellow}",
461      "ansiBlue": "{blue}",
462      "ansiMagenta": "{magenta}",
463      "ansiCyan": "{cyan}",
464      "ansiWhite": "{white}",
465      "ansiBrightBlack": "{bright_black}",
466      "ansiBrightRed": "{bright_red}",
467      "ansiBrightGreen": "{bright_green}",
468      "ansiBrightYellow": "{bright_yellow}",
469      "ansiBrightBlue": "{bright_blue}",
470      "ansiBrightMagenta": "{bright_magenta}",
471      "ansiBrightCyan": "{bright_cyan}",
472      "ansiBrightWhite": "{bright_white}"
473    }}
474  }}
475}}
476"#,
477                background = theme.background,
478                foreground = theme.foreground,
479                cursor = theme.cursor,
480                selection_bg = theme.selection_bg,
481                black = theme.black,
482                red = theme.red,
483                green = theme.green,
484                yellow = theme.yellow,
485                blue = theme.blue,
486                magenta = theme.magenta,
487                cyan = theme.cyan,
488                white = theme.white,
489                bright_black = theme.bright_black,
490                bright_red = theme.bright_red,
491                bright_green = theme.bright_green,
492                bright_yellow = theme.bright_yellow,
493                bright_blue = theme.bright_blue,
494                bright_magenta = theme.bright_magenta,
495                bright_cyan = theme.bright_cyan,
496                bright_white = theme.bright_white,
497            )
498        }
499
500        TerminalType::Warp => r#"# Warp Theme Synchronization
501# Warp uses its own theme system
502# To create a custom theme:
503# 1. Open Warp Settings
504# 2. Go to Appearance → Themes
505# 3. Click "New Theme" or "Import Theme"
506# 4. Use the VT Code color values provided in the wizard
507
508# VT Code colors are displayed in the terminal setup output
509# You can manually configure them in Warp's theme editor
510"#
511        .to_string(),
512
513        TerminalType::WindowsTerminal => {
514            format!(
515                r#"{{
516  "schemes": [
517    {{
518      "name": "VT Code Dark",
519      "background": "{background}",
520      "foreground": "{foreground}",
521      "cursorColor": "{cursor}",
522      "selectionBackground": "{selection_bg}",
523      "black": "{black}",
524      "red": "{red}",
525      "green": "{green}",
526      "yellow": "{yellow}",
527      "blue": "{blue}",
528      "purple": "{magenta}",
529      "cyan": "{cyan}",
530      "white": "{white}",
531      "brightBlack": "{bright_black}",
532      "brightRed": "{bright_red}",
533      "brightGreen": "{bright_green}",
534      "brightYellow": "{bright_yellow}",
535      "brightBlue": "{bright_blue}",
536      "brightPurple": "{bright_magenta}",
537      "brightCyan": "{bright_cyan}",
538      "brightWhite": "{bright_white}"
539    }}
540  ],
541  "profiles": {{
542    "defaults": {{
543      "colorScheme": "VT Code Dark"
544    }}
545  }}
546}}
547"#,
548                background = theme.background,
549                foreground = theme.foreground,
550                cursor = theme.cursor,
551                selection_bg = theme.selection_bg,
552                black = theme.black,
553                red = theme.red,
554                green = theme.green,
555                yellow = theme.yellow,
556                blue = theme.blue,
557                magenta = theme.magenta,
558                cyan = theme.cyan,
559                white = theme.white,
560                bright_black = theme.bright_black,
561                bright_red = theme.bright_red,
562                bright_green = theme.bright_green,
563                bright_yellow = theme.bright_yellow,
564                bright_blue = theme.bright_blue,
565                bright_magenta = theme.bright_magenta,
566                bright_cyan = theme.bright_cyan,
567                bright_white = theme.bright_white,
568            )
569        }
570
571        TerminalType::Hyper => {
572            format!(
573                r#"// VT Code Dark Theme for Hyper
574module.exports = {{
575  config: {{
576    backgroundColor: '{background}',
577    foregroundColor: '{foreground}',
578    cursorColor: '{cursor}',
579    selectionColor: '{selection_bg}',
580    colors: {{
581      black: '{black}',
582      red: '{red}',
583      green: '{green}',
584      yellow: '{yellow}',
585      blue: '{blue}',
586      magenta: '{magenta}',
587      cyan: '{cyan}',
588      white: '{white}',
589      lightBlack: '{bright_black}',
590      lightRed: '{bright_red}',
591      lightGreen: '{bright_green}',
592      lightYellow: '{bright_yellow}',
593      lightBlue: '{bright_blue}',
594      lightMagenta: '{bright_magenta}',
595      lightCyan: '{bright_cyan}',
596      lightWhite: '{bright_white}',
597    }}
598  }}
599}};
600"#,
601                background = theme.background,
602                foreground = theme.foreground,
603                cursor = theme.cursor,
604                selection_bg = theme.selection_bg,
605                black = theme.black,
606                red = theme.red,
607                green = theme.green,
608                yellow = theme.yellow,
609                blue = theme.blue,
610                magenta = theme.magenta,
611                cyan = theme.cyan,
612                white = theme.white,
613                bright_black = theme.bright_black,
614                bright_red = theme.bright_red,
615                bright_green = theme.bright_green,
616                bright_yellow = theme.bright_yellow,
617                bright_blue = theme.bright_blue,
618                bright_magenta = theme.bright_magenta,
619                bright_cyan = theme.bright_cyan,
620                bright_white = theme.bright_white,
621            )
622        }
623
624        TerminalType::Tabby => {
625            format!(
626                r#"# VT Code Dark Theme for Tabby
627appearance:
628  colorScheme:
629    name: "VT Code Dark"
630    foreground: "{foreground}"
631    background: "{background}"
632    cursor: "{cursor}"
633    selection: "{selection_bg}"
634    colors:
635      - "{black}"
636      - "{red}"
637      - "{green}"
638      - "{yellow}"
639      - "{blue}"
640      - "{magenta}"
641      - "{cyan}"
642      - "{white}"
643      - "{bright_black}"
644      - "{bright_red}"
645      - "{bright_green}"
646      - "{bright_yellow}"
647      - "{bright_blue}"
648      - "{bright_magenta}"
649      - "{bright_cyan}"
650      - "{bright_white}"
651"#,
652                background = theme.background,
653                foreground = theme.foreground,
654                cursor = theme.cursor,
655                selection_bg = theme.selection_bg,
656                black = theme.black,
657                red = theme.red,
658                green = theme.green,
659                yellow = theme.yellow,
660                blue = theme.blue,
661                magenta = theme.magenta,
662                cyan = theme.cyan,
663                white = theme.white,
664                bright_black = theme.bright_black,
665                bright_red = theme.bright_red,
666                bright_green = theme.bright_green,
667                bright_yellow = theme.bright_yellow,
668                bright_blue = theme.bright_blue,
669                bright_magenta = theme.bright_magenta,
670                bright_cyan = theme.bright_cyan,
671                bright_white = theme.bright_white,
672            )
673        }
674
675        TerminalType::ITerm2 => r#"Manual iTerm2 Theme Configuration:
676
6771. Open iTerm2 Preferences (Cmd+,)
6782. Go to Profiles → Colors
6793. Click "Color Presets..." → "Import..."
6804. Or manually configure colors:
681
682Background: #1e1e1e
683Foreground: #d4d4d4
684Cursor: #ffffff
685Selection: #264f78
686
687ANSI Colors:
688Black: #000000, Red: #cd3131, Green: #0dbc79, Yellow: #e5e510
689Blue: #2472c8, Magenta: #bc3fbc, Cyan: #11a8cd, White: #e5e5e5
690
691Bright Colors:
692Black: #666666, Red: #f14c4c, Green: #23d18b, Yellow: #f5f543
693Blue: #3b8eea, Magenta: #d670d6, Cyan: #29b8db, White: #ffffff
694
695Alternative: Download VT Code.itermcolors file and import
696"#
697        .to_string(),
698
699        TerminalType::VSCode => {
700            format!(
701                r#"VS Code Terminal Theme Configuration:
702
703The terminal automatically inherits your VS Code theme colors.
704
705To customize terminal colors independently, add to settings.json:
706{{
707  "workbench.colorCustomizations": {{
708    "terminal.background": "{background}",
709    "terminal.foreground": "{foreground}",
710    "terminalCursor.background": "{cursor}",
711    "terminal.selectionBackground": "{selection_bg}",
712    "terminal.ansiBlack": "{black}",
713    "terminal.ansiRed": "{red}",
714    "terminal.ansiGreen": "{green}",
715    "terminal.ansiYellow": "{yellow}",
716    "terminal.ansiBlue": "{blue}",
717    "terminal.ansiMagenta": "{magenta}",
718    "terminal.ansiCyan": "{cyan}",
719    "terminal.ansiWhite": "{white}",
720    "terminal.ansiBrightBlack": "{bright_black}",
721    "terminal.ansiBrightRed": "{bright_red}",
722    "terminal.ansiBrightGreen": "{bright_green}",
723    "terminal.ansiBrightYellow": "{bright_yellow}",
724    "terminal.ansiBrightBlue": "{bright_blue}",
725    "terminal.ansiBrightMagenta": "{bright_magenta}",
726    "terminal.ansiBrightCyan": "{bright_cyan}",
727    "terminal.ansiBrightWhite": "{bright_white}"
728  }}
729}}
730"#,
731                background = theme.background,
732                foreground = theme.foreground,
733                cursor = theme.cursor,
734                selection_bg = theme.selection_bg,
735                black = theme.black,
736                red = theme.red,
737                green = theme.green,
738                yellow = theme.yellow,
739                blue = theme.blue,
740                magenta = theme.magenta,
741                cyan = theme.cyan,
742                white = theme.white,
743                bright_black = theme.bright_black,
744                bright_red = theme.bright_red,
745                bright_green = theme.bright_green,
746                bright_yellow = theme.bright_yellow,
747                bright_blue = theme.bright_blue,
748                bright_magenta = theme.bright_magenta,
749                bright_cyan = theme.bright_cyan,
750                bright_white = theme.bright_white,
751            )
752        }
753
754        TerminalType::Unknown => {
755            anyhow::bail!("Cannot generate theme config for unknown terminal type");
756        }
757    };
758
759    Ok(config)
760}
761
762#[cfg(test)]
763mod tests {
764    use super::*;
765
766    #[test]
767    fn test_vtcode_dark_theme_defaults() {
768        let theme = VTCodeDarkTheme::default();
769        assert_eq!(theme.background, "#1e1e1e");
770        assert_eq!(theme.foreground, "#d4d4d4");
771        assert_eq!(theme.cursor, "#ffffff");
772    }
773
774    #[test]
775    fn test_generate_ghostty_config() {
776        let config = generate_config(TerminalType::Ghostty).unwrap();
777        assert!(config.contains("palette = 0="));
778        assert!(config.contains("palette = 255="));
779        assert!(config.contains("#1e1e1e"));
780    }
781
782    #[test]
783    fn test_generate_kitty_config() {
784        let config = generate_config(TerminalType::Kitty).unwrap();
785        assert!(config.contains("color0 "));
786        assert!(config.contains("color255 "));
787    }
788
789    #[test]
790    fn test_generate_alacritty_config() {
791        let config = generate_config(TerminalType::Alacritty).unwrap();
792        assert!(config.contains("[colors"));
793        assert!(config.contains("primary"));
794        assert!(config.contains("index = 255"));
795    }
796
797    #[test]
798    fn test_generate_windows_terminal_config() {
799        let config = generate_config(TerminalType::WindowsTerminal).unwrap();
800        assert!(config.contains("schemes"));
801        assert!(config.contains("VT Code Dark"));
802    }
803
804    #[test]
805    fn test_generate_vscode_instructions() {
806        let config = generate_config(TerminalType::VSCode).unwrap();
807        assert!(config.contains("workbench.colorCustomizations"));
808        assert!(config.contains("terminal.ansi"));
809    }
810
811    #[test]
812    fn test_unknown_terminal_error() {
813        let result = generate_config(TerminalType::Unknown);
814        result.unwrap_err();
815    }
816
817    #[test]
818    fn test_generate_config() {
819        // This test exists for backward compatibility with the stub
820        generate_config(TerminalType::Kitty).unwrap();
821    }
822
823    #[test]
824    fn test_generated_palette_has_256_entries_and_preserves_base16() {
825        let theme = VTCodeDarkTheme::default();
826        let palette = generate_256_palette(&theme, false).unwrap();
827
828        assert_eq!(palette.len(), 256);
829
830        let expected_base16: Vec<String> = theme
831            .base16_colors()
832            .iter()
833            .map(|c| Rgb::from_hex(c))
834            .collect::<Result<Vec<_>, _>>()
835            .expect("base16 colors should be valid hex")
836            .into_iter()
837            .map(|rgb| rgb.to_hex())
838            .collect();
839        let actual_base16 = palette.iter().take(16).map(|rgb| rgb.to_hex()).collect::<Vec<_>>();
840
841        assert_eq!(actual_base16, expected_base16);
842    }
843
844    #[test]
845    fn test_non_ascii_hex_is_rejected_without_panicking() {
846        assert!(Rgb::from_hex("红色").is_err());
847    }
848}