use ratatui::text::Span;
pub fn width(s: &str) -> usize {
Span::from(s).width()
}
pub fn pad(s: &str, to: usize) -> String {
let w = width(s);
if w >= to {
s.to_string()
} else {
format!("{s}{}", " ".repeat(to - w))
}
}
pub fn trunc(s: &str, max: usize) -> String {
if width(s) <= max {
return s.to_string();
}
let budget = max.saturating_sub(1);
let mut out = String::new();
for ch in s.chars() {
let mut candidate = out.clone();
candidate.push(ch);
if width(&candidate) > budget {
break;
}
out = candidate;
}
out.push('…');
out
}
#[cfg(test)]
mod tests {
use super::{pad, trunc, width};
#[test]
fn width_counts_cells_not_chars() {
assert_eq!(width("abc"), 3);
assert_eq!(width("项目名称测"), 10);
}
#[test]
fn pad_grows_but_never_clips() {
assert_eq!(pad("ab", 4), "ab ");
assert_eq!(pad("abcd", 2), "abcd", "pad must not clip; that is trunc's job");
assert_eq!(pad("项", 4), "项 ");
}
#[test]
fn trunc_leaves_short_strings_alone() {
assert_eq!(trunc("abc", 5), "abc");
assert_eq!(trunc("abc", 3), "abc");
}
#[test]
fn trunc_never_splits_a_wide_glyph() {
let out = trunc("项目名称", 5);
assert_eq!(out, "项目…");
assert!(width(&out) <= 5, "{out:?} overflowed its budget");
}
#[test]
fn trunc_marks_every_cut_with_exactly_one_ellipsis() {
let out = trunc("abcdefgh", 4);
assert_eq!(out, "abc…");
assert_eq!(out.matches('…').count(), 1);
}
}