Skip to main content

lean_ctx/
terminal_ui.rs

1use std::io::{self, IsTerminal, Write};
2
3const LOGO: [&str; 6] = [
4    r"  ██╗     ███████╗ █████╗ ███╗   ██╗     ██████╗████████╗██╗  ██╗",
5    r"  ██║     ██╔════╝██╔══██╗████╗  ██║    ██╔════╝╚══██╔══╝╚██╗██╔╝",
6    r"  ██║     █████╗  ███████║██╔██╗ ██║    ██║        ██║    ╚███╔╝ ",
7    r"  ██║     ██╔══╝  ██╔══██║██║╚██╗██║    ██║        ██║    ██╔██╗ ",
8    r"  ███████╗███████╗██║  ██║██║ ╚████║    ╚██████╗   ██║   ██╔╝ ██╗",
9    r"  ╚══════╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═══╝     ╚═════╝   ╚═╝   ╚═╝  ╚═╝",
10];
11
12const TAGLINE: &str = "Context Runtime for AI Agents";
13
14pub fn print_logo_animated() {
15    let cfg = crate::core::config::Config::load();
16    let t = crate::core::theme::load_theme(&cfg.theme);
17    print_logo_animated_themed(&t);
18}
19
20pub fn print_logo_animated_themed(t: &crate::core::theme::Theme) {
21    if crate::core::theme::no_color() {
22        print_logo_plain();
23        return;
24    }
25    if !io::stdout().is_terminal() {
26        print_logo_themed_static(t);
27        return;
28    }
29
30    let mut stdout = io::stdout();
31    let frames = 28;
32    let frame_ms = 45;
33    let top_padding = 2;
34
35    let _ = writeln!(stdout);
36    let _ = writeln!(stdout);
37
38    for frame in 0..frames {
39        if frame > 0 {
40            print!("\x1b[{}A", LOGO.len() + 2 + top_padding);
41            for _ in 0..top_padding {
42                let _ = writeln!(stdout);
43            }
44        }
45
46        let wave_offset = frame as f64 / frames as f64;
47
48        for (i, line) in LOGO.iter().enumerate() {
49            let chars: Vec<char> = line.chars().collect();
50            let max_j = chars.len().max(1) as f64;
51            let mut buf = String::with_capacity(chars.len() * 20);
52
53            for (j, ch) in chars.iter().enumerate() {
54                if *ch == ' ' {
55                    buf.push(' ');
56                    continue;
57                }
58                let pos = j as f64 / max_j + i as f64 * 0.15;
59                let blend = ((pos + wave_offset * 2.0) * std::f64::consts::PI)
60                    .sin()
61                    .mul_add(0.5, 0.5);
62                let c = t.primary.lerp(&t.secondary, blend);
63                buf.push_str(&c.fg());
64                buf.push(*ch);
65            }
66            buf.push_str("\x1b[0m");
67            let _ = writeln!(stdout, "{buf}");
68        }
69
70        let tag_blend = ((wave_offset * 2.0 + 1.0) * std::f64::consts::PI)
71            .sin()
72            .mul_add(0.5, 0.5);
73        let tag_color = t.muted.lerp(&t.accent, tag_blend * 0.5);
74        let _ = writeln!(stdout, "{}             {TAGLINE}\x1b[0m", tag_color.fg());
75        let _ = writeln!(stdout);
76
77        let _ = stdout.flush();
78        std::thread::sleep(std::time::Duration::from_millis(frame_ms));
79    }
80
81    print!("\x1b[{}A", LOGO.len() + 2 + top_padding);
82    print_logo_themed_static(t);
83}
84
85pub fn print_logo_static() {
86    let cfg = crate::core::config::Config::load();
87    let t = crate::core::theme::load_theme(&cfg.theme);
88    print_logo_themed_static(&t);
89}
90
91fn print_logo_themed_static(t: &crate::core::theme::Theme) {
92    if crate::core::theme::no_color() {
93        print_logo_plain();
94        return;
95    }
96    let mut stdout = io::stdout();
97
98    let _ = writeln!(stdout);
99    let _ = writeln!(stdout);
100
101    for (i, line) in LOGO.iter().enumerate() {
102        let chars: Vec<char> = line.chars().collect();
103        let mut buf = String::with_capacity(chars.len() * 20);
104
105        for (j, ch) in chars.iter().enumerate() {
106            if *ch == ' ' {
107                buf.push(' ');
108                continue;
109            }
110            let progress = if chars.len() > 1 {
111                j as f64 / (chars.len() - 1) as f64
112            } else {
113                0.5
114            };
115            let row_t = i as f64 / (LOGO.len() - 1).max(1) as f64;
116            let blend = (progress + row_t * 0.3).min(1.0);
117            let c = t.primary.lerp(&t.secondary, blend);
118            buf.push_str(&c.fg());
119            buf.push(*ch);
120        }
121        buf.push_str("\x1b[0m");
122        let _ = writeln!(stdout, "{buf}");
123    }
124
125    let _ = writeln!(stdout, "{}             {TAGLINE}\x1b[0m", t.muted.fg());
126    let _ = writeln!(stdout);
127    let _ = stdout.flush();
128}
129
130fn print_logo_plain() {
131    println!();
132    println!();
133    for line in &LOGO {
134        println!("{line}");
135    }
136    println!("             {TAGLINE}");
137    println!();
138}
139
140pub fn print_command_box() {
141    use crate::core::theme;
142    let cfg = crate::core::config::Config::load();
143    let t = theme::load_theme(&cfg.theme);
144    let d = theme::dim();
145    let b = theme::bold();
146    let r = theme::rst();
147    let cmd = t.accent.fg();
148    let ok = t.success.fg();
149    let m = t.muted.fg();
150
151    println!("  {d}┌─────────────────────────────────────────────────────────┐{r}");
152    println!(
153        "  {d}│{r}  {cmd}{b}lean-ctx gain{r}        {m}Token savings dashboard{r}         {d}│{r}"
154    );
155    println!(
156        "  {d}│{r}  {cmd}{b}lean-ctx dashboard{r}   {m}Web analytics (browser){r}        {d}│{r}"
157    );
158    println!(
159        "  {d}│{r}  {cmd}{b}lean-ctx heatmap{r}     {m}Project context heat map{r}        {d}│{r}"
160    );
161    println!(
162        "  {d}│{r}  {cmd}{b}lean-ctx benchmark{r}   {m}Test compression quality{r}        {d}│{r}"
163    );
164    println!(
165        "  {d}│{r}  {cmd}{b}lean-ctx config{r}      {m}Edit settings{r}                   {d}│{r}"
166    );
167    println!(
168        "  {d}│{r}  {cmd}{b}lean-ctx doctor{r}      {m}Verify installation{r}             {d}│{r}"
169    );
170    println!(
171        "  {d}│{r}  {cmd}{b}lean-ctx update{r}      {m}Self-update to latest{r}           {d}│{r}"
172    );
173    println!("  {d}│{r}  {cmd}{b}lean-ctx off{r} / {cmd}{b}on{r}    {m}Toggle compression{r}              {d}│{r}");
174    println!(
175        "  {d}│{r}  {cmd}{b}lean-ctx report-issue{r} {m}Report a bug (auto-diagnostics){r} {d}│{r}"
176    );
177    println!("  {d}│{r}  {cmd}{b}lean-ctx contribute{r}  {m}Share anonymized compression stats{r}{d}│{r}");
178    println!(
179        "  {d}│{r}  {cmd}{b}lean-ctx uninstall{r}   {m}Clean removal{r}                   {d}│{r}"
180    );
181    println!("  {d}└─────────────────────────────────────────────────────────┘{r}");
182    println!("  {ok}Ready!{r} Your next AI command will be automatically optimized.");
183    println!("  {d}Docs: https://leanctx.com/docs{r}");
184    println!();
185}
186
187pub fn print_step_header(step: u8, total: u8, title: &str) {
188    let dim = "\x1b[2m";
189    let bold = "\x1b[1m";
190    let cyan = "\x1b[36m";
191    let rst = "\x1b[0m";
192    println!();
193    println!("  {cyan}{bold}[{step}/{total}]{rst} {bold}{title}{rst}");
194    println!("  {dim}─────────────────────────────────────────────────────{rst}");
195}
196
197pub fn print_status_ok(msg: &str) {
198    println!("  \x1b[32m✓\x1b[0m {msg}");
199}
200
201pub fn print_status_skip(msg: &str) {
202    println!("  \x1b[2m○\x1b[0m \x1b[2m{msg}\x1b[0m");
203}
204
205pub fn print_status_new(msg: &str) {
206    println!("  \x1b[1;32m✓\x1b[0m \x1b[1m{msg}\x1b[0m");
207}
208
209pub fn print_status_warn(msg: &str) {
210    println!("  \x1b[33m⚠\x1b[0m {msg}");
211}
212
213pub fn spinner_tick(msg: &str, frame: usize) {
214    let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
215    let ch = frames[frame % frames.len()];
216    print!("\r  \x1b[36m{ch}\x1b[0m {msg}");
217    let _ = io::stdout().flush();
218}
219
220pub fn spinner_done(msg: &str) {
221    print!("\r  \x1b[32m✓\x1b[0m {msg}\x1b[K\n");
222    let _ = io::stdout().flush();
223}
224
225pub fn print_setup_header() {
226    let dim = "\x1b[2m";
227    let bold = "\x1b[1m";
228    let green = "\x1b[32m";
229    let rst = "\x1b[0m";
230    println!();
231    println!("  {dim}╭──────────────────────────────────────────╮{rst}");
232    println!(
233        "  {dim}│{rst}  {green}{bold}◆ lean-ctx setup{rst}                         {dim}│{rst}"
234    );
235    println!("  {dim}│{rst}  {dim}Configuring your development environment{rst} {dim}│{rst}");
236    println!("  {dim}╰──────────────────────────────────────────╯{rst}");
237    println!();
238}