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}
27
28impl Buffer {
29 pub fn empty(area: Rect) -> Self {
31 let size = area.area() as usize;
32 Self {
33 area,
34 content: vec![Cell::default(); size],
35 clip_stack: Vec::new(),
36 }
37 }
38
39 pub fn push_clip(&mut self, rect: Rect) {
45 let effective = if let Some(current) = self.clip_stack.last() {
46 intersect_rects(*current, rect)
47 } else {
48 rect
49 };
50 self.clip_stack.push(effective);
51 }
52
53 pub fn pop_clip(&mut self) {
58 self.clip_stack.pop();
59 }
60
61 fn effective_clip(&self) -> Option<&Rect> {
62 self.clip_stack.last()
63 }
64
65 #[inline]
66 fn index_of(&self, x: u32, y: u32) -> usize {
67 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
68 }
69
70 #[inline]
72 pub fn in_bounds(&self, x: u32, y: u32) -> bool {
73 x >= self.area.x && x < self.area.right() && y >= self.area.y && y < self.area.bottom()
74 }
75
76 #[inline]
80 pub fn get(&self, x: u32, y: u32) -> &Cell {
81 &self.content[self.index_of(x, y)]
82 }
83
84 #[inline]
88 pub fn get_mut(&mut self, x: u32, y: u32) -> &mut Cell {
89 let idx = self.index_of(x, y);
90 &mut self.content[idx]
91 }
92
93 pub fn set_string(&mut self, mut x: u32, y: u32, s: &str, style: Style) {
100 if y >= self.area.bottom() {
101 return;
102 }
103 let clip = self.effective_clip().copied();
104 for ch in s.chars() {
105 if x >= self.area.right() {
106 break;
107 }
108 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
109 if char_width == 0 {
110 if x > self.area.x {
113 let prev_in_clip = clip.map_or(true, |clip| {
114 (x - 1) >= clip.x
115 && (x - 1) < clip.right()
116 && y >= clip.y
117 && y < clip.bottom()
118 });
119 if prev_in_clip {
120 self.get_mut(x - 1, y).symbol.push(ch);
121 }
122 }
123 continue;
124 }
125
126 let in_clip = clip.map_or(true, |clip| {
127 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
128 });
129
130 if !in_clip {
131 x = x.saturating_add(char_width);
132 continue;
133 }
134
135 let cell = self.get_mut(x, y);
136 cell.set_char(ch);
137 cell.set_style(style);
138
139 if char_width > 1 {
141 let next_x = x + 1;
142 if next_x < self.area.right() {
143 let next = self.get_mut(next_x, y);
144 next.symbol.clear();
145 next.style = style;
146 }
147 }
148
149 x = x.saturating_add(char_width);
150 }
151 }
152
153 pub fn set_string_linked(&mut self, mut x: u32, y: u32, s: &str, style: Style, url: &str) {
158 if y >= self.area.bottom() {
159 return;
160 }
161 let clip = self.effective_clip().copied();
162 let link = Some(compact_str::CompactString::new(url));
163 for ch in s.chars() {
164 if x >= self.area.right() {
165 break;
166 }
167 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
168 if char_width == 0 {
169 if x > self.area.x {
170 let prev_in_clip = clip.map_or(true, |clip| {
171 (x - 1) >= clip.x
172 && (x - 1) < clip.right()
173 && y >= clip.y
174 && y < clip.bottom()
175 });
176 if prev_in_clip {
177 self.get_mut(x - 1, y).symbol.push(ch);
178 }
179 }
180 continue;
181 }
182
183 let in_clip = clip.map_or(true, |clip| {
184 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
185 });
186
187 if !in_clip {
188 x = x.saturating_add(char_width);
189 continue;
190 }
191
192 let cell = self.get_mut(x, y);
193 cell.set_char(ch);
194 cell.set_style(style);
195 cell.hyperlink = link.clone();
196
197 if char_width > 1 {
198 let next_x = x + 1;
199 if next_x < self.area.right() {
200 let next = self.get_mut(next_x, y);
201 next.symbol.clear();
202 next.style = style;
203 next.hyperlink = link.clone();
204 }
205 }
206
207 x = x.saturating_add(char_width);
208 }
209 }
210
211 pub fn set_char(&mut self, x: u32, y: u32, ch: char, style: Style) {
215 let in_clip = self.effective_clip().map_or(true, |clip| {
216 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
217 });
218 if !self.in_bounds(x, y) || !in_clip {
219 return;
220 }
221 let cell = self.get_mut(x, y);
222 cell.set_char(ch);
223 cell.set_style(style);
224 }
225
226 pub fn diff<'a>(&'a self, other: &'a Buffer) -> Vec<(u32, u32, &'a Cell)> {
232 let mut updates = Vec::new();
233 for y in self.area.y..self.area.bottom() {
234 for x in self.area.x..self.area.right() {
235 let cur = self.get(x, y);
236 let prev = other.get(x, y);
237 if cur != prev {
238 updates.push((x, y, cur));
239 }
240 }
241 }
242 updates
243 }
244
245 pub fn reset(&mut self) {
247 for cell in &mut self.content {
248 cell.reset();
249 }
250 self.clip_stack.clear();
251 }
252
253 pub fn reset_with_bg(&mut self, bg: crate::style::Color) {
255 for cell in &mut self.content {
256 cell.reset();
257 cell.style.bg = Some(bg);
258 }
259 self.clip_stack.clear();
260 }
261
262 pub fn resize(&mut self, area: Rect) {
267 self.area = area;
268 let size = area.area() as usize;
269 self.content.resize(size, Cell::default());
270 self.reset();
271 }
272}
273
274fn intersect_rects(a: Rect, b: Rect) -> Rect {
275 let x = a.x.max(b.x);
276 let y = a.y.max(b.y);
277 let right = a.right().min(b.right());
278 let bottom = a.bottom().min(b.bottom());
279 let width = right.saturating_sub(x);
280 let height = bottom.saturating_sub(y);
281 Rect::new(x, y, width, height)
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn clip_stack_intersects_nested_regions() {
290 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
291 buf.push_clip(Rect::new(1, 1, 6, 3));
292 buf.push_clip(Rect::new(4, 0, 6, 4));
293
294 buf.set_char(3, 2, 'x', Style::new());
295 buf.set_char(4, 2, 'y', Style::new());
296
297 assert_eq!(buf.get(3, 2).symbol, " ");
298 assert_eq!(buf.get(4, 2).symbol, "y");
299 }
300
301 #[test]
302 fn set_string_advances_even_when_clipped() {
303 let mut buf = Buffer::empty(Rect::new(0, 0, 8, 1));
304 buf.push_clip(Rect::new(2, 0, 6, 1));
305
306 buf.set_string(0, 0, "abcd", Style::new());
307
308 assert_eq!(buf.get(2, 0).symbol, "c");
309 assert_eq!(buf.get(3, 0).symbol, "d");
310 }
311
312 #[test]
313 fn pop_clip_restores_previous_clip() {
314 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
315 buf.push_clip(Rect::new(0, 0, 2, 1));
316 buf.push_clip(Rect::new(4, 0, 2, 1));
317
318 buf.set_char(1, 0, 'a', Style::new());
319 buf.pop_clip();
320 buf.set_char(1, 0, 'b', Style::new());
321
322 assert_eq!(buf.get(1, 0).symbol, "b");
323 }
324
325 #[test]
326 fn reset_clears_clip_stack() {
327 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
328 buf.push_clip(Rect::new(0, 0, 0, 0));
329 buf.reset();
330 buf.set_char(0, 0, 'z', Style::new());
331
332 assert_eq!(buf.get(0, 0).symbol, "z");
333 }
334}