1use crossterm::style::Color;
2use std::fmt;
3use unicode_segmentation::UnicodeSegmentation;
4use unicode_width::UnicodeWidthStr;
5
6#[derive(Debug, Clone, Copy)]
8struct StyleParams {
9 fg: Color,
10 bg: Color,
11 bold: bool,
12 italic: bool,
13 underline: bool,
14 dim: bool,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Cell {
20 pub ch: char,
21 pub fg: Color,
22 pub bg: Color,
23 pub bold: bool,
24 pub italic: bool,
25 pub underline: bool,
26 pub dim: bool,
27 pub wide_continuation: bool,
30}
31
32impl Default for Cell {
33 fn default() -> Self {
34 Self {
35 ch: ' ',
36 fg: Color::Reset,
37 bg: Color::Reset,
38 bold: false,
39 italic: false,
40 underline: false,
41 dim: false,
42 wide_continuation: false,
43 }
44 }
45}
46
47impl Cell {
48 pub fn new(ch: char, fg: Color, bg: Color) -> Self {
50 Self {
51 ch,
52 fg,
53 bg,
54 ..Default::default()
55 }
56 }
57
58 pub fn styled(
60 ch: char,
61 fg: Color,
62 bg: Color,
63 bold: bool,
64 italic: bool,
65 underline: bool,
66 dim: bool,
67 ) -> Self {
68 Self {
69 ch,
70 fg,
71 bg,
72 bold,
73 italic,
74 underline,
75 dim,
76 wide_continuation: false,
77 }
78 }
79
80 pub fn wide_continuation(fg: Color, bg: Color) -> Self {
83 Self {
84 ch: ' ',
85 fg,
86 bg,
87 wide_continuation: true,
88 ..Default::default()
89 }
90 }
91
92 pub fn wide_continuation_styled(
94 fg: Color,
95 bg: Color,
96 bold: bool,
97 italic: bool,
98 underline: bool,
99 dim: bool,
100 ) -> Self {
101 Self {
102 ch: ' ',
103 fg,
104 bg,
105 bold,
106 italic,
107 underline,
108 dim,
109 wide_continuation: true,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy)]
116pub struct Rect {
117 pub x: u16,
118 pub y: u16,
119 pub width: u16,
120 pub height: u16,
121}
122
123impl Rect {
124 pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
125 Self {
126 x,
127 y,
128 width,
129 height,
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
136pub struct Buffer {
137 cells: Vec<Cell>,
138 pub width: u16,
139 pub height: u16,
140}
141
142impl Buffer {
143 pub fn new(width: u16, height: u16) -> Self {
145 let size = (width as usize) * (height as usize);
146 Self {
147 cells: vec![Cell::default(); size],
148 width,
149 height,
150 }
151 }
152
153 fn index(&self, x: u16, y: u16) -> Option<usize> {
155 if x < self.width && y < self.height {
156 Some((y as usize) * (self.width as usize) + (x as usize))
157 } else {
158 None
159 }
160 }
161
162 pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
164 self.index(x, y).map(|i| &self.cells[i])
165 }
166
167 pub fn set_cell(&mut self, x: u16, y: u16, cell: Cell) {
169 if let Some(i) = self.index(x, y) {
170 self.cells[i] = cell;
171 }
172 }
173
174 pub fn set(&mut self, x: u16, y: u16, ch: char, fg: Color, bg: Color) {
176 self.set_cell(x, y, Cell::new(ch, fg, bg));
177 }
178
179 pub fn write_str(&mut self, x: u16, y: u16, s: &str, fg: Color, bg: Color) {
186 let mut col = x;
187 for grapheme in s.graphemes(true) {
188 if col >= self.width {
189 break;
190 }
191 let width = UnicodeWidthStr::width(grapheme);
192 if width == 0 {
193 continue; }
195
196 if width == 2 && col + 1 >= self.width {
198 self.set_cell(col, y, Cell::new(' ', fg, bg));
200 break;
201 }
202
203 let ch = grapheme.chars().next().unwrap_or(' ');
206 self.set_cell(col, y, Cell::new(ch, fg, bg));
207
208 if width == 2 {
210 self.set_cell(col + 1, y, Cell::wide_continuation(fg, bg));
211 }
212 col += width as u16;
213 }
214 }
215
216 #[allow(clippy::too_many_arguments)]
223 pub fn write_str_styled(
224 &mut self,
225 x: u16,
226 y: u16,
227 s: &str,
228 fg: Color,
229 bg: Color,
230 bold: bool,
231 italic: bool,
232 underline: bool,
233 dim: bool,
234 ) {
235 let style = StyleParams {
236 fg,
237 bg,
238 bold,
239 italic,
240 underline,
241 dim,
242 };
243 self.write_str_styled_impl(x, y, s, style);
244 }
245
246 fn write_str_styled_impl(&mut self, x: u16, y: u16, s: &str, style: StyleParams) {
248 let mut col = x;
249 for grapheme in s.graphemes(true) {
250 if col >= self.width {
251 break;
252 }
253 let width = UnicodeWidthStr::width(grapheme);
254 if width == 0 {
255 continue; }
257
258 if width == 2 && col + 1 >= self.width {
260 self.set_cell(
262 col,
263 y,
264 Cell::styled(' ', style.fg, style.bg, style.bold, style.italic, style.underline, style.dim),
265 );
266 break;
267 }
268
269 let ch = grapheme.chars().next().unwrap_or(' ');
272 self.set_cell(
273 col,
274 y,
275 Cell::styled(ch, style.fg, style.bg, style.bold, style.italic, style.underline, style.dim),
276 );
277
278 if width == 2 {
280 self.set_cell(
281 col + 1,
282 y,
283 Cell::wide_continuation_styled(style.fg, style.bg, style.bold, style.italic, style.underline, style.dim),
284 );
285 }
286 col += width as u16;
287 }
288 }
289
290 pub fn rect(&self) -> Rect {
292 Rect::new(0, 0, self.width, self.height)
293 }
294
295 #[allow(dead_code)]
297 pub fn clear(&mut self) {
298 for cell in &mut self.cells {
299 *cell = Cell::default();
300 }
301 }
302
303 pub fn fill(&mut self, fg: Color, bg: Color) {
305 for cell in &mut self.cells {
306 cell.ch = ' ';
307 cell.fg = fg;
308 cell.bg = bg;
309 cell.bold = false;
310 cell.italic = false;
311 cell.underline = false;
312 cell.dim = false;
313 }
314 }
315
316 fn buffer_to_string(&self) -> String {
318 let mut result = String::new();
319 for y in 0..self.height {
320 for x in 0..self.width {
321 if let Some(cell) = self.get(x, y) {
322 if cell.wide_continuation {
324 continue;
325 }
326 result.push(cell.ch);
327 }
328 }
329 let trimmed = result.trim_end_matches(' ');
331 result.truncate(trimmed.len());
332 result.push('\n');
333 }
334 while result.ends_with("\n\n") {
336 result.pop();
337 }
338 result
339 }
340
341 pub fn diff<'a>(&'a self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
344 let mut changes = Vec::new();
345
346 for y in 0..self.height {
347 for x in 0..self.width {
348 let self_cell = self.get(x, y);
349 let other_cell = other.get(x, y);
350
351 match (self_cell, other_cell) {
352 (Some(a), Some(b)) if a != b => {
353 changes.push((x, y, a));
354 }
355 (Some(a), None) => {
356 changes.push((x, y, a));
357 }
358 _ => {}
359 }
360 }
361 }
362
363 changes
364 }
365}
366
367impl fmt::Display for Buffer {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 write!(f, "{}", self.buffer_to_string())
370 }
371}