use crate::core::buffer::{Buffer, Cell};
use crate::core::color::Color;
pub fn sanitize_str(s: &str, max_width: usize) -> String {
let mut result = String::with_capacity(s.len());
let mut width = 0;
for ch in s.chars() {
let w = char_display_width(ch);
if width + w > max_width {
break;
}
if ch.is_control() && ch != '\n' && ch != '\t' {
result.push(' ');
} else {
result.push(ch);
}
width += w;
}
result
}
pub fn sanitize_and_set(
buf: &mut Buffer,
x: usize,
y: usize,
s: &str,
fg: Color,
bg: Option<Color>,
max_width: usize,
) -> usize {
let mut col = 0;
for ch in s.chars() {
if col >= max_width {
break;
}
let w = char_display_width(ch);
if ch.is_control() && ch != '\n' && ch != '\t' {
if col + 1 <= max_width {
buf.set(x + col, y, Cell::new(' ', fg, bg));
col += 1;
}
} else if w == 1 {
if col + 1 <= max_width {
buf.set(x + col, y, Cell::new(ch, fg, bg));
col += 1;
}
} else if w == 2 {
if col + 2 <= max_width {
buf.set(x + col, y, Cell::new(ch, fg, bg));
col += 2;
}
} else {
if col + 1 <= max_width {
buf.set(x + col, y, Cell::new(ch, fg, bg));
col += 1;
}
}
}
col
}
pub fn char_display_width(ch: char) -> usize {
if ch.is_control() {
return 0;
}
let cp = ch as u32;
if cp == 0x200B || cp == 0x200C || cp == 0x200D || cp == 0xFEFF {
return 0;
}
if cp >= 0x1100
&& (cp <= 0x115F
|| cp == 0x2329
|| cp == 0x232A
|| (cp >= 0x2E80 && cp <= 0x303E)
|| (cp >= 0x3040 && cp <= 0x33BF)
|| (cp >= 0x3400 && cp <= 0x4DBF)
|| (cp >= 0x4E00 && cp <= 0x9FFF)
|| (cp >= 0xA000 && cp <= 0xA4CF)
|| (cp >= 0xAC00 && cp <= 0xD7AF)
|| (cp >= 0xF900 && cp <= 0xFAFF)
|| (cp >= 0xFE30 && cp <= 0xFE6F)
|| (cp >= 0xFF01 && cp <= 0xFF60)
|| (cp >= 0xFFE0 && cp <= 0xFFE6)
|| (cp >= 0x20000 && cp <= 0x2FFFD)
|| (cp >= 0x30000 && cp <= 0x3FFFD))
{
return 2;
}
1
}
pub fn str_display_width(s: &str) -> usize {
s.chars().map(char_display_width).sum()
}
pub fn truncate_str(s: &str, max_width: usize) -> String {
let w = str_display_width(s);
if w <= max_width {
return s.to_string();
}
let ellipsis_width = 3;
if max_width <= ellipsis_width {
return sanitize_str(s, max_width);
}
let target = max_width - ellipsis_width;
let mut result = String::new();
let mut width = 0;
for ch in s.chars() {
let cw = char_display_width(ch);
if width + cw > target {
break;
}
result.push(ch);
width += cw;
}
result.push_str("...");
result
}
pub fn sanitize_title(s: &str, max_width: usize) -> String {
let cleaned: String = s.chars().filter(|c| !c.is_control()).collect();
truncate_str(&cleaned, max_width)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_display_width_ascii() {
assert_eq!(char_display_width('a'), 1);
assert_eq!(char_display_width(' '), 1);
assert_eq!(char_display_width('\n'), 0);
}
#[test]
fn test_char_display_width_cjk() {
assert_eq!(char_display_width('中'), 2);
assert_eq!(char_display_width('日'), 2);
}
#[test]
fn test_str_display_width() {
assert_eq!(str_display_width("abc"), 3);
assert_eq!(str_display_width("中a"), 3);
}
#[test]
fn test_sanitize_str() {
let s = "hello\x01\x02world";
let result = sanitize_str(s, 20);
assert_eq!(result, "hello world");
}
#[test]
fn test_truncate_str() {
let result = truncate_str("hello world", 8);
assert_eq!(result, "hello...");
}
#[test]
fn test_truncate_no_op() {
let result = truncate_str("hi", 10);
assert_eq!(result, "hi");
}
#[test]
fn test_sanitize_and_set() {
let mut buf = Buffer::new(10, 1);
let written = sanitize_and_set(&mut buf, 0, 0, "abc", Color::WHITE, None, 10);
assert_eq!(written, 3);
assert_eq!(buf.get(0, 0).unwrap().ch, 'a');
assert_eq!(buf.get(2, 0).unwrap().ch, 'c');
}
#[test]
fn test_sanitize_and_set_truncates() {
let mut buf = Buffer::new(5, 1);
let written = sanitize_and_set(&mut buf, 0, 0, "hello world", Color::WHITE, None, 5);
assert_eq!(written, 5);
assert_eq!(buf.get(0, 0).unwrap().ch, 'h');
assert_eq!(buf.get(4, 0).unwrap().ch, 'o');
}
#[test]
fn test_sanitize_title() {
let result = sanitize_title("my\x01title", 10);
assert_eq!(result, "mytitle");
}
}