1use crate::cell::Cell;
8use crate::rect::Rect;
9use crate::style::Style;
10use unicode_width::UnicodeWidthChar;
11
12pub struct Buffer {
21 pub area: Rect,
23 pub content: Vec<Cell>,
25 pub(crate) clip_stack: Vec<Rect>,
26 pub(crate) raw_sequences: Vec<(u32, u32, String)>,
27}
28
29impl Buffer {
30 pub fn empty(area: Rect) -> Self {
32 let size = area.area() as usize;
33 Self {
34 area,
35 content: vec![Cell::default(); size],
36 clip_stack: Vec::new(),
37 raw_sequences: Vec::new(),
38 }
39 }
40
41 pub fn raw_sequence(&mut self, x: u32, y: u32, seq: String) {
45 self.raw_sequences.push((x, y, seq));
46 }
47
48 pub fn push_clip(&mut self, rect: Rect) {
54 let effective = if let Some(current) = self.clip_stack.last() {
55 intersect_rects(*current, rect)
56 } else {
57 rect
58 };
59 self.clip_stack.push(effective);
60 }
61
62 pub fn pop_clip(&mut self) {
67 self.clip_stack.pop();
68 }
69
70 fn effective_clip(&self) -> Option<&Rect> {
71 self.clip_stack.last()
72 }
73
74 #[inline]
75 fn index_of(&self, x: u32, y: u32) -> usize {
76 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
77 }
78
79 #[inline]
81 pub fn in_bounds(&self, x: u32, y: u32) -> bool {
82 x >= self.area.x && x < self.area.right() && y >= self.area.y && y < self.area.bottom()
83 }
84
85 #[inline]
89 pub fn get(&self, x: u32, y: u32) -> &Cell {
90 debug_assert!(
91 self.in_bounds(x, y),
92 "Buffer::get({x}, {y}) out of bounds for area {:?}",
93 self.area
94 );
95 &self.content[self.index_of(x, y)]
96 }
97
98 #[inline]
102 pub fn get_mut(&mut self, x: u32, y: u32) -> &mut Cell {
103 debug_assert!(
104 self.in_bounds(x, y),
105 "Buffer::get_mut({x}, {y}) out of bounds for area {:?}",
106 self.area
107 );
108 let idx = self.index_of(x, y);
109 &mut self.content[idx]
110 }
111
112 pub fn set_string(&mut self, mut x: u32, y: u32, s: &str, style: Style) {
119 if y >= self.area.bottom() {
120 return;
121 }
122 let clip = self.effective_clip().copied();
123 for ch in s.chars() {
124 if x >= self.area.right() {
125 break;
126 }
127 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
128 if char_width == 0 {
129 if x > self.area.x {
132 let prev_in_clip = clip.map_or(true, |clip| {
133 (x - 1) >= clip.x
134 && (x - 1) < clip.right()
135 && y >= clip.y
136 && y < clip.bottom()
137 });
138 if prev_in_clip {
139 self.get_mut(x - 1, y).symbol.push(ch);
140 }
141 }
142 continue;
143 }
144
145 let in_clip = clip.map_or(true, |clip| {
146 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
147 });
148
149 if !in_clip {
150 x = x.saturating_add(char_width);
151 continue;
152 }
153
154 let cell = self.get_mut(x, y);
155 cell.set_char(ch);
156 cell.set_style(style);
157
158 if char_width > 1 {
160 let next_x = x + 1;
161 if next_x < self.area.right() {
162 let next = self.get_mut(next_x, y);
163 next.symbol.clear();
164 next.style = style;
165 }
166 }
167
168 x = x.saturating_add(char_width);
169 }
170 }
171
172 pub fn set_string_linked(&mut self, mut x: u32, y: u32, s: &str, style: Style, url: &str) {
177 if y >= self.area.bottom() {
178 return;
179 }
180 let clip = self.effective_clip().copied();
181 let link = Some(compact_str::CompactString::new(url));
182 for ch in s.chars() {
183 if x >= self.area.right() {
184 break;
185 }
186 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
187 if char_width == 0 {
188 if x > self.area.x {
189 let prev_in_clip = clip.map_or(true, |clip| {
190 (x - 1) >= clip.x
191 && (x - 1) < clip.right()
192 && y >= clip.y
193 && y < clip.bottom()
194 });
195 if prev_in_clip {
196 self.get_mut(x - 1, y).symbol.push(ch);
197 }
198 }
199 continue;
200 }
201
202 let in_clip = clip.map_or(true, |clip| {
203 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
204 });
205
206 if !in_clip {
207 x = x.saturating_add(char_width);
208 continue;
209 }
210
211 let cell = self.get_mut(x, y);
212 cell.set_char(ch);
213 cell.set_style(style);
214 cell.hyperlink = link.clone();
215
216 if char_width > 1 {
217 let next_x = x + 1;
218 if next_x < self.area.right() {
219 let next = self.get_mut(next_x, y);
220 next.symbol.clear();
221 next.style = style;
222 next.hyperlink = link.clone();
223 }
224 }
225
226 x = x.saturating_add(char_width);
227 }
228 }
229
230 pub fn set_char(&mut self, x: u32, y: u32, ch: char, style: Style) {
234 let in_clip = self.effective_clip().map_or(true, |clip| {
235 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
236 });
237 if !self.in_bounds(x, y) || !in_clip {
238 return;
239 }
240 let cell = self.get_mut(x, y);
241 cell.set_char(ch);
242 cell.set_style(style);
243 }
244
245 pub fn diff<'a>(&'a self, other: &'a Buffer) -> Vec<(u32, u32, &'a Cell)> {
251 let mut updates = Vec::new();
252 for y in self.area.y..self.area.bottom() {
253 for x in self.area.x..self.area.right() {
254 let cur = self.get(x, y);
255 let prev = other.get(x, y);
256 if cur != prev {
257 updates.push((x, y, cur));
258 }
259 }
260 }
261 updates
262 }
263
264 pub fn reset(&mut self) {
266 for cell in &mut self.content {
267 cell.reset();
268 }
269 self.clip_stack.clear();
270 self.raw_sequences.clear();
271 }
272
273 pub fn reset_with_bg(&mut self, bg: crate::style::Color) {
275 for cell in &mut self.content {
276 cell.reset();
277 cell.style.bg = Some(bg);
278 }
279 self.clip_stack.clear();
280 self.raw_sequences.clear();
281 }
282
283 pub fn resize(&mut self, area: Rect) {
288 self.area = area;
289 let size = area.area() as usize;
290 self.content.resize(size, Cell::default());
291 self.reset();
292 }
293}
294
295fn intersect_rects(a: Rect, b: Rect) -> Rect {
296 let x = a.x.max(b.x);
297 let y = a.y.max(b.y);
298 let right = a.right().min(b.right());
299 let bottom = a.bottom().min(b.bottom());
300 let width = right.saturating_sub(x);
301 let height = bottom.saturating_sub(y);
302 Rect::new(x, y, width, height)
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn clip_stack_intersects_nested_regions() {
311 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
312 buf.push_clip(Rect::new(1, 1, 6, 3));
313 buf.push_clip(Rect::new(4, 0, 6, 4));
314
315 buf.set_char(3, 2, 'x', Style::new());
316 buf.set_char(4, 2, 'y', Style::new());
317
318 assert_eq!(buf.get(3, 2).symbol, " ");
319 assert_eq!(buf.get(4, 2).symbol, "y");
320 }
321
322 #[test]
323 fn set_string_advances_even_when_clipped() {
324 let mut buf = Buffer::empty(Rect::new(0, 0, 8, 1));
325 buf.push_clip(Rect::new(2, 0, 6, 1));
326
327 buf.set_string(0, 0, "abcd", Style::new());
328
329 assert_eq!(buf.get(2, 0).symbol, "c");
330 assert_eq!(buf.get(3, 0).symbol, "d");
331 }
332
333 #[test]
334 fn pop_clip_restores_previous_clip() {
335 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
336 buf.push_clip(Rect::new(0, 0, 2, 1));
337 buf.push_clip(Rect::new(4, 0, 2, 1));
338
339 buf.set_char(1, 0, 'a', Style::new());
340 buf.pop_clip();
341 buf.set_char(1, 0, 'b', Style::new());
342
343 assert_eq!(buf.get(1, 0).symbol, "b");
344 }
345
346 #[test]
347 fn reset_clears_clip_stack() {
348 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
349 buf.push_clip(Rect::new(0, 0, 0, 0));
350 buf.reset();
351 buf.set_char(0, 0, 'z', Style::new());
352
353 assert_eq!(buf.get(0, 0).symbol, "z");
354 }
355}