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 CANCELED: &str = "⊘";
14    pub const CONFIRMING: &str = "⏳";
15    pub const ARROW: &str = "→";
16    pub const THINKING: &str = "💭";
17    pub const ROBOT: &str = "🤖";
18    pub const TOOL: &str = "🔧";
19    pub const FILE: &str = "📄";
20    pub const FOLDER: &str = "📁";
21    pub const SECURITY: &str = "🔒";
22    pub const SEARCH: &str = "🔍";
23}
24
25/// ANSI escape codes for direct terminal control
26pub mod ansi {
27    /// Clear current line
28    pub const CLEAR_LINE: &str = "\x1b[2K\r";
29    /// Move cursor up one line
30    pub const CURSOR_UP: &str = "\x1b[1A";
31    /// Hide cursor
32    pub const HIDE_CURSOR: &str = "\x1b[?25l";
33    /// Show cursor
34    pub const SHOW_CURSOR: &str = "\x1b[?25h";
35    /// Reset all styles
36    pub const RESET: &str = "\x1b[0m";
37    /// Bold
38    pub const BOLD: &str = "\x1b[1m";
39    /// Dim
40    pub const DIM: &str = "\x1b[2m";
41
42    // 256-color codes for Syncable brand
43    pub const PURPLE: &str = "\x1b[38;5;141m";
44    pub const ORANGE: &str = "\x1b[38;5;216m";
45    pub const PINK: &str = "\x1b[38;5;212m";
46    pub const MAGENTA: &str = "\x1b[38;5;207m";
47    pub const CYAN: &str = "\x1b[38;5;51m";
48    pub const GRAY: &str = "\x1b[38;5;245m";
49    pub const SUCCESS: &str = "\x1b[38;5;114m"; // Green for success
50}
51
52/// Format a tool name for display
53pub fn format_tool_name(name: &str) -> String {
54    name.cyan().bold().to_string()
55}
56
57/// Format a status message based on success/failure
58pub fn format_status(success: bool, message: &str) -> String {
59    if success {
60        format!("{} {}", icons::SUCCESS.green(), message.green())
61    } else {
62        format!("{} {}", icons::ERROR.red(), message.red())
63    }
64}
65
66/// Format elapsed time for display
67pub fn format_elapsed(seconds: u64) -> String {
68    if seconds < 60 {
69        format!("{}s", seconds)
70    } else {
71        let mins = seconds / 60;
72        let secs = seconds % 60;
73        format!("{}m {}s", mins, secs)
74    }
75}
76
77/// Format a thinking/reasoning message
78pub fn format_thinking(subject: &str) -> String {
79    format!(
80        "{} {}",
81        icons::THINKING,
82        subject.cyan().italic()
83    )
84}
85
86/// Format an info message
87pub fn format_info(message: &str) -> String {
88    format!("{} {}", icons::ARROW.cyan(), message)
89}
90
91/// Format a warning message
92pub fn format_warning(message: &str) -> String {
93    format!("⚠ {}", message.yellow())
94}
95
96/// Format an error message
97pub fn format_error(message: &str) -> String {
98    format!("{} {}", icons::ERROR.red(), message.red())
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_format_elapsed() {
107        assert_eq!(format_elapsed(5), "5s");
108        assert_eq!(format_elapsed(30), "30s");
109        assert_eq!(format_elapsed(65), "1m 5s");
110        assert_eq!(format_elapsed(125), "2m 5s");
111    }
112}