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 &self.content[self.index_of(x, y)]
91 }
92
93 #[inline]
97 pub fn get_mut(&mut self, x: u32, y: u32) -> &mut Cell {
98 let idx = self.index_of(x, y);
99 &mut self.content[idx]
100 }
101
102 pub fn set_string(&mut self, mut x: u32, y: u32, s: &str, style: Style) {
109 if y >= self.area.bottom() {
110 return;
111 }
112 let clip = self.effective_clip().copied();
113 for ch in s.chars() {
114 if x >= self.area.right() {
115 break;
116 }
117 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
118 if char_width == 0 {
119 if x > self.area.x {
122 let prev_in_clip = clip.map_or(true, |clip| {
123 (x - 1) >= clip.x
124 && (x - 1) < clip.right()
125 && y >= clip.y
126 && y < clip.bottom()
127 });
128 if prev_in_clip {
129 self.get_mut(x - 1, y).symbol.push(ch);
130 }
131 }
132 continue;
133 }
134
135 let in_clip = clip.map_or(true, |clip| {
136 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
137 });
138
139 if !in_clip {
140 x = x.saturating_add(char_width);
141 continue;
142 }
143
144 let cell = self.get_mut(x, y);
145 cell.set_char(ch);
146 cell.set_style(style);
147
148 if char_width > 1 {
150 let next_x = x + 1;
151 if next_x < self.area.right() {
152 let next = self.get_mut(next_x, y);
153 next.symbol.clear();
154 next.style = style;
155 }
156 }
157
158 x = x.saturating_add(char_width);
159 }
160 }
161
162 pub fn set_string_linked(&mut self, mut x: u32, y: u32, s: &str, style: Style, url: &str) {
167 if y >= self.area.bottom() {
168 return;
169 }
170 let clip = self.effective_clip().copied();
171 let link = Some(compact_str::CompactString::new(url));
172 for ch in s.chars() {
173 if x >= self.area.right() {
174 break;
175 }
176 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
177 if char_width == 0 {
178 if x > self.area.x {
179 let prev_in_clip = clip.map_or(true, |clip| {
180 (x - 1) >= clip.x
181 && (x - 1) < clip.right()
182 && y >= clip.y
183 && y < clip.bottom()
184 });
185 if prev_in_clip {
186 self.get_mut(x - 1, y).symbol.push(ch);
187 }
188 }
189 continue;
190 }
191
192 let in_clip = clip.map_or(true, |clip| {
193 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
194 });
195
196 if !in_clip {
197 x = x.saturating_add(char_width);
198 continue;
199 }
200
201 let cell = self.get_mut(x, y);
202 cell.set_char(ch);
203 cell.set_style(style);
204 cell.hyperlink = link.clone();
205
206 if char_width > 1 {
207 let next_x = x + 1;
208 if next_x < self.area.right() {
209 let next = self.get_mut(next_x, y);
210 next.symbol.clear();
211 next.style = style;
212 next.hyperlink = link.clone();
213 }
214 }
215
216 x = x.saturating_add(char_width);
217 }
218 }
219
220 pub fn set_char(&mut self, x: u32, y: u32, ch: char, style: Style) {
224 let in_clip = self.effective_clip().map_or(true, |clip| {
225 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
226 });
227 if !self.in_bounds(x, y) || !in_clip {
228 return;
229 }
230 let cell = self.get_mut(x, y);
231 cell.set_char(ch);
232 cell.set_style(style);
233 }
234
235 pub fn diff<'a>(&'a self, other: &'a Buffer) -> Vec<(u32, u32, &'a Cell)> {
241 let mut updates = Vec::new();
242 for y in self.area.y..self.area.bottom() {
243 for x in self.area.x..self.area.right() {
244 let cur = self.get(x, y);
245 let prev = other.get(x, y);
246 if cur != prev {
247 updates.push((x, y, cur));
248 }
249 }
250 }
251 updates
252 }
253
254 pub fn reset(&mut self) {
256 for cell in &mut self.content {
257 cell.reset();
258 }
259 self.clip_stack.clear();
260 self.raw_sequences.clear();
261 }
262
263 pub fn reset_with_bg(&mut self, bg: crate::style::Color) {
265 for cell in &mut self.content {
266 cell.reset();
267 cell.style.bg = Some(bg);
268 }
269 self.clip_stack.clear();
270 self.raw_sequences.clear();
271 }
272
273 pub fn resize(&mut self, area: Rect) {
278 self.area = area;
279 let size = area.area() as usize;
280 self.content.resize(size, Cell::default());
281 self.reset();
282 }
283}
284
285fn intersect_rects(a: Rect, b: Rect) -> Rect {
286 let x = a.x.max(b.x);
287 let y = a.y.max(b.y);
288 let right = a.right().min(b.right());
289 let bottom = a.bottom().min(b.bottom());
290 let width = right.saturating_sub(x);
291 let height = bottom.saturating_sub(y);
292 Rect::new(x, y, width, height)
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn clip_stack_intersects_nested_regions() {
301 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
302 buf.push_clip(Rect::new(1, 1, 6, 3));
303 buf.push_clip(Rect::new(4, 0, 6, 4));
304
305 buf.set_char(3, 2, 'x', Style::new());
306 buf.set_char(4, 2, 'y', Style::new());
307
308 assert_eq!(buf.get(3, 2).symbol, " ");
309 assert_eq!(buf.get(4, 2).symbol, "y");
310 }
311
312 #[test]
313 fn set_string_advances_even_when_clipped() {
314 let mut buf = Buffer::empty(Rect::new(0, 0, 8, 1));
315 buf.push_clip(Rect::new(2, 0, 6, 1));
316
317 buf.set_string(0, 0, "abcd", Style::new());
318
319 assert_eq!(buf.get(2, 0).symbol, "c");
320 assert_eq!(buf.get(3, 0).symbol, "d");
321 }
322
323 #[test]
324 fn pop_clip_restores_previous_clip() {
325 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
326 buf.push_clip(Rect::new(0, 0, 2, 1));
327 buf.push_clip(Rect::new(4, 0, 2, 1));
328
329 buf.set_char(1, 0, 'a', Style::new());
330 buf.pop_clip();
331 buf.set_char(1, 0, 'b', Style::new());
332
333 assert_eq!(buf.get(1, 0).symbol, "b");
334 }
335
336 #[test]
337 fn reset_clears_clip_stack() {
338 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
339 buf.push_clip(Rect::new(0, 0, 0, 0));
340 buf.reset();
341 buf.set_char(0, 0, 'z', Style::new());
342
343 assert_eq!(buf.get(0, 0).symbol, "z");
344 }
345}