use std::sync::LazyLock;
use regex::Regex;
use unicode_width::UnicodeWidthStr;
static ANSI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b\][^\x1b]*\x1b\\|\x1b.").unwrap()
});
pub fn clean_for_display(s: &str) -> String {
let after_cr = if let Some(last_cr_pos) = s.rfind('\r') {
&s[last_cr_pos + 1..]
} else {
s
};
let no_ansi = ANSI_REGEX.replace_all(after_cr, "");
no_ansi
.chars()
.filter(|c| !c.is_control() || *c == ' ')
.collect()
}
pub fn strip_ansi(s: &str) -> String {
ANSI_REGEX.replace_all(s, "").to_string()
}
pub fn truncate_to_width(s: &str, max_width: usize) -> String {
let clean = clean_for_display(s);
let width = clean.width();
if width <= max_width {
return clean;
}
let target_width = max_width.saturating_sub(3); let mut current_width = 0;
let mut end_idx = 0;
for (idx, ch) in clean.char_indices() {
let ch_width = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
if current_width + ch_width > target_width {
break;
}
current_width += ch_width;
end_idx = idx + ch.len_utf8();
}
format!("{}...", &clean[..end_idx])
}
pub fn pad_to_width(s: &str, target_width: usize) -> String {
let width = s.width();
if width >= target_width {
return s.to_string();
}
let padding = target_width - width;
format!("{}{}", s, " ".repeat(padding))
}
pub fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = 1024 * 1024;
const GB: u64 = 1024 * 1024 * 1024;
if bytes >= GB {
format!("{:.1} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
pub fn display_width(s: &str) -> usize {
s.width()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clean_for_display_carriage_return() {
assert_eq!(clean_for_display("abc\rdef"), "def");
assert_eq!(clean_for_display("no cr here"), "no cr here");
}
#[test]
fn test_clean_for_display_ansi() {
assert_eq!(clean_for_display("\x1b[31mred\x1b[0m"), "red");
assert_eq!(
clean_for_display("\x1b[1;32mbold green\x1b[0m"),
"bold green"
);
}
#[test]
fn test_strip_ansi() {
assert_eq!(strip_ansi("\x1b[31mred\x1b[0m text"), "red text");
}
#[test]
fn test_truncate_to_width() {
assert_eq!(truncate_to_width("short", 10), "short");
assert_eq!(truncate_to_width("this is a long string", 10), "this is...");
}
#[test]
fn test_pad_to_width() {
assert_eq!(pad_to_width("hi", 5), "hi ");
assert_eq!(pad_to_width("hello", 3), "hello");
}
#[test]
fn test_format_size() {
assert_eq!(format_size(0), "0 B");
assert_eq!(format_size(1023), "1023 B");
assert_eq!(format_size(1024), "1.0 KB");
assert_eq!(format_size(1048576), "1.0 MB");
assert_eq!(format_size(1073741824), "1.0 GB");
}
}