use std::borrow::Cow;
pub use crate::text_util::{char_cell_width, display_width};
pub fn truncate_to_width(s: &str, max_cols: usize) -> &str {
let mut used = 0usize;
for (idx, c) in s.char_indices() {
let w = char_cell_width(c) as usize;
if used + w > max_cols {
return &s[..idx];
}
used += w;
}
s
}
pub fn truncate_to_width_ellipsis(s: &str, max_cols: usize) -> Cow<'_, str> {
if display_width(s) <= max_cols {
return Cow::Borrowed(s);
}
if max_cols == 0 {
return Cow::Borrowed("");
}
let body = truncate_to_width(s, max_cols - 1);
let mut out = String::with_capacity(body.len() + '…'.len_utf8());
out.push_str(body);
out.push('…');
Cow::Owned(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn char_cell_width_ascii_is_one() {
assert_eq!(char_cell_width('a'), 1);
assert_eq!(char_cell_width(' '), 1);
}
#[test]
fn char_cell_width_cjk_is_two() {
assert_eq!(char_cell_width('日'), 2);
assert_eq!(char_cell_width('中'), 2);
}
#[test]
fn char_cell_width_pua_glyphs_are_width_one_and_consistent_across_blocks() {
let bmp_pua = char_cell_width('\u{F09B}');
let spua_a = char_cell_width('\u{F0000}');
assert_eq!(bmp_pua, 1);
assert_eq!(spua_a, 1);
assert_eq!(bmp_pua, spua_a);
}
#[test]
fn char_cell_width_combining_mark_is_zero() {
assert_eq!(char_cell_width('\u{0301}'), 0);
}
#[test]
fn display_width_sums_mixed_ascii_and_cjk() {
assert_eq!(display_width("ab日本"), 2 + 2 + 2);
assert_eq!(display_width(""), 0);
assert_eq!(display_width("hello"), 5);
}
#[test]
fn truncate_to_width_returns_whole_string_when_it_fits() {
assert_eq!(truncate_to_width("hi", 10), "hi");
assert_eq!(truncate_to_width("hi", 2), "hi");
}
#[test]
fn truncate_to_width_cuts_on_char_boundary_never_panics() {
assert_eq!(truncate_to_width("日本語", 1), "");
assert_eq!(truncate_to_width("日本語", 2), "日");
assert_eq!(truncate_to_width("日本語", 3), "日");
assert_eq!(truncate_to_width("日本語", 4), "日本");
}
#[test]
fn truncate_to_width_never_splits_a_wide_glyph() {
let out = truncate_to_width("a日", 2);
assert_eq!(out, "a");
assert!(display_width(out) <= 2);
}
#[test]
fn truncate_to_width_zero_budget_is_empty() {
assert_eq!(truncate_to_width("hello", 0), "");
}
#[test]
fn truncate_to_width_ellipsis_leaves_short_strings_untouched() {
let out = truncate_to_width_ellipsis("hi", 10);
assert_eq!(out, "hi");
assert!(matches!(out, Cow::Borrowed(_)));
}
#[test]
fn truncate_to_width_ellipsis_clips_and_marks_long_strings() {
let out = truncate_to_width_ellipsis("hello world", 6);
assert_eq!(out, "hello…");
assert_eq!(display_width(&out), 6);
assert!(matches!(out, Cow::Owned(_)));
}
#[test]
fn truncate_to_width_ellipsis_respects_wide_glyphs() {
let out = truncate_to_width_ellipsis("日本語", 3);
assert_eq!(out, "日…");
assert!(display_width(&out) <= 3);
}
#[test]
fn truncate_to_width_ellipsis_zero_budget_is_empty() {
assert_eq!(truncate_to_width_ellipsis("hello", 0), "");
}
}