1use crate::terminal_setup::detector::TerminalType;
7use anyhow::{Result, anyhow};
8
9pub struct VTCodeDarkTheme {
11 pub background: &'static str,
13 pub foreground: &'static str,
15 pub cursor: &'static str,
17 pub selection_bg: &'static str,
19 pub black: &'static str,
21 pub red: &'static str,
23 pub green: &'static str,
25 pub yellow: &'static str,
27 pub blue: &'static str,
29 pub magenta: &'static str,
31 pub cyan: &'static str,
33 pub white: &'static str,
35 pub bright_black: &'static str,
37 pub bright_red: &'static str,
39 pub bright_green: &'static str,
41 pub bright_yellow: &'static str,
43 pub bright_blue: &'static str,
45 pub bright_magenta: &'static str,
47 pub bright_cyan: &'static str,
49 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 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_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
309pub 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 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}