1use colored::Colorize;
7use indicatif::{ProgressBar, ProgressStyle};
8use parking_lot::Mutex;
9use std::fmt::Write;
10use std::time::Duration;
11
12use crate::theme;
13use crate::theme::adapters::cli::gradient_string;
14
15pub mod rgb {
22 use crate::theme;
23 use crate::theme::adapters::cli::ToColoredRgb;
24
25 pub fn accent_primary() -> (u8, u8, u8) {
27 theme::current().color("accent.primary").to_rgb()
28 }
29
30 pub fn accent_secondary() -> (u8, u8, u8) {
32 theme::current().color("accent.secondary").to_rgb()
33 }
34
35 pub fn accent_tertiary() -> (u8, u8, u8) {
37 theme::current().color("accent.tertiary").to_rgb()
38 }
39
40 pub fn warning() -> (u8, u8, u8) {
42 theme::current().color("warning").to_rgb()
43 }
44
45 pub fn success() -> (u8, u8, u8) {
47 theme::current().color("success").to_rgb()
48 }
49
50 pub fn error() -> (u8, u8, u8) {
52 theme::current().color("error").to_rgb()
53 }
54
55 pub fn text_primary() -> (u8, u8, u8) {
57 theme::current().color("text.primary").to_rgb()
58 }
59
60 pub fn text_secondary() -> (u8, u8, u8) {
62 theme::current().color("text.secondary").to_rgb()
63 }
64
65 pub fn text_muted() -> (u8, u8, u8) {
67 theme::current().color("text.muted").to_rgb()
68 }
69
70 pub fn text_dim() -> (u8, u8, u8) {
72 theme::current().color("text.dim").to_rgb()
73 }
74}
75
76static QUIET_MODE: std::sync::LazyLock<Mutex<bool>> =
78 std::sync::LazyLock::new(|| Mutex::new(false));
79
80pub fn set_quiet_mode(enabled: bool) {
82 let mut quiet_mode = QUIET_MODE.lock();
83 *quiet_mode = enabled;
84}
85
86pub fn is_quiet_mode() -> bool {
88 *QUIET_MODE.lock()
89}
90
91pub fn create_spinner(message: &str) -> ProgressBar {
92 if is_quiet_mode() {
94 return ProgressBar::hidden();
95 }
96
97 let pb = ProgressBar::new_spinner();
98
99 if crate::agents::status::is_agent_mode_enabled() {
101 pb.set_style(
102 ProgressStyle::default_spinner()
103 .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
104 .template("{spinner:.bright_cyan.bold} {msg}")
105 .expect("Could not set spinner style"),
106 );
107
108 pb.set_message("◎ Iris initializing...");
110
111 let pb_clone = pb.clone();
113 tokio::spawn(async move {
114 let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(200));
115 loop {
116 interval.tick().await;
117 let status_message = crate::agents::status::IRIS_STATUS.get_for_spinner();
118 pb_clone.set_message(status_message.text);
119 }
120 });
121
122 pb.enable_steady_tick(Duration::from_millis(100));
123 } else {
124 pb.set_style(
125 ProgressStyle::default_spinner()
126 .tick_chars("✦✧✶✷✸✹✺✻✼✽")
127 .template("{spinner} {msg}")
128 .expect("Could not set spinner style"),
129 );
130 pb.set_message(message.to_string());
131 pb.enable_steady_tick(Duration::from_millis(100));
132 }
133
134 pb
135}
136
137pub fn print_info(message: &str) {
139 if !is_quiet_mode() {
140 let color = theme::current().color("info");
141 println!("{}", message.truecolor(color.r, color.g, color.b).bold());
142 }
143}
144
145pub fn print_warning(message: &str) {
147 if !is_quiet_mode() {
148 let color = theme::current().color("warning");
149 println!("{}", message.truecolor(color.r, color.g, color.b).bold());
150 }
151}
152
153pub fn print_error(message: &str) {
155 let color = theme::current().color("error");
157 eprintln!("{}", message.truecolor(color.r, color.g, color.b).bold());
158}
159
160pub fn print_success(message: &str) {
162 if !is_quiet_mode() {
163 let color = theme::current().color("success");
164 println!("{}", message.truecolor(color.r, color.g, color.b).bold());
165 }
166}
167
168pub fn print_version(version: &str) {
169 if !is_quiet_mode() {
170 let t = theme::current();
171 let purple = t.color("accent.primary");
172 let cyan = t.color("accent.secondary");
173 let green = t.color("success");
174
175 println!(
176 "{} {} {}",
177 "🔮 Git-Iris".truecolor(purple.r, purple.g, purple.b).bold(),
178 "version".truecolor(cyan.r, cyan.g, cyan.b),
179 version.truecolor(green.r, green.g, green.b)
180 );
181 }
182}
183
184pub fn print_bordered_content(content: &str) {
186 if !is_quiet_mode() {
187 let color = theme::current().color("accent.primary");
188 println!("{}", "━".repeat(50).truecolor(color.r, color.g, color.b));
189 println!("{content}");
190 println!("{}", "━".repeat(50).truecolor(color.r, color.g, color.b));
191 }
192}
193
194pub fn print_message(message: &str) {
196 if !is_quiet_mode() {
197 println!("{message}");
198 }
199}
200
201pub fn print_newline() {
203 if !is_quiet_mode() {
204 println!();
205 }
206}
207
208pub fn create_gradient_text(text: &str) -> String {
210 if let Some(gradient) = theme::current().get_gradient("primary") {
211 gradient_string(text, gradient)
212 } else {
213 let gradient = vec![
215 (225, 53, 255), (200, 100, 255), (180, 150, 250), (150, 200, 245), (128, 255, 234), ];
221 apply_gradient(text, &gradient)
222 }
223}
224
225pub fn create_secondary_gradient_text(text: &str) -> String {
227 if let Some(gradient) = theme::current().get_gradient("warm") {
228 gradient_string(text, gradient)
229 } else {
230 let gradient = vec![
232 (255, 106, 193), (255, 150, 180), (255, 200, 160), (248, 230, 140), (241, 250, 140), ];
238 apply_gradient(text, &gradient)
239 }
240}
241
242fn apply_gradient(text: &str, gradient: &[(u8, u8, u8)]) -> String {
243 let chars: Vec<char> = text.chars().collect();
244 let chars_len = chars.len();
245 let gradient_len = gradient.len();
246
247 let mut result = String::new();
248
249 if chars_len == 0 || gradient_len == 0 {
250 return result;
251 }
252
253 chars.iter().enumerate().fold(&mut result, |acc, (i, &c)| {
254 let index = if chars_len == 1 {
255 0
256 } else {
257 i * (gradient_len - 1) / (chars_len - 1)
258 };
259 let (r, g, b) = gradient[index];
260 write!(acc, "{}", c.to_string().truecolor(r, g, b)).expect("writing to string cannot fail");
261 acc
262 });
263
264 result
265}