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(url.to_string());
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 resize(&mut self, area: Rect) {
258 self.area = area;
259 let size = area.area() as usize;
260 self.content.resize(size, Cell::default());
261 self.reset();
262 }
263}
264
265fn intersect_rects(a: Rect, b: Rect) -> Rect {
266 let x = a.x.max(b.x);
267 let y = a.y.max(b.y);
268 let right = a.right().min(b.right());
269 let bottom = a.bottom().min(b.bottom());
270 let width = right.saturating_sub(x);
271 let height = bottom.saturating_sub(y);
272 Rect::new(x, y, width, height)
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn clip_stack_intersects_nested_regions() {
281 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
282 buf.push_clip(Rect::new(1, 1, 6, 3));
283 buf.push_clip(Rect::new(4, 0, 6, 4));
284
285 buf.set_char(3, 2, 'x', Style::new());
286 buf.set_char(4, 2, 'y', Style::new());
287
288 assert_eq!(buf.get(3, 2).symbol, " ");
289 assert_eq!(buf.get(4, 2).symbol, "y");
290 }
291
292 #[test]
293 fn set_string_advances_even_when_clipped() {
294 let mut buf = Buffer::empty(Rect::new(0, 0, 8, 1));
295 buf.push_clip(Rect::new(2, 0, 6, 1));
296
297 buf.set_string(0, 0, "abcd", Style::new());
298
299 assert_eq!(buf.get(2, 0).symbol, "c");
300 assert_eq!(buf.get(3, 0).symbol, "d");
301 }
302
303 #[test]
304 fn pop_clip_restores_previous_clip() {
305 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
306 buf.push_clip(Rect::new(0, 0, 2, 1));
307 buf.push_clip(Rect::new(4, 0, 2, 1));
308
309 buf.set_char(1, 0, 'a', Style::new());
310 buf.pop_clip();
311 buf.set_char(1, 0, 'b', Style::new());
312
313 assert_eq!(buf.get(1, 0).symbol, "b");
314 }
315
316 #[test]
317 fn reset_clears_clip_stack() {
318 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
319 buf.push_clip(Rect::new(0, 0, 0, 0));
320 buf.reset();
321 buf.set_char(0, 0, 'z', Style::new());
322
323 assert_eq!(buf.get(0, 0).symbol, "z");
324 }
325}