lean_ctx/cli/
theme_cmd.rs1use 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}