sivtr_core/buffer/
line.rs1#[derive(Debug, Clone, PartialEq)]
3pub enum AnsiColor {
4 Indexed(u8),
6 Rgb(u8, u8, u8),
8}
9
10#[derive(Debug, Clone, PartialEq)]
12pub struct StyledSpan {
13 pub start: usize,
15 pub end: usize,
17 pub fg: Option<AnsiColor>,
19 pub bg: Option<AnsiColor>,
21 pub bold: bool,
22 pub italic: bool,
23 pub underline: bool,
24 pub dim: bool,
25}
26
27#[derive(Debug, Clone)]
29pub struct Line {
30 pub content: String,
32 pub display_widths: Vec<u8>,
34 pub styles: Vec<StyledSpan>,
36}
37
38impl Line {
39 pub fn display_width(&self) -> usize {
41 self.display_widths.iter().map(|&w| w as usize).sum()
42 }
43
44 pub fn char_count(&self) -> usize {
46 self.content.chars().count()
47 }
48
49 pub fn char_index_for_display_col(&self, target_col: usize) -> usize {
51 let mut display_col = 0usize;
52 for (idx, width) in self.display_widths.iter().enumerate() {
53 let width = *width as usize;
54 if display_col + width > target_col {
55 return idx;
56 }
57 display_col += width;
58 }
59 self.display_widths.len()
60 }
61
62 pub fn display_col_for_char_index(&self, char_idx: usize) -> usize {
64 self.display_widths
65 .iter()
66 .take(char_idx.min(self.display_widths.len()))
67 .map(|&w| w as usize)
68 .sum()
69 }
70
71 pub fn extract_by_display_cols(&self, col_start: usize, col_end: usize) -> String {
74 if col_start >= col_end {
75 return String::new();
76 }
77 let (char_start, char_end) =
78 crate::parse::unicode::display_col_to_char_range(&self.content, col_start, col_end);
79 self.content
80 .chars()
81 .skip(char_start)
82 .take(char_end - char_start)
83 .collect()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn make_line(s: &str) -> Line {
92 let content = s.to_string();
93 let display_widths = crate::parse::unicode::compute_display_widths(&content);
94 Line {
95 content,
96 display_widths,
97 styles: Vec::new(),
98 }
99 }
100
101 #[test]
102 fn test_display_width() {
103 let line = make_line("hello");
104 assert_eq!(line.display_width(), 5);
105 }
106
107 #[test]
108 fn test_display_width_cjk() {
109 let line = make_line("你好");
110 assert_eq!(line.display_width(), 4);
111 }
112
113 #[test]
114 fn test_extract_ascii() {
115 let line = make_line("hello world");
116 assert_eq!(line.extract_by_display_cols(0, 5), "hello");
117 }
118
119 #[test]
120 fn test_extract_cjk() {
121 let line = make_line("你好世界");
122 assert_eq!(line.extract_by_display_cols(0, 4), "你好");
123 }
124
125 #[test]
126 fn test_extract_beyond_line() {
127 let line = make_line("hi");
128 assert_eq!(line.extract_by_display_cols(0, 10), "hi");
129 }
130
131 #[test]
132 fn test_extract_empty_range() {
133 let line = make_line("hello");
134 assert_eq!(line.extract_by_display_cols(0, 0), "");
135 assert_eq!(line.extract_by_display_cols(2, 2), "");
136 }
137
138 #[test]
139 fn test_char_index_for_display_col() {
140 let line = make_line("a你好");
141 assert_eq!(line.char_index_for_display_col(0), 0);
142 assert_eq!(line.char_index_for_display_col(1), 1);
143 assert_eq!(line.char_index_for_display_col(2), 1);
144 assert_eq!(line.char_index_for_display_col(3), 2);
145 assert_eq!(line.char_index_for_display_col(4), 2);
146 }
147
148 #[test]
149 fn test_display_col_for_char_index() {
150 let line = make_line("a你好");
151 assert_eq!(line.display_col_for_char_index(0), 0);
152 assert_eq!(line.display_col_for_char_index(1), 1);
153 assert_eq!(line.display_col_for_char_index(2), 3);
154 assert_eq!(line.display_col_for_char_index(3), 5);
155 }
156}