Skip to main content

lean_ctx/cli/
theme_cmd.rs

1use crate::core::config;
2use crate::core::theme;
3
4pub fn cmd_theme(args: &[String]) {
5    let sub = args.first().map_or("list", std::string::String::as_str);
6    let r = theme::rst();
7    let b = theme::bold();
8    let d = theme::dim();
9
10    match sub {
11        "list" => {
12            let cfg = config::Config::load();
13            let active = cfg.theme.as_str();
14            println!();
15            println!("  {b}Available themes:{r}");
16            println!("  {ln}", ln = "─".repeat(40));
17            for name in theme::PRESET_NAMES {
18                let marker = if *name == active { " ◀ active" } else { "" };
19                let Some(t) = theme::from_preset(name) else {
20                    continue;
21                };
22                let preview = format!(
23                    "{p}██{r}{s}██{r}{a}██{r}{sc}██{r}{w}██{r}",
24                    p = t.primary.fg(),
25                    s = t.secondary.fg(),
26                    a = t.accent.fg(),
27                    sc = t.success.fg(),
28                    w = t.warning.fg(),
29                );
30                println!("  {preview}  {b}{name:<12}{r}{d}{marker}{r}");
31            }
32            if let Some(path) = theme::theme_file_path()
33                && path.exists()
34            {
35                let custom = theme::load_theme("_custom_");
36                let preview = format!(
37                    "{p}██{r}{s}██{r}{a}██{r}{sc}██{r}{w}██{r}",
38                    p = custom.primary.fg(),
39                    s = custom.secondary.fg(),
40                    a = custom.accent.fg(),
41                    sc = custom.success.fg(),
42                    w = custom.warning.fg(),
43                );
44                let marker = if active == "custom" {
45                    " ◀ active"
46                } else {
47                    ""
48                };
49                println!("  {preview}  {b}{:<12}{r}{d}{marker}{r}", custom.name);
50            }
51            println!();
52            println!("  {d}Set theme: lean-ctx theme set <name>{r}");
53            println!();
54        }
55        "set" => {
56            if args.len() < 2 {
57                eprintln!("Usage: lean-ctx theme set <name>");
58                std::process::exit(1);
59            }
60            let name = &args[1];
61            if theme::from_preset(name).is_none() && name != "custom" {
62                eprintln!(
63                    "Unknown theme '{name}'. Available: {}",
64                    theme::PRESET_NAMES.join(", ")
65                );
66                std::process::exit(1);
67            }
68            match config::Config::update_global(|cfg| cfg.theme.clone_from(name)) {
69                Ok(_) => {
70                    let t = theme::load_theme(name);
71                    println!("  {sc}✓{r} Theme set to {b}{name}{r}", sc = t.success.fg());
72                    let preview = t.gradient_bar(0.75, 30);
73                    println!("  {preview}");
74                }
75                Err(e) => eprintln!("Error: {e}"),
76            }
77        }
78        "export" => {
79            let cfg = config::Config::load();
80            let t = theme::load_theme(&cfg.theme);
81            println!("{}", t.to_toml());
82        }
83        "import" => {
84            if args.len() < 2 {
85                eprintln!("Usage: lean-ctx theme import <path>");
86                std::process::exit(1);
87            }
88            let path = std::path::Path::new(&args[1]);
89            if !path.exists() {
90                eprintln!("File not found: {}", args[1]);
91                std::process::exit(1);
92            }
93            match std::fs::read_to_string(path) {
94                Ok(content) => match toml::from_str::<theme::Theme>(&content) {
95                    Ok(imported) => match theme::save_theme(&imported) {
96                        Ok(()) => {
97                            if let Err(e) = config::Config::update_global(|cfg| {
98                                cfg.theme = "custom".to_string();
99                            }) {
100                                tracing::warn!("could not persist theme: {e}");
101                            }
102                            println!(
103                                "  {sc}✓{r} Imported theme '{name}' → ~/.lean-ctx/theme.toml",
104                                sc = imported.success.fg(),
105                                name = imported.name,
106                            );
107                            println!("  Config updated: theme = custom");
108                        }
109                        Err(e) => eprintln!("Error saving theme: {e}"),
110                    },
111                    Err(e) => eprintln!("Invalid theme file: {e}"),
112                },
113                Err(e) => eprintln!("Error reading file: {e}"),
114            }
115        }
116        "preview" => {
117            let name = args.get(1).map_or("default", std::string::String::as_str);
118            let Some(t) = theme::from_preset(name) else {
119                eprintln!("Unknown theme: {name}");
120                std::process::exit(1);
121            };
122            println!();
123            println!(
124                "  {icon} {title}  {d}Theme Preview: {name}{r}",
125                icon = t.header_icon(),
126                title = t.brand_title(),
127            );
128            println!("  {ln}", ln = t.border_line(50));
129            println!();
130            println!(
131                "  {b}{sc} 1.2M      {r}  {b}{sec} 87.3%     {r}  {b}{wrn} 4,521    {r}  {b}{acc} $12.50   {r}",
132                sc = t.success.fg(),
133                sec = t.secondary.fg(),
134                wrn = t.warning.fg(),
135                acc = t.accent.fg(),
136            );
137            println!("  {d} tokens saved   compression    commands       USD saved{r}");
138            println!();
139            println!(
140                "  {b}{txt}Gradient Bar{r}      {bar}",
141                txt = t.text.fg(),
142                bar = t.gradient_bar(0.85, 30),
143            );
144            println!(
145                "  {b}{txt}Sparkline{r}         {spark}",
146                txt = t.text.fg(),
147                spark = t.gradient_sparkline(&[20, 40, 30, 80, 60, 90, 70]),
148            );
149            println!();
150            println!("  {top}", top = t.box_top(50));
151            println!(
152                "  {side}  {b}{txt}Box content with themed borders{r}                  {side_r}",
153                side = t.box_side(),
154                side_r = t.box_side(),
155                txt = t.text.fg(),
156            );
157            println!("  {bot}", bot = t.box_bottom(50));
158            println!();
159        }
160        "tokens" => {
161            let format = args.get(1).map_or("css", String::as_str);
162            let cfg = config::Config::load();
163            let t = theme::load_theme(&cfg.theme);
164            match format {
165                "css" => println!("{}", t.to_css_vars()),
166                "js" => print!("{}", t.to_js_tokens()),
167                _ => {
168                    eprintln!("Usage: lean-ctx theme tokens [css|js]");
169                    std::process::exit(1);
170                }
171            }
172        }
173        _ => {
174            eprintln!("Usage: lean-ctx theme [list|set|export|import|preview|tokens]");
175            std::process::exit(1);
176        }
177    }
178}