use std::borrow::Cow;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
pub fn cell_len(text: &str) -> usize {
text.width()
}
pub fn get_character_cell_size(c: char) -> usize {
c.width().unwrap_or(0)
}
pub fn set_cell_size(text: &str, total: usize) -> Cow<'_, str> {
let current_len = cell_len(text);
if current_len == total {
return Cow::Borrowed(text);
}
if current_len < total {
let mut result = String::with_capacity(text.len() + (total - current_len));
result.push_str(text);
result.push_str(&" ".repeat(total - current_len));
return Cow::Owned(result);
}
if total == 0 {
return Cow::Borrowed("");
}
let mut result = String::with_capacity(text.len());
let mut cell_position = 0;
for c in text.chars() {
let char_width = get_character_cell_size(c);
if cell_position + char_width <= total {
result.push(c);
cell_position += char_width;
} else if cell_position < total {
result.push_str(&" ".repeat(total - cell_position));
break;
} else {
break;
}
}
Cow::Owned(result)
}
pub fn chop_cells(text: &str, width: usize) -> Vec<String> {
if width == 0 {
return vec![];
}
let mut lines = Vec::new();
let mut current_line = String::new();
let mut current_width = 0;
for c in text.chars() {
let char_width = get_character_cell_size(c);
if current_width + char_width <= width {
current_line.push(c);
current_width += char_width;
} else {
if !current_line.is_empty() {
lines.push(current_line);
current_line = String::new();
}
current_line.push(c);
current_width = char_width;
}
}
if !current_line.is_empty() {
lines.push(current_line);
}
lines
}
pub fn is_single_cell_widths(text: &str) -> bool {
text.chars().all(|c| get_character_cell_size(c) == 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_character_cell_size() {
assert_eq!(get_character_cell_size('\0'), 0);
let x01_width = get_character_cell_size('\x01');
let x1f_width = get_character_cell_size('\x1f');
assert!(
x01_width <= 1,
"\\x01 width should be 0 or 1, got {}",
x01_width
);
assert!(
x1f_width <= 1,
"\\x1f width should be 0 or 1, got {}",
x1f_width
);
assert_eq!(get_character_cell_size('a'), 1);
assert_eq!(get_character_cell_size('A'), 1);
assert_eq!(get_character_cell_size('0'), 1);
assert_eq!(get_character_cell_size(' '), 1);
assert_eq!(get_character_cell_size('💩'), 2);
assert_eq!(get_character_cell_size('😽'), 2);
assert_eq!(get_character_cell_size('あ'), 2);
assert_eq!(get_character_cell_size('わ'), 2);
assert_eq!(get_character_cell_size('さ'), 2);
assert_eq!(get_character_cell_size('び'), 2);
}
#[test]
fn test_cell_len() {
assert_eq!(cell_len(""), 0);
assert_eq!(cell_len("abc"), 3);
assert_eq!(cell_len("hello world"), 11);
assert_eq!(cell_len("💩"), 2);
assert_eq!(cell_len("😽😽"), 4);
assert_eq!(cell_len("わさび"), 6); assert_eq!(cell_len("あ"), 2);
assert_eq!(cell_len("ありがとう"), 10);
assert_eq!(cell_len("aあb"), 4);
let x01_len = cell_len("\x01");
assert!(x01_len <= 1, "Expected \\x01 width 0 or 1, got {}", x01_len);
let x1f_len = cell_len("\x1f");
assert!(x1f_len <= 1, "Expected \\x1f width 0 or 1, got {}", x1f_len);
let a_x01_b_len = cell_len("a\x01b");
assert!(
a_x01_b_len >= 2 && a_x01_b_len <= 3,
"Expected a\\x01b width 2-3, got {}",
a_x01_b_len
);
assert_eq!(cell_len("┌─┬┐"), 4);
assert_eq!(cell_len("│ ││"), 4);
}
#[test]
fn test_set_cell_size_exact_match() {
assert_eq!(set_cell_size("foo", 3), "foo");
assert_eq!(set_cell_size("😽😽", 4), "😽😽");
}
#[test]
fn test_set_cell_size_padding() {
assert_eq!(set_cell_size("foo", 4), "foo ");
assert_eq!(set_cell_size("foo", 5), "foo ");
assert_eq!(set_cell_size("😽😽", 5), "😽😽 ");
assert_eq!(set_cell_size("a", 10), "a ");
}
#[test]
fn test_set_cell_size_cropping() {
assert_eq!(set_cell_size("foo", 0), "");
assert_eq!(set_cell_size("foo", 1), "f");
assert_eq!(set_cell_size("foo", 2), "fo");
assert_eq!(set_cell_size("abcdefgh", 5), "abcde");
}
#[test]
fn test_set_cell_size_crop_double_width() {
assert_eq!(set_cell_size("😽😽", 4), "😽😽");
assert_eq!(set_cell_size("😽😽", 2), "😽");
assert_eq!(set_cell_size("😽😽", 3), "😽 ");
assert_eq!(set_cell_size("😽😽", 1), " ");
let result = set_cell_size("ありがとう", 6);
assert_eq!(
result,
"ありが",
"Expected 'ありが' (6 cells), got '{}' ({} cells)",
result,
cell_len(&result)
);
assert_eq!(set_cell_size("ありがとう", 5), "あり "); assert_eq!(set_cell_size("ありがとう", 4), "あり");
assert_eq!(set_cell_size("ありがとう", 3), "あ ");
}
#[test]
fn test_set_cell_size_mixed_width() {
assert_eq!(set_cell_size("a😽b", 4), "a😽b");
assert_eq!(set_cell_size("a😽b", 3), "a😽");
assert_eq!(set_cell_size("a😽b", 2), "a ");
assert_eq!(set_cell_size("aあb", 4), "aあb");
assert_eq!(set_cell_size("aあb", 3), "aあ");
assert_eq!(set_cell_size("aあb", 2), "a ");
}
#[test]
fn test_chop_cells_single_width() {
assert_eq!(
chop_cells("abcdefghijk", 3),
vec!["abc", "def", "ghi", "jk"]
);
assert_eq!(chop_cells("hello", 3), vec!["hel", "lo"]);
assert_eq!(chop_cells("abc", 3), vec!["abc"]);
assert_eq!(chop_cells("abc", 10), vec!["abc"]);
}
#[test]
fn test_chop_cells_double_width() {
assert_eq!(
chop_cells("ありがとう", 3),
vec!["あ", "り", "が", "と", "う"]
);
assert_eq!(chop_cells("ありがとう", 4), vec!["あり", "がと", "う"]);
assert_eq!(chop_cells("ありがとう", 6), vec!["ありが", "とう"]);
assert_eq!(chop_cells("😽😽😽", 4), vec!["😽😽", "😽"]);
assert_eq!(chop_cells("😽😽😽", 5), vec!["😽😽", "😽"]); }
#[test]
fn test_chop_cells_mixed_width() {
let text = "あ1り234が5と6う78";
let result = chop_cells(text, 3);
assert_eq!(result, vec!["あ1", "り2", "34", "が5", "と6", "う7", "8"]);
}
#[test]
fn test_chop_cells_empty() {
assert_eq!(chop_cells("", 3), Vec::<String>::new());
assert_eq!(chop_cells("abc", 0), Vec::<String>::new());
}
#[test]
fn test_is_single_cell_widths() {
assert!(is_single_cell_widths("hello world"));
assert!(is_single_cell_widths("abc123"));
assert!(is_single_cell_widths("The quick brown fox"));
assert!(is_single_cell_widths("┌─┬┐│ ││"));
assert!(is_single_cell_widths("├─┼─┤"));
assert!(is_single_cell_widths(""));
assert!(!is_single_cell_widths("💩"));
assert!(!is_single_cell_widths("😽"));
assert!(!is_single_cell_widths("hello 💩"));
assert!(!is_single_cell_widths("わさび"));
assert!(!is_single_cell_widths("ありがとう"));
assert!(!is_single_cell_widths("hello あ"));
assert!(!is_single_cell_widths("\x01"));
assert!(!is_single_cell_widths("a\x01b"));
}
#[test]
fn test_long_strings() {
let long_ascii = "a".repeat(600);
assert_eq!(cell_len(&long_ascii), 600);
assert_eq!(set_cell_size(&long_ascii, 500).len(), 500);
assert!(is_single_cell_widths(&long_ascii));
let long_cjk = "あ".repeat(300);
assert_eq!(cell_len(&long_cjk), 600); assert!(!is_single_cell_widths(&long_cjk));
}
#[test]
fn test_edge_cases() {
assert_eq!(cell_len("a"), 1);
assert_eq!(set_cell_size("a", 1), "a");
assert_eq!(chop_cells("a", 1), vec!["a"]);
let nul_a_len = cell_len("\x00a");
assert!(
nul_a_len >= 1 && nul_a_len <= 2,
"Expected \\x00a width 1-2, got {}",
nul_a_len
);
assert_eq!(cell_len(" "), 3);
assert_eq!(set_cell_size(" ", 5), " ");
let tab_width = get_character_cell_size('\t');
let newline_width = get_character_cell_size('\n');
assert!(
tab_width <= 4,
"Tab width should be <= 4, got {}",
tab_width
);
assert!(
newline_width <= 1,
"Newline width should be <= 1, got {}",
newline_width
);
}
}