use unicode_width::UnicodeWidthChar;
pub fn compute_display_widths(s: &str) -> Vec<u8> {
s.chars()
.map(|ch| {
if ch == '\t' {
8u8
} else {
ch.width().unwrap_or(0) as u8
}
})
.collect()
}
pub fn display_width(s: &str) -> usize {
compute_display_widths(s).iter().map(|&w| w as usize).sum()
}
pub fn display_col_to_char_range(s: &str, col_start: usize, col_end: usize) -> (usize, usize) {
if col_start >= col_end {
let char_idx = s.chars().count().min(
compute_display_widths(s)
.iter()
.scan(0usize, |col, width| {
let start = *col;
*col += *width as usize;
Some(start)
})
.position(|start| start >= col_start)
.unwrap_or_else(|| s.chars().count()),
);
return (char_idx, char_idx);
}
let mut current_col = 0usize;
let mut char_start = None;
let mut char_end = 0;
for (i, ch) in s.chars().enumerate() {
let w = if ch == '\t' {
8
} else {
ch.width().unwrap_or(0)
};
if char_start.is_none() && current_col + w > col_start {
char_start = Some(i);
}
current_col += w;
if char_start.is_some() {
char_end = i + 1;
}
if current_col >= col_end {
break;
}
}
(char_start.unwrap_or(0), char_end)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ascii_widths() {
let widths = compute_display_widths("hello");
assert_eq!(widths, vec![1, 1, 1, 1, 1]);
}
#[test]
fn test_cjk_widths() {
let widths = compute_display_widths("你好");
assert_eq!(widths, vec![2, 2]);
}
#[test]
fn test_mixed_widths() {
let widths = compute_display_widths("hi你好");
assert_eq!(widths, vec![1, 1, 2, 2]);
}
#[test]
fn test_display_width() {
assert_eq!(display_width("hello"), 5);
assert_eq!(display_width("你好"), 4);
assert_eq!(display_width("hi你好"), 6);
}
#[test]
fn test_tab_width() {
let widths = compute_display_widths("\t");
assert_eq!(widths, vec![8]);
}
#[test]
fn test_display_col_to_char_range_ascii() {
let (start, end) = display_col_to_char_range("hello", 1, 4);
assert_eq!(start, 1);
assert_eq!(end, 4);
}
#[test]
fn test_display_col_to_char_range_cjk() {
let (start, end) = display_col_to_char_range("你好世界", 0, 4);
assert_eq!(start, 0);
assert_eq!(end, 2);
}
#[test]
fn test_display_col_to_char_range_mixed() {
let (start, end) = display_col_to_char_range("hi你好", 1, 5);
assert_eq!(start, 1);
assert_eq!(end, 4);
}
#[test]
fn test_display_col_to_char_range_empty() {
assert_eq!(display_col_to_char_range("hello", 0, 0), (0, 0));
assert_eq!(display_col_to_char_range("hello", 2, 2), (2, 2));
}
}