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 tracing::Level;
7use tracing_subscriber::fmt::{FormatFields, format::Writer};
8use unicode_width::UnicodeWidthStr;
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!("{} {{}} {}", box_chars::VERTICAL, box_chars::VERTICAL);
176
177    let bottom = format!(
178        "{}{}{}",
179        box_chars::BOTTOM_LEFT,
180        box_chars::HORIZONTAL.repeat(width - 2),
181        box_chars::BOTTOM_RIGHT
182    );
183
184    (top, middle, bottom)
185}
186
187/// Print the startup banner
188pub fn print_banner(version: &str) {
189    let title = format!("ant-quic v{version}");
190    let (top, middle, bottom) = draw_box(&title, 60);
191
192    println!("{top}");
193    println!(
194        "{}",
195        middle.replace(
196            "{}",
197            "Starting QUIC P2P with NAT Traversal                 "
198        )
199    );
200    println!("{bottom}");
201    println!();
202}
203
204/// Print a section header
205pub fn print_section(icon: &str, title: &str) {
206    println!("{} {}{}{}", icon, colors::BOLD, title, colors::RESET);
207}
208
209/// Print an item with bullet point
210pub fn print_item(text: &str, indent: usize) {
211    let indent_str = " ".repeat(indent);
212    println!("{}{} {}", indent_str, symbols::DOT, text);
213}
214
215/// Print a status line with icon
216pub fn print_status(icon: &str, text: &str, color: &str) {
217    println!("  {} {}{}{}", icon, color, text, colors::RESET);
218}
219
220/// Format bytes into human-readable size
221pub fn format_bytes(bytes: u64) -> String {
222    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
223    let mut size = bytes as f64;
224    let mut unit_index = 0;
225
226    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
227        size /= 1024.0;
228        unit_index += 1;
229    }
230
231    if unit_index == 0 {
232        format!("{} {}", size as u64, UNITS[unit_index])
233    } else {
234        format!("{:.1} {}", size, UNITS[unit_index])
235    }
236}
237
238/// Format duration into human-readable time
239pub fn format_duration(duration: std::time::Duration) -> String {
240    let total_seconds = duration.as_secs();
241    let hours = total_seconds / 3600;
242    let minutes = (total_seconds % 3600) / 60;
243    let seconds = total_seconds % 60;
244
245    format!("{hours:02}:{minutes:02}:{seconds:02}")
246}
247
248/// Format timestamp into HH:MM:SS format
249pub fn format_timestamp(_timestamp: std::time::Instant) -> String {
250    use std::time::SystemTime;
251
252    // This is a simplified timestamp - in a real app you'd want proper time handling
253    let now = SystemTime::now();
254    let duration_since_epoch = now
255        .duration_since(SystemTime::UNIX_EPOCH)
256        .unwrap_or(std::time::Duration::ZERO);
257
258    let total_seconds = duration_since_epoch.as_secs();
259    let hours = (total_seconds % 86400) / 3600;
260    let minutes = (total_seconds % 3600) / 60;
261    let seconds = total_seconds % 60;
262
263    format!("{hours:02}:{minutes:02}:{seconds:02}")
264}
265
266/// Custom log formatter that adds colors and symbols
267pub struct ColoredLogFormatter;
268
269impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for ColoredLogFormatter
270where
271    S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
272    N: for<'a> FormatFields<'a> + 'static,
273{
274    fn format_event(
275        &self,
276        ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
277        mut writer: Writer<'_>,
278        event: &tracing::Event<'_>,
279    ) -> std::fmt::Result {
280        let metadata = event.metadata();
281        let level = metadata.level();
282
283        // Choose color and symbol based on level
284        let (color, symbol) = match *level {
285            Level::ERROR => (colors::RED, symbols::CROSS),
286            Level::WARN => (colors::YELLOW, symbols::WARNING),
287            Level::INFO => (colors::GREEN, symbols::CHECK),
288            Level::DEBUG => (colors::BLUE, symbols::INFO),
289            Level::TRACE => (colors::DIM, symbols::DOT),
290        };
291
292        // Write colored output
293        write!(&mut writer, "{color}{symbol} ")?;
294
295        // Write the message
296        ctx.field_format().format_fields(writer.by_ref(), event)?;
297
298        write!(&mut writer, "{}", colors::RESET)?;
299
300        writeln!(writer)
301    }
302}
303
304/// Progress indicator for operations
305pub struct ProgressIndicator {
306    message: String,
307    frames: Vec<&'static str>,
308    current_frame: usize,
309}
310
311impl ProgressIndicator {
312    pub fn new(message: String) -> Self {
313        Self {
314            message,
315            frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
316            current_frame: 0,
317        }
318    }
319
320    pub fn tick(&mut self) {
321        print!(
322            "\r{} {} {} ",
323            self.frames[self.current_frame],
324            colors::BLUE,
325            self.message
326        );
327        self.current_frame = (self.current_frame + 1) % self.frames.len();
328        use std::io::{self, Write};
329        io::stdout().flush().unwrap();
330    }
331
332    pub fn finish_success(&self, message: &str) {
333        println!(
334            "\r{} {}{}{} {}",
335            symbols::CHECK,
336            colors::GREEN,
337            self.message,
338            colors::RESET,
339            message
340        );
341    }
342
343    pub fn finish_error(&self, message: &str) {
344        println!(
345            "\r{} {}{}{} {}",
346            symbols::CROSS,
347            colors::RED,
348            self.message,
349            colors::RESET,
350            message
351        );
352    }
353}