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