cascade_cli/cli/
output.rs

1use console::{style, Color, Emoji, Style};
2use std::fmt::Display;
3
4/// Theme configuration for Cascade CLI
5/// Matches the branding: black, gray, green palette
6struct Theme;
7
8impl Theme {
9    /// Bright/bold green for success messages (matches banner accent)
10    /// Using Color256(46) for a brighter, more vibrant green
11    fn success_style() -> Style {
12        Style::new().color256(46).bold() // Bright bold green
13    }
14
15    /// Red for errors
16    const ERROR: Color = Color::Red;
17
18    /// Yellow for warnings
19    const WARNING: Color = Color::Yellow;
20
21    /// Muted green (Color256) for info - complements success green
22    /// Using terminal color 35 (teal/green) for better readability
23    fn info_style() -> Style {
24        Style::new().color256(35) // Muted teal-green
25    }
26
27    /// Same muted green for tips
28    fn tip_style() -> Style {
29        Style::new().color256(35) // Muted teal-green
30    }
31
32    /// Dim gray for secondary text
33    fn dim_style() -> Style {
34        Style::new().dim()
35    }
36}
37
38/// Centralized output formatting utilities for consistent CLI presentation
39pub struct Output;
40
41impl Output {
42    /// Print a success message with checkmark (bright bold green)
43    pub fn success<T: Display>(message: T) {
44        println!("{} {}", Theme::success_style().apply_to("✓"), message);
45    }
46
47    /// Print an error message with X mark
48    pub fn error<T: Display>(message: T) {
49        println!("{} {}", style("✗").fg(Theme::ERROR), message);
50    }
51
52    /// Print a warning message with warning emoji
53    pub fn warning<T: Display>(message: T) {
54        println!("{} {}", style("⚠").fg(Theme::WARNING), message);
55    }
56
57    /// Print an info message with info emoji (muted green)
58    pub fn info<T: Display>(message: T) {
59        println!("{} {}", Theme::info_style().apply_to("ℹ"), message);
60    }
61
62    /// Print a sub-item with arrow prefix
63    pub fn sub_item<T: Display>(message: T) {
64        println!("  {} {}", Theme::dim_style().apply_to("→"), message);
65    }
66
67    /// Print a bullet point
68    pub fn bullet<T: Display>(message: T) {
69        println!("  {} {}", Theme::dim_style().apply_to("•"), message);
70    }
71
72    /// Print a section header
73    pub fn section<T: Display>(title: T) {
74        println!("\n{}", style(title).bold().underlined());
75    }
76
77    /// Print a tip/suggestion (muted green)
78    pub fn tip<T: Display>(message: T) {
79        println!(
80            "{} {}",
81            Theme::tip_style().apply_to("TIP:"),
82            Theme::dim_style().apply_to(message)
83        );
84    }
85
86    /// Print progress indicator (muted green)
87    pub fn progress<T: Display>(message: T) {
88        println!("{} {}", Theme::info_style().apply_to("→"), message);
89    }
90
91    /// Print a divider line
92    pub fn divider() {
93        println!("{}", Theme::dim_style().apply_to("─".repeat(50)));
94    }
95
96    /// Print stack information in a formatted way
97    pub fn stack_info(
98        name: &str,
99        id: &str,
100        base_branch: &str,
101        working_branch: Option<&str>,
102        is_active: bool,
103    ) {
104        // Show as info, not success (we're viewing, not creating)
105        println!(
106            "{} {}",
107            Theme::info_style().apply_to("Stack:"),
108            style(name).bold()
109        );
110        Self::sub_item(format!("Stack ID: {}", Theme::dim_style().apply_to(id)));
111        Self::sub_item(format!(
112            "Base branch: {}",
113            Theme::info_style().apply_to(base_branch)
114        ));
115
116        if let Some(working) = working_branch {
117            Self::sub_item(format!(
118                "Working branch: {}",
119                Theme::info_style().apply_to(working)
120            ));
121        }
122
123        if is_active {
124            Self::sub_item(format!(
125                "Status: {}",
126                Theme::success_style().apply_to("Active")
127            ));
128        }
129    }
130
131    /// Print next steps guidance
132    pub fn next_steps(steps: &[&str]) {
133        println!();
134        Self::tip("Next steps:");
135        for step in steps {
136            Self::bullet(step);
137        }
138    }
139
140    /// Print a command example
141    pub fn command_example<T: Display>(command: T) {
142        println!("  {}", style(command).fg(Theme::WARNING));
143    }
144
145    /// Print a check start message
146    pub fn check_start<T: Display>(message: T) {
147        println!("\n{} {}", style("🔍").bright(), style(message).bold());
148    }
149
150    /// Print a solution message
151    pub fn solution<T: Display>(message: T) {
152        println!("     {}: {}", style("Solution").fg(Theme::WARNING), message);
153    }
154
155    /// Print a numbered item (muted green)
156    pub fn numbered_item<T: Display>(number: usize, message: T) {
157        println!("  {}. {}", Theme::info_style().apply_to(number), message);
158    }
159
160    /// Print empty line for spacing
161    pub fn spacing() {
162        println!();
163    }
164
165    /// Format stack entry status with appropriate color
166    /// - pending: Yellow (work in progress)
167    /// - submitted: Muted green (PR open/under review)
168    /// - merged: Bright green (completed!)
169    pub fn entry_status(is_submitted: bool, is_merged: bool) -> String {
170        if is_merged {
171            format!("{}", Theme::success_style().apply_to("[merged]"))
172        } else if is_submitted {
173            format!("{}", Theme::info_style().apply_to("[submitted]"))
174        } else {
175            format!("{}", style("[pending]").fg(Theme::WARNING))
176        }
177    }
178}
179
180/// Emojis for different contexts
181pub struct Emojis;
182
183impl Emojis {
184    pub const SUCCESS: Emoji<'_, '_> = Emoji("✓", "OK");
185    pub const ERROR: Emoji<'_, '_> = Emoji("✗", "ERROR");
186    pub const WARNING: Emoji<'_, '_> = Emoji("⚠", "WARNING");
187    pub const INFO: Emoji<'_, '_> = Emoji("ℹ", "INFO");
188    pub const TIP: Emoji<'_, '_> = Emoji("💡", "TIP");
189    pub const ROCKET: Emoji<'_, '_> = Emoji("🚀", "ROCKET");
190    pub const SEARCH: Emoji<'_, '_> = Emoji("🔍", "SEARCH");
191    pub const UPLOAD: Emoji<'_, '_> = Emoji("📤", "UPLOAD");
192    pub const STACK: Emoji<'_, '_> = Emoji("📊", "STACK");
193}