#[must_use]
pub fn floor_char_boundary(s: &str, index: usize) -> usize {
if index >= s.len() {
return s.len();
}
let mut boundary = index;
while boundary > 0 && !s.is_char_boundary(boundary) {
boundary -= 1;
}
boundary
}
pub fn truncate_with_ellipsis(s: &str, max_chars: usize) -> String {
let char_count = s.chars().count();
if char_count <= max_chars {
s.to_string()
} else if max_chars < 3 {
s.chars().take(max_chars).collect()
} else {
let keep = max_chars - 3;
let byte_idx = s
.char_indices()
.nth(keep)
.map(|(idx, _)| idx)
.unwrap_or(s.len());
format!("{}...", &s[..byte_idx])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_floor_char_boundary_ascii() {
let s = "hello";
assert_eq!(floor_char_boundary(s, 0), 0);
assert_eq!(floor_char_boundary(s, 3), 3);
assert_eq!(floor_char_boundary(s, 5), 5);
assert_eq!(floor_char_boundary(s, 10), 5); }
#[test]
fn test_floor_char_boundary_emoji() {
let s = "hiπ¦ok"; assert_eq!(floor_char_boundary(s, 2), 2); assert_eq!(floor_char_boundary(s, 3), 2); assert_eq!(floor_char_boundary(s, 4), 2); assert_eq!(floor_char_boundary(s, 5), 2); assert_eq!(floor_char_boundary(s, 6), 6); }
#[test]
fn test_floor_char_boundary_checkmark() {
let s = "aβ
b"; assert_eq!(floor_char_boundary(s, 1), 1); assert_eq!(floor_char_boundary(s, 2), 1); assert_eq!(floor_char_boundary(s, 3), 1); assert_eq!(floor_char_boundary(s, 4), 4); }
#[test]
fn test_floor_char_boundary_empty() {
assert_eq!(floor_char_boundary("", 0), 0);
assert_eq!(floor_char_boundary("", 5), 0);
}
#[test]
fn test_short_string_unchanged() {
assert_eq!(truncate_with_ellipsis("short", 10), "short");
assert_eq!(truncate_with_ellipsis("", 5), "");
assert_eq!(truncate_with_ellipsis("exact", 5), "exact");
}
#[test]
fn test_long_string_truncated() {
assert_eq!(
truncate_with_ellipsis("this is a long string", 10),
"this is..."
);
assert_eq!(truncate_with_ellipsis("abcdef", 3), "...");
}
#[test]
fn test_utf8_boundaries_arrows() {
let arrows = "ββββββββ";
assert_eq!(truncate_with_ellipsis(arrows, 5), "ββ...");
}
#[test]
fn test_utf8_boundaries_mixed() {
let mixed = "aβbβcβd";
assert_eq!(truncate_with_ellipsis(mixed, 5), "aβ...");
}
#[test]
fn test_utf8_boundaries_emoji() {
let emoji = "ππππ";
assert_eq!(truncate_with_ellipsis(emoji, 3), "...");
}
#[test]
fn test_utf8_complex_emoji() {
let s = "hi π¦ there";
assert_eq!(truncate_with_ellipsis(s, 4), "h...");
}
#[test]
fn test_zero_max_chars() {
assert_eq!(truncate_with_ellipsis("hello", 0), "");
}
#[test]
fn test_single_char_truncation() {
assert_eq!(truncate_with_ellipsis("hello", 1), "h");
assert_eq!(truncate_with_ellipsis("πhello", 1), "π");
}
}