use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
#[inline]
pub fn char_width(c: char) -> usize {
c.width().unwrap_or(0)
}
#[inline]
pub fn str_width(s: &str) -> usize {
s.width()
}
pub trait DisplayWidth {
fn display_width(&self) -> usize;
}
impl DisplayWidth for str {
#[inline]
fn display_width(&self) -> usize {
str_width(self)
}
}
impl DisplayWidth for String {
#[inline]
fn display_width(&self) -> usize {
str_width(self)
}
}
#[inline]
pub fn visual_column_at_byte(s: &str, byte_offset: usize) -> usize {
s[..byte_offset.min(s.len())].chars().map(char_width).sum()
}
#[inline]
pub fn byte_offset_at_visual_column(s: &str, visual_col: usize) -> usize {
let mut current_col = 0;
for (byte_idx, ch) in s.char_indices() {
if current_col >= visual_col {
return byte_idx;
}
current_col += char_width(ch);
}
s.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ascii_width() {
assert_eq!(str_width("Hello"), 5);
assert_eq!(str_width(""), 0);
assert_eq!(str_width(" "), 1);
}
#[test]
fn test_cjk_width() {
assert_eq!(str_width("你好"), 4);
assert_eq!(str_width("你好世界"), 8);
assert_eq!(str_width("月"), 2);
assert_eq!(str_width("日本"), 4);
assert_eq!(str_width("한글"), 4);
}
#[test]
fn test_emoji_width() {
assert_eq!(str_width("🚀"), 2);
assert_eq!(str_width("🎉"), 2);
assert_eq!(str_width("🚀🎉"), 4);
}
#[test]
fn test_mixed_width() {
assert_eq!(str_width("Hello你好"), 5 + 4);
assert_eq!(str_width("a你b"), 1 + 2 + 1);
assert_eq!(str_width("Hi🚀"), 2 + 2);
}
#[test]
fn test_char_width() {
assert_eq!(char_width('a'), 1);
assert_eq!(char_width('你'), 2);
assert_eq!(char_width('🚀'), 2);
}
#[test]
fn test_zero_width() {
assert_eq!(char_width('\0'), 0);
assert_eq!(char_width('\t'), 0);
assert_eq!(char_width('\u{200B}'), 0);
}
#[test]
fn test_display_width_trait() {
let s = "你好";
assert_eq!(s.display_width(), 4);
let string = String::from("Hello🚀");
assert_eq!(string.display_width(), 7);
}
}