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                if path.exists() {
34                    let custom = theme::load_theme("_custom_");
35                    let preview = format!(
36                        "{p}██{r}{s}██{r}{a}██{r}{sc}██{r}{w}██{r}",
37                        p = custom.primary.fg(),
38                        s = custom.secondary.fg(),
39                        a = custom.accent.fg(),
40                        sc = custom.success.fg(),
41                        w = custom.warning.fg(),
42                    );
43                    let marker = if active == "custom" {
44                        " ◀ active"
45                    } else {
46                        ""
47                    };
48                    println!("  {preview}  {b}{:<12}{r}{d}{marker}{r}", custom.name);
49                }
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            let mut cfg = config::Config::load();
69            cfg.theme.clone_from(name);
70            match cfg.save() {
71                Ok(()) => {
72                    let t = theme::load_theme(name);
73                    println!("  {sc}✓{r} Theme set to {b}{name}{r}", sc = t.success.fg());
74                    let preview = t.gradient_bar(0.75, 30);
75                    println!("  {preview}");
76                }
77                Err(e) => eprintln!("Error: {e}"),
78            }
79        }
80        "export" => {
81            let cfg = config::Config::load();
82            let t = theme::load_theme(&cfg.theme);
83            println!("{}", t.to_toml());
84        }
85        "import" => {
86            if args.len() < 2 {
87                eprintln!("Usage: lean-ctx theme import <path>");
88                std::process::exit(1);
89            }
90            let path = std::path::Path::new(&args[1]);
91            if !path.exists() {
92                eprintln!("File not found: {}", args[1]);
93                std::process::exit(1);
94            }
95            match std::fs::read_to_string(path) {
96                Ok(content) => match toml::from_str::<theme::Theme>(&content) {
97                    Ok(imported) => match theme::save_theme(&imported) {
98                        Ok(()) => {
99                            let mut cfg = config::Config::load();
100                            cfg.theme = "custom".to_string();
101                            let _ = cfg.save();
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        _ => {
161            eprintln!("Usage: lean-ctx theme [list|set|export|import|preview]");
162            std::process::exit(1);
163        }
164    }
165}