1pub struct CliFormatter;
2
3impl CliFormatter {
4 pub fn print_section_header(title: &str) {
5 println!("\n{}", ansi_color("clean_blue", title, true));
6 println!("{}", "─".repeat(title.len()).dimmed());
7 }
8
9 pub fn print_field(label: &str, value: &str, color: Option<&str>) {
10 let colored_value = match color {
11 Some(c) => ansi_color(c, value, false),
12 None => value.to_string(),
13 };
14 println!(
15 "{} {:<20} {}",
16 "│".dimmed(),
17 format!("{}:", label).dimmed(),
18 colored_value
19 );
20 }
21
22 pub fn print_field_bold(label: &str, value: &str, color: Option<&str>) {
23 let colored_value = match color {
24 Some(c) => ansi_color(c, value, true),
25 None => bold(value),
26 };
27 println!(
28 "{} {:<20} {}",
29 "│".dimmed(),
30 format!("{}:", label).dimmed(),
31 colored_value
32 );
33 }
34
35 pub fn print_status(status: &str, is_active: bool) {
36 let (symbol, color) = if is_active {
37 ("●", "accent")
38 } else {
39 ("○", "red")
40 };
41 println!(
42 "{} {:<20} {} {}",
43 "│".dimmed(),
44 "Status:".dimmed(),
45 ansi_color(color, symbol, false),
46 ansi_color(color, status, true)
47 );
48 }
49
50 pub fn print_summary(title: &str, total: &str) {
51 println!("\n{}", ansi_color("white", title, true));
52 println!(" {}", ansi_color("green", total, true));
53 }
54
55 pub fn print_project_entry(name: &str, duration: &str) {
56 println!(
57 " {:<25} {}",
58 ansi_color("clean_blue", &truncate_string(name, 25), true),
59 ansi_color("accent", duration, true)
60 );
61 }
62
63 pub fn print_context_entry(context: &str, duration: &str) {
64 let color = get_context_color(context);
65 println!(
66 " {:<20} {}",
67 ansi_color(color, &format!("├─ {}", context), false),
68 ansi_color("accent", duration, false)
69 );
70 }
71
72 pub fn print_session_entry(
73 _session_id: Option<i64>,
74 project: &str,
75 duration: &str,
76 status: &str,
77 timestamp: &str,
78 ) {
79 let status_symbol = if status == "active" { "●" } else { "○" };
80 let status_color = if status == "active" { "accent" } else { "gray" };
81
82 println!(
83 " {} {:<20} {:<15} {}",
84 ansi_color(status_color, status_symbol, false),
85 ansi_color("clean_blue", &truncate_string(project, 20), false),
86 ansi_color("accent", duration, false),
87 timestamp.dimmed()
88 );
89 }
90
91 pub fn print_empty_state(message: &str) {
92 println!("\n {}", message.dimmed());
93 }
94
95 pub fn print_error(message: &str) {
96 println!(
97 " {} {}",
98 ansi_color("red", "✗", true),
99 ansi_color("red", message, false)
100 );
101 }
102
103 pub fn print_success(message: &str) {
104 println!(
105 " {} {}",
106 ansi_color("accent", "✓", true),
107 ansi_color("accent", message, false)
108 );
109 }
110
111 pub fn print_warning(message: &str) {
112 println!(
113 " {} {}",
114 ansi_color("yellow", "⚠", true),
115 ansi_color("yellow", message, false)
116 );
117 }
118
119 pub fn print_info(message: &str) {
120 println!(" {} {}", ansi_color("clean_blue", "ℹ", true), message);
121 }
122
123 pub fn print_block_line(label: &str, value: &str) {
124 println!(
125 "{} {:<20} {}",
126 "│".dimmed(),
127 format!("{}:", label).dimmed(),
128 value
129 );
130 }
131
132 pub fn print_daemon_start(version: &str) {
133 println!(
134 "{}",
135 format!("Starting Tempo Daemon (v{})...", version).dimmed()
136 );
137 }
138
139 pub fn print_daemon_success(pid: u32, info: &str) {
140 let msg = format!("Daemon active. PID: {} [{}]", pid, info);
141 println!(
142 "{} {}",
143 ansi_color("accent", "✓", true),
144 ansi_color("accent", &msg, false)
145 );
146 }
147}
148
149pub fn ansi_color(color: &str, text: &str, bold: bool) -> String {
151 let color_code = match color {
152 "red" => "38;2;243;139;168", "green" => "38;2;166;227;161", "yellow" => "38;2;249;226;175", "blue" => "38;2;137;180;250", "magenta" => "38;2;203;166;247", "cyan" => "38;2;148;226;213", "white" => "38;2;217;224;238", "gray" => "38;2;108;112;134", "accent" => "38;2;51;255;153", "neon_cyan" => "38;2;0;255;255", "neon_green" => "38;2;57;255;20", "clean_blue" => "38;2;100;150;255", _ => "38;2;217;224;238", };
166
167 if bold {
168 format!("\x1b[1;{}m{}\x1b[0m", color_code, text)
169 } else {
170 format!("\x1b[{}m{}\x1b[0m", color_code, text)
171 }
172}
173
174fn bold(text: &str) -> String {
175 format!("\x1b[1m{}\x1b[0m", text)
176}
177
178pub trait StringFormat {
179 fn dimmed(&self) -> String;
180}
181
182impl StringFormat for str {
183 fn dimmed(&self) -> String {
184 format!("\x1b[2m{}\x1b[0m", self)
185 }
186}
187
188fn get_context_color(context: &str) -> &str {
189 match context {
190 "terminal" => "neon_cyan",
191 "ide" => "magenta",
192 "linked" => "yellow",
193 "manual" => "clean_blue",
194 _ => "white",
195 }
196}
197
198pub fn truncate_string(s: &str, max_len: usize) -> String {
199 if s.len() <= max_len {
200 s.to_string()
201 } else {
202 format!("{}…", &s[..max_len.saturating_sub(1)])
203 }
204}
205
206pub fn format_duration_clean(seconds: i64) -> String {
207 let hours = seconds / 3600;
208 let minutes = (seconds % 3600) / 60;
209 let secs = seconds % 60;
210
211 if hours > 0 {
212 format!("{}h {}m", hours, minutes)
213 } else if minutes > 0 {
214 format!("{}m {}s", minutes, secs)
215 } else {
216 format!("{}s", secs)
217 }
218}