ant_quic/
terminal_ui.rs

1//! Terminal UI formatting and display helpers for ant-quic
2//!
3//! Provides colored output, formatting, and visual elements for better UX
4
5use std::net::{IpAddr, SocketAddr};
6use unicode_width::UnicodeWidthStr;
7use tracing::Level;
8use tracing_subscriber::fmt::{format::Writer, FormatFields};
9// use four_word_networking::FourWordAdaptiveEncoder; // TODO: Add this dependency or implement locally
10
11/// ANSI color codes for terminal output
12pub mod colors {
13    pub const RESET: &str = "\x1b[0m";
14    pub const BOLD: &str = "\x1b[1m";
15    pub const DIM: &str = "\x1b[2m";
16    
17    // Regular colors
18    pub const BLACK: &str = "\x1b[30m";
19    pub const RED: &str = "\x1b[31m";
20    pub const GREEN: &str = "\x1b[32m";
21    pub const YELLOW: &str = "\x1b[33m";
22    pub const BLUE: &str = "\x1b[34m";
23    pub const MAGENTA: &str = "\x1b[35m";
24    pub const CYAN: &str = "\x1b[36m";
25    pub const WHITE: &str = "\x1b[37m";
26    
27    // Bright colors
28    pub const BRIGHT_BLACK: &str = "\x1b[90m";
29    pub const BRIGHT_RED: &str = "\x1b[91m";
30    pub const BRIGHT_GREEN: &str = "\x1b[92m";
31    pub const BRIGHT_YELLOW: &str = "\x1b[93m";
32    pub const BRIGHT_BLUE: &str = "\x1b[94m";
33    pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
34    pub const BRIGHT_CYAN: &str = "\x1b[96m";
35    pub const BRIGHT_WHITE: &str = "\x1b[97m";
36}
37
38/// Unicode symbols for visual indicators
39pub mod symbols {
40    pub const CHECK: &str = "✓";
41    pub const CROSS: &str = "✗";
42    pub const INFO: &str = "ℹ";
43    pub const WARNING: &str = "⚠";
44    pub const ARROW_RIGHT: &str = "→";
45    pub const DOT: &str = "•";
46    pub const KEY: &str = "🔑";
47    pub const NETWORK: &str = "📡";
48    pub const GLOBE: &str = "🌐";
49    pub const ROCKET: &str = "🚀";
50    pub const HOURGLASS: &str = "⏳";
51    pub const CIRCULAR_ARROWS: &str = "⟳";
52}
53
54/// Box drawing characters for borders
55pub mod box_chars {
56    pub const TOP_LEFT: &str = "╭";
57    pub const TOP_RIGHT: &str = "╮";
58    pub const BOTTOM_LEFT: &str = "╰";
59    pub const BOTTOM_RIGHT: &str = "╯";
60    pub const HORIZONTAL: &str = "─";
61    pub const VERTICAL: &str = "│";
62    pub const T_LEFT: &str = "├";
63    pub const T_RIGHT: &str = "┤";
64}
65
66/// Check if an IPv6 address is link-local (fe80::/10)
67fn is_ipv6_link_local(ip: &std::net::Ipv6Addr) -> bool {
68    let octets = ip.octets();
69    (octets[0] == 0xfe) && ((octets[1] & 0xc0) == 0x80)
70}
71
72/// Check if an IPv6 address is unique local (fc00::/7)
73fn is_ipv6_unique_local(ip: &std::net::Ipv6Addr) -> bool {
74    let octets = ip.octets();
75    (octets[0] & 0xfe) == 0xfc
76}
77
78/// Check if an IPv6 address is multicast (ff00::/8)
79fn is_ipv6_multicast(ip: &std::net::Ipv6Addr) -> bool {
80    let octets = ip.octets();
81    octets[0] == 0xff
82}
83
84/// Format a peer ID with color (shows first 8 chars)
85pub fn format_peer_id(peer_id: &[u8; 32]) -> String {
86    let hex = hex::encode(&peer_id[..4]);
87    format!("{}{}{}{}", colors::CYAN, hex, "...", colors::RESET)
88}
89
90/// Format an address with appropriate coloring
91pub fn format_address(addr: &SocketAddr) -> String {
92    let color = match addr.ip() {
93        IpAddr::V4(ip) => {
94            if ip.is_loopback() {
95                colors::DIM
96            } else if ip.is_private() {
97                colors::YELLOW
98            } else {
99                colors::GREEN
100            }
101        }
102        IpAddr::V6(ip) => {
103            if ip.is_loopback() {
104                colors::DIM
105            } else if ip.is_unspecified() {
106                colors::DIM
107            } else if is_ipv6_link_local(&ip) {
108                colors::YELLOW
109            } else if is_ipv6_unique_local(&ip) {
110                colors::CYAN
111            } else {
112                colors::BRIGHT_CYAN
113            }
114        }
115    };
116    
117    format!("{}{}{}", color, addr, colors::RESET)
118}
119
120/// Format an address as four words with original address in brackets
121pub fn format_address_with_words(addr: &SocketAddr) -> String {
122    // TODO: Implement four-word encoding or add dependency
123    // For now, just return the colored address
124    format_address(addr)
125}
126
127/// Categorize and describe an IP address
128pub fn describe_address(addr: &SocketAddr) -> &'static str {
129    match addr.ip() {
130        IpAddr::V4(ip) => {
131            if ip.is_loopback() {
132                "loopback"
133            } else if ip.is_private() {
134                "private network"
135            } else if ip.is_link_local() {
136                "link-local"
137            } else {
138                "public"
139            }
140        }
141        IpAddr::V6(ip) => {
142            if ip.is_loopback() {
143                "IPv6 loopback"
144            } else if ip.is_unspecified() {
145                "IPv6 unspecified"
146            } else if is_ipv6_link_local(&ip) {
147                "IPv6 link-local"
148            } else if is_ipv6_unique_local(&ip) {
149                "IPv6 unique local"
150            } else if is_ipv6_multicast(&ip) {
151                "IPv6 multicast"
152            } else {
153                "IPv6 global"
154            }
155        }
156    }
157}
158
159/// Draw a box with title and content
160pub fn draw_box(title: &str, width: usize) -> (String, String, String) {
161    let padding = width.saturating_sub(title.width() + 4);
162    let left_pad = padding / 2;
163    let right_pad = padding - left_pad;
164    
165    let top = format!(
166        "{}{} {} {}{}{}",
167        box_chars::TOP_LEFT,
168        box_chars::HORIZONTAL.repeat(left_pad),
169        title,
170        box_chars::HORIZONTAL.repeat(right_pad),
171        box_chars::HORIZONTAL,
172        box_chars::TOP_RIGHT
173    );
174    
175    let middle = format!(
176        "{} {{}} {}",
177        box_chars::VERTICAL,
178        box_chars::VERTICAL
179    );
180    
181    let bottom = format!(
182        "{}{}{}",
183        box_chars::BOTTOM_LEFT,
184        box_chars::HORIZONTAL.repeat(width - 2),
185        box_chars::BOTTOM_RIGHT
186    );
187    
188    (top, middle, bottom)
189}
190
191/// Print the startup banner
192pub fn print_banner(version: &str) {
193    let title = format!("ant-quic v{}", version);
194    let (top, middle, bottom) = draw_box(&title, 60);
195    
196    println!("{}", top);
197    println!("{}", middle.replace("{}", "Starting QUIC P2P with NAT Traversal                 "));
198    println!("{}", bottom);
199    println!();
200}
201
202/// Print a section header
203pub fn print_section(icon: &str, title: &str) {
204    println!("{} {}{}{}", icon, colors::BOLD, title, colors::RESET);
205}
206
207/// Print an item with bullet point
208pub fn print_item(text: &str, indent: usize) {
209    let indent_str = " ".repeat(indent);
210    println!("{}{} {}", indent_str, symbols::DOT, text);
211}
212
213/// Print a status line with icon
214pub fn print_status(icon: &str, text: &str, color: &str) {
215    println!("  {} {}{}{}", icon, color, text, colors::RESET);
216}
217
218/// Format bytes into human-readable size
219pub fn format_bytes(bytes: u64) -> String {
220    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
221    let mut size = bytes as f64;
222    let mut unit_index = 0;
223    
224    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
225        size /= 1024.0;
226        unit_index += 1;
227    }
228    
229    if unit_index == 0 {
230        format!("{} {}", size as u64, UNITS[unit_index])
231    } else {
232        format!("{:.1} {}", size, UNITS[unit_index])
233    }
234}
235
236/// Format duration into human-readable time
237pub fn format_duration(duration: std::time::Duration) -> String {
238    let total_seconds = duration.as_secs();
239    let hours = total_seconds / 3600;
240    let minutes = (total_seconds % 3600) / 60;
241    let seconds = total_seconds % 60;
242    
243    format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
244}
245
246/// Format timestamp into HH:MM:SS format
247pub fn format_timestamp(_timestamp: std::time::Instant) -> String {
248    use std::time::SystemTime;
249    
250    // This is a simplified timestamp - in a real app you'd want proper time handling
251    let now = SystemTime::now();
252    let duration_since_epoch = now.duration_since(SystemTime::UNIX_EPOCH)
253        .unwrap_or_else(|_| std::time::Duration::ZERO);
254    
255    let total_seconds = duration_since_epoch.as_secs();
256    let hours = (total_seconds % 86400) / 3600;
257    let minutes = (total_seconds % 3600) / 60;
258    let seconds = total_seconds % 60;
259    
260    format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
261}
262
263/// Custom log formatter that adds colors and symbols
264pub struct ColoredLogFormatter;
265
266impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for ColoredLogFormatter
267where
268    S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
269    N: for<'a> FormatFields<'a> + 'static,
270{
271    fn format_event(
272        &self,
273        ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
274        mut writer: Writer<'_>,
275        event: &tracing::Event<'_>,
276    ) -> std::fmt::Result {
277        let metadata = event.metadata();
278        let level = metadata.level();
279        
280        // Choose color and symbol based on level
281        let (color, symbol) = match *level {
282            Level::ERROR => (colors::RED, symbols::CROSS),
283            Level::WARN => (colors::YELLOW, symbols::WARNING),
284            Level::INFO => (colors::GREEN, symbols::CHECK),
285            Level::DEBUG => (colors::BLUE, symbols::INFO),
286            Level::TRACE => (colors::DIM, symbols::DOT),
287        };
288        
289        // Write colored output
290        write!(&mut writer, "{}{} ", color, symbol)?;
291        
292        // Write the message
293        ctx.field_format().format_fields(writer.by_ref(), event)?;
294        
295        write!(&mut writer, "{}", colors::RESET)?;
296        
297        writeln!(writer)
298    }
299}
300
301/// Progress indicator for operations
302pub struct ProgressIndicator {
303    message: String,
304    frames: Vec<&'static str>,
305    current_frame: usize,
306}
307
308impl ProgressIndicator {
309    pub fn new(message: String) -> Self {
310        Self {
311            message,
312            frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
313            current_frame: 0,
314        }
315    }
316    
317    pub fn tick(&mut self) {
318        print!("\r{} {} {} ", 
319            self.frames[self.current_frame], 
320            colors::BLUE, 
321            self.message
322        );
323        self.current_frame = (self.current_frame + 1) % self.frames.len();
324        use std::io::{self, Write};
325        io::stdout().flush().unwrap();
326    }
327    
328    pub fn finish_success(&self, message: &str) {
329        println!("\r{} {}{}{} {}", 
330            symbols::CHECK, 
331            colors::GREEN,
332            self.message,
333            colors::RESET,
334            message
335        );
336    }
337    
338    pub fn finish_error(&self, message: &str) {
339        println!("\r{} {}{}{} {}", 
340            symbols::CROSS, 
341            colors::RED,
342            self.message,
343            colors::RESET,
344            message
345        );
346    }
347}