#[derive(Debug, Clone, PartialEq)]
pub enum AnsiColor {
Indexed(u8),
Rgb(u8, u8, u8),
}
#[derive(Debug, Clone, PartialEq)]
pub struct StyledSpan {
pub start: usize,
pub end: usize,
pub fg: Option<AnsiColor>,
pub bg: Option<AnsiColor>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub dim: bool,
}
#[derive(Debug, Clone)]
pub struct Line {
pub content: String,
pub display_widths: Vec<u8>,
pub styles: Vec<StyledSpan>,
}
impl Line {
pub fn display_width(&self) -> usize {
self.display_widths.iter().map(|&w| w as usize).sum()
}
pub fn char_count(&self) -> usize {
self.content.chars().count()
}
pub fn char_index_for_display_col(&self, target_col: usize) -> usize {
let mut display_col = 0usize;
for (idx, width) in self.display_widths.iter().enumerate() {
let width = *width as usize;
if display_col + width > target_col {
return idx;
}
display_col += width;
}
self.display_widths.len()
}
pub fn display_col_for_char_index(&self, char_idx: usize) -> usize {
self.display_widths
.iter()
.take(char_idx.min(self.display_widths.len()))
.map(|&w| w as usize)
.sum()
}
pub fn extract_by_display_cols(&self, col_start: usize, col_end: usize) -> String {
if col_start >= col_end {
return String::new();
}
let (char_start, char_end) =
crate::parse::unicode::display_col_to_char_range(&self.content, col_start, col_end);
self.content
.chars()
.skip(char_start)
.take(char_end - char_start)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_line(s: &str) -> Line {
let content = s.to_string();
let display_widths = crate::parse::unicode::compute_display_widths(&content);
Line {
content,
display_widths,
styles: Vec::new(),
}
}
#[test]
fn test_display_width() {
let line = make_line("hello");
assert_eq!(line.display_width(), 5);
}
#[test]
fn test_display_width_cjk() {
let line = make_line("你好");
assert_eq!(line.display_width(), 4);
}
#[test]
fn test_extract_ascii() {
let line = make_line("hello world");
assert_eq!(line.extract_by_display_cols(0, 5), "hello");
}
#[test]
fn test_extract_cjk() {
let line = make_line("你好世界");
assert_eq!(line.extract_by_display_cols(0, 4), "你好");
}
#[test]
fn test_extract_beyond_line() {
let line = make_line("hi");
assert_eq!(line.extract_by_display_cols(0, 10), "hi");
}
#[test]
fn test_extract_empty_range() {
let line = make_line("hello");
assert_eq!(line.extract_by_display_cols(0, 0), "");
assert_eq!(line.extract_by_display_cols(2, 2), "");
}
#[test]
fn test_char_index_for_display_col() {
let line = make_line("a你好");
assert_eq!(line.char_index_for_display_col(0), 0);
assert_eq!(line.char_index_for_display_col(1), 1);
assert_eq!(line.char_index_for_display_col(2), 1);
assert_eq!(line.char_index_for_display_col(3), 2);
assert_eq!(line.char_index_for_display_col(4), 2);
}
#[test]
fn test_display_col_for_char_index() {
let line = make_line("a你好");
assert_eq!(line.display_col_for_char_index(0), 0);
assert_eq!(line.display_col_for_char_index(1), 1);
assert_eq!(line.display_col_for_char_index(2), 3);
assert_eq!(line.display_col_for_char_index(3), 5);
}
}