syncable_cli/agent/ui/
colors.rs

1//! Color theme and styling utilities for terminal UI
2//!
3//! Provides semantic colors and ANSI escape codes for consistent styling.
4
5use colored::Colorize;
6
7/// Status icons for different states
8pub mod icons {
9    pub const PENDING: &str = "○";
10    pub const EXECUTING: &str = "◐";
11    pub const SUCCESS: &str = "✓";
12    pub const ERROR: &str = "✗";
13    pub const WARNING: &str = "⚠";
14    pub const CANCELED: &str = "⊘";
15    pub const CONFIRMING: &str = "⏳";
16    pub const ARROW: &str = "→";
17    pub const THINKING: &str = "💭";
18    pub const ROBOT: &str = "🤖";
19    pub const TOOL: &str = "🔧";
20    pub const SHELL: &str = "🐚";
21    pub const EDIT: &str = "✏️";
22    pub const FILE: &str = "📄";
23    pub const FOLDER: &str = "📁";
24    pub const SECURITY: &str = "🔒";
25    pub const SEARCH: &str = "🔍";
26    pub const DOCKER: &str = "🐳";
27    pub const LINT: &str = "📋";
28    pub const FIX: &str = "🔧";
29    pub const CRITICAL: &str = "🔴";
30    pub const HIGH: &str = "🟠";
31    pub const MEDIUM: &str = "🟡";
32    pub const LOW: &str = "🟢";
33    pub const KUBERNETES: &str = "☸";
34    pub const HELM: &str = "⎈";
35}
36
37/// ANSI escape codes for direct terminal control
38pub mod ansi {
39    /// Clear current line
40    pub const CLEAR_LINE: &str = "\x1b[2K\r";
41    /// Move cursor up one line
42    pub const CURSOR_UP: &str = "\x1b[1A";
43    /// Hide cursor
44    pub const HIDE_CURSOR: &str = "\x1b[?25l";
45    /// Show cursor
46    pub const SHOW_CURSOR: &str = "\x1b[?25h";
47    /// Reset all styles
48    pub const RESET: &str = "\x1b[0m";
49    /// Bold
50    pub const BOLD: &str = "\x1b[1m";
51    /// Dim (use sparingly - varies across terminals)
52    pub const DIM: &str = "\x1b[2m";
53    /// Italic
54    pub const ITALIC: &str = "\x1b[3m";
55
56    // Theme-safe standard ANSI colors (16-color, adapts to terminal theme)
57    // These are mapped by the terminal to theme-appropriate colors
58    /// Bright/bold default foreground - works on light AND dark terminals
59    pub const BRIGHT: &str = "\x1b[1m";
60    /// Standard dim that's still readable (uses italic instead of dim for better visibility)
61    pub const SUBDUED: &str = "\x1b[2;3m"; // Dim + italic for visual distinction without invisibility
62    /// Standard cyan (adapts to terminal theme)
63    pub const STD_CYAN: &str = "\x1b[36m";
64    /// Standard yellow (adapts to terminal theme)
65    pub const STD_YELLOW: &str = "\x1b[33m";
66    /// Standard green (adapts to terminal theme)
67    pub const STD_GREEN: &str = "\x1b[32m";
68    /// Standard red (adapts to terminal theme)
69    pub const STD_RED: &str = "\x1b[31m";
70    /// Standard blue (adapts to terminal theme)
71    pub const STD_BLUE: &str = "\x1b[34m";
72    /// Standard magenta (adapts to terminal theme)
73    pub const STD_MAGENTA: &str = "\x1b[35m";
74
75    // 256-color codes for Syncable brand
76    pub const PURPLE: &str = "\x1b[38;5;141m";
77    pub const ORANGE: &str = "\x1b[38;5;216m";
78    pub const PINK: &str = "\x1b[38;5;212m";
79    pub const MAGENTA: &str = "\x1b[38;5;207m";
80    pub const CYAN: &str = "\x1b[38;5;51m";
81    pub const GRAY: &str = "\x1b[38;5;245m";
82    pub const WHITE: &str = "\x1b[38;5;255m";
83    pub const SUCCESS: &str = "\x1b[38;5;114m"; // Green for success
84
85    // Hadolint/Docker specific colors (teal/docker-blue theme)
86    pub const DOCKER_BLUE: &str = "\x1b[38;5;39m"; // Docker brand blue
87    pub const TEAL: &str = "\x1b[38;5;30m"; // Teal for hadolint
88    pub const CRITICAL: &str = "\x1b[38;5;196m"; // Bright red
89    pub const HIGH: &str = "\x1b[38;5;208m"; // Orange
90    pub const MEDIUM: &str = "\x1b[38;5;220m"; // Yellow
91    pub const LOW: &str = "\x1b[38;5;114m"; // Green
92    pub const INFO_BLUE: &str = "\x1b[38;5;75m"; // Light blue for info
93}
94
95/// Format a tool name for display
96pub fn format_tool_name(name: &str) -> String {
97    name.cyan().bold().to_string()
98}
99
100/// Format a status message based on success/failure
101pub fn format_status(success: bool, message: &str) -> String {
102    if success {
103        format!("{} {}", icons::SUCCESS.green(), message.green())
104    } else {
105        format!("{} {}", icons::ERROR.red(), message.red())
106    }
107}
108
109/// Format elapsed time for display
110pub fn format_elapsed(seconds: u64) -> String {
111    if seconds < 60 {
112        format!("{}s", seconds)
113    } else {
114        let mins = seconds / 60;
115        let secs = seconds % 60;
116        format!("{}m {}s", mins, secs)
117    }
118}
119
120/// Format a thinking/reasoning message
121pub fn format_thinking(subject: &str) -> String {
122    format!("{} {}", icons::THINKING, subject.cyan().italic())
123}
124
125/// Format an info message
126pub fn format_info(message: &str) -> String {
127    format!("{} {}", icons::ARROW.cyan(), message)
128}
129
130/// Format a warning message
131pub fn format_warning(message: &str) -> String {
132    format!("⚠ {}", message.yellow())
133}
134
135/// Format an error message
136pub fn format_error(message: &str) -> String {
137    format!("{} {}", icons::ERROR.red(), message.red())
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_format_elapsed() {
146        assert_eq!(format_elapsed(5), "5s");
147        assert_eq!(format_elapsed(30), "30s");
148        assert_eq!(format_elapsed(65), "1m 5s");
149        assert_eq!(format_elapsed(125), "2m 5s");
150    }
151}