Skip to main content

flywheel/buffer/
buffer.rs

1//! Buffer: A grid of cells representing the terminal screen.
2//!
3//! The buffer uses contiguous memory allocation for cache efficiency.
4//! Cells are stored in row-major order.
5
6use super::cell::{Cell, CellFlags, Rgb};
7
8/// A grid of cells representing the terminal screen.
9///
10/// The buffer stores cells in a contiguous `Vec` for cache efficiency.
11/// Access is in row-major order: `index = y * width + x`.
12///
13/// # Overflow Storage
14///
15/// Complex graphemes (>4 bytes) are stored in a separate arena (`Vec`).
16/// The cell contains an index into this overflow storage when the
17/// `OVERFLOW` flag is set.
18#[derive(Clone)]
19pub struct Buffer {
20    /// Contiguous cell storage (row-major order).
21    cells: Vec<Cell>,
22    /// Terminal width in columns.
23    width: u16,
24    /// Terminal height in rows.
25    height: u16,
26    /// Overflow arena for complex graphemes (O(1) indexed access).
27    overflow: Vec<String>,
28}
29
30impl Buffer {
31    /// Create a new buffer with the given dimensions.
32    ///
33    /// All cells are initialized to empty (space with default colors).
34    ///
35    /// # Panics
36    /// Panics if width or height is 0.
37    pub fn new(width: u16, height: u16) -> Self {
38        assert!(width > 0 && height > 0, "Buffer dimensions must be non-zero");
39        let size = (width as usize) * (height as usize);
40        Self {
41            cells: vec![Cell::EMPTY; size],
42            width,
43            height,
44            overflow: Vec::new(),
45        }
46    }
47
48    /// Get the buffer width.
49    #[inline]
50    pub const fn width(&self) -> u16 {
51        self.width
52    }
53
54    /// Get the buffer height.
55    #[inline]
56    pub const fn height(&self) -> u16 {
57        self.height
58    }
59
60    /// Get the total number of cells.
61    #[inline]
62    pub const fn len(&self) -> usize {
63        self.cells.len()
64    }
65
66    /// Check if the buffer is empty (should never be true after construction).
67    #[inline]
68    pub const fn is_empty(&self) -> bool {
69        self.cells.is_empty()
70    }
71
72    /// Get a reference to the underlying cell slice.
73    #[inline]
74    pub fn cells(&self) -> &[Cell] {
75        &self.cells
76    }
77
78    /// Get a mutable reference to the underlying cell slice.
79    #[inline]
80    pub fn cells_mut(&mut self) -> &mut [Cell] {
81        &mut self.cells
82    }
83
84    /// Convert (x, y) coordinates to a linear index.
85    ///
86    /// Returns `None` if coordinates are out of bounds.
87    #[inline]
88    pub const fn index_of(&self, x: u16, y: u16) -> Option<usize> {
89        if x < self.width && y < self.height {
90            Some((y as usize) * (self.width as usize) + (x as usize))
91        } else {
92            None
93        }
94    }
95
96    /// Convert a linear index to (x, y) coordinates.
97    #[inline]
98    #[allow(clippy::cast_possible_truncation)]
99    pub const fn coords_of(&self, index: usize) -> Option<(u16, u16)> {
100        if index < self.cells.len() {
101            let x = (index % (self.width as usize)) as u16;
102            let y = (index / (self.width as usize)) as u16;
103            Some((x, y))
104        } else {
105            None
106        }
107    }
108
109    /// Get a reference to a cell at (x, y).
110    ///
111    /// Returns `None` if coordinates are out of bounds.
112    #[inline]
113    pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
114        self.index_of(x, y).map(|i| &self.cells[i])
115    }
116
117    /// Get a mutable reference to a cell at (x, y).
118    ///
119    /// Returns `None` if coordinates are out of bounds.
120    #[inline]
121    pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
122        self.index_of(x, y).map(|i| &mut self.cells[i])
123    }
124
125    /// Set a cell at (x, y).
126    ///
127    /// Returns `false` if coordinates are out of bounds.
128    #[inline]
129    pub fn set(&mut self, x: u16, y: u16, cell: Cell) -> bool {
130        if let Some(idx) = self.index_of(x, y) {
131            self.cells[idx] = cell;
132            true
133        } else {
134            false
135        }
136    }
137
138    /// Set a grapheme at (x, y), handling overflow automatically.
139    ///
140    /// For wide characters (CJK), this also sets a continuation cell
141    /// at (x+1, y).
142    ///
143    /// Returns the display width of the grapheme, or 0 if out of bounds.
144    pub fn set_grapheme(&mut self, x: u16, y: u16, grapheme: &str, fg: Rgb, bg: Rgb) -> u8 {
145        let Some(idx) = self.index_of(x, y) else {
146            return 0;
147        };
148
149        let width = u8::try_from(unicode_width::UnicodeWidthStr::width(grapheme)).unwrap_or(1);
150
151        // Try to create an inline cell
152        let cell = if let Some(mut cell) = Cell::from_grapheme(grapheme) {
153            cell.set_fg(fg).set_bg(bg);
154            cell
155        } else {
156            // Overflow: store in arena (simple Vec indexing)
157            // Safety: Overflow arena is capped at u32::MAX entries (4 billion graphemes)
158            #[allow(clippy::cast_possible_truncation)]
159            let overflow_idx = self.overflow.len() as u32;
160            self.overflow.push(grapheme.to_string());
161            Cell::overflow(overflow_idx, width).with_fg(fg).with_bg(bg)
162        };
163
164        self.cells[idx] = cell;
165
166        // Handle wide characters (CJK)
167        if width == 2 {
168            if let Some(next_idx) = self.index_of(x + 1, y) {
169                self.cells[next_idx] = Cell::wide_continuation().with_bg(bg);
170            }
171        }
172
173        width
174    }
175
176    /// Get the grapheme at (x, y), including overflow lookup.
177    ///
178    /// Returns `None` if out of bounds or if it's a continuation cell.
179    pub fn get_grapheme(&self, x: u16, y: u16) -> Option<&str> {
180        let cell = self.get(x, y)?;
181
182        if cell.is_wide_continuation() {
183            return None;
184        }
185
186        if cell.flags().contains(CellFlags::OVERFLOW) {
187            let idx = cell.overflow_index()?;
188            self.overflow.get(idx as usize).map(String::as_str)
189        } else {
190            cell.grapheme()
191        }
192    }
193
194    /// Get an overflow grapheme by its index.
195    ///
196    /// This is used by the diffing engine when rendering overflow cells.
197    #[inline]
198    pub fn get_overflow(&self, index: u32) -> Option<&str> {
199        self.overflow.get(index as usize).map(String::as_str)
200    }
201
202    /// Fill a rectangular region with a cell.
203    pub fn fill_rect(&mut self, x: u16, y: u16, width: u16, height: u16, cell: Cell) {
204        for row in y..(y + height).min(self.height) {
205            for col in x..(x + width).min(self.width) {
206                if let Some(idx) = self.index_of(col, row) {
207                    self.cells[idx] = cell;
208                }
209            }
210        }
211    }
212
213    /// Clear the entire buffer (fill with empty cells).
214    pub fn clear(&mut self) {
215        self.cells.fill(Cell::EMPTY);
216        self.overflow.clear();
217    }
218
219    /// Clear a rectangular region.
220    pub fn clear_rect(&mut self, x: u16, y: u16, width: u16, height: u16) {
221        self.fill_rect(x, y, width, height, Cell::EMPTY);
222    }
223
224    /// Resize the buffer, preserving content where possible.
225    ///
226    /// New cells are initialized to empty.
227    pub fn resize(&mut self, new_width: u16, new_height: u16) {
228        if new_width == self.width && new_height == self.height {
229            return;
230        }
231
232        let new_size = (new_width as usize) * (new_height as usize);
233        let mut new_cells = vec![Cell::EMPTY; new_size];
234
235        // Copy existing content
236        let copy_width = self.width.min(new_width) as usize;
237        let copy_height = self.height.min(new_height) as usize;
238
239        for y in 0..copy_height {
240            let old_start = y * (self.width as usize);
241            let new_start = y * (new_width as usize);
242            new_cells[new_start..new_start + copy_width]
243                .copy_from_slice(&self.cells[old_start..old_start + copy_width]);
244        }
245
246        self.cells = new_cells;
247        self.width = new_width;
248        self.height = new_height;
249    }
250
251    /// Copy content from another buffer.
252    ///
253    /// The buffers must have the same dimensions.
254    pub fn copy_from(&mut self, other: &Self) {
255        debug_assert_eq!(self.width, other.width);
256        debug_assert_eq!(self.height, other.height);
257        self.cells.copy_from_slice(&other.cells);
258        self.overflow.clone_from(&other.overflow);
259    }
260
261    /// Swap the contents of two buffers.
262    ///
263    /// This is O(1) - just pointer swaps.
264    pub const fn swap(&mut self, other: &mut Self) {
265        std::mem::swap(&mut self.cells, &mut other.cells);
266        std::mem::swap(&mut self.width, &mut other.width);
267        std::mem::swap(&mut self.height, &mut other.height);
268        std::mem::swap(&mut self.overflow, &mut other.overflow);
269    }
270
271    /// Get an iterator over rows.
272    pub fn rows(&self) -> impl Iterator<Item = &[Cell]> {
273        self.cells.chunks(self.width as usize)
274    }
275
276    /// Get a mutable iterator over rows.
277    pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [Cell]> {
278        self.cells.chunks_mut(self.width as usize)
279    }
280
281    /// Get memory usage in bytes (approximate).
282    pub fn memory_usage(&self) -> usize {
283        let cells_size = self.cells.len() * std::mem::size_of::<Cell>();
284        let overflow_size: usize = self.overflow.iter().map(|s| s.len() + 32).sum();
285        cells_size + overflow_size + std::mem::size_of::<Self>()
286    }
287}
288
289impl std::fmt::Debug for Buffer {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        f.debug_struct("Buffer")
292            .field("width", &self.width)
293            .field("height", &self.height)
294            .field("overflow_count", &self.overflow.len())
295            .field("memory_bytes", &self.memory_usage())
296            .finish_non_exhaustive()
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn test_buffer_new() {
306        let buffer = Buffer::new(80, 24);
307        assert_eq!(buffer.width(), 80);
308        assert_eq!(buffer.height(), 24);
309        assert_eq!(buffer.len(), 80 * 24);
310    }
311
312    #[test]
313    #[should_panic]
314    fn test_buffer_zero_width() {
315        Buffer::new(0, 24);
316    }
317
318    #[test]
319    fn test_buffer_get_set() {
320        let mut buffer = Buffer::new(80, 24);
321        let cell = Cell::new('X');
322        assert!(buffer.set(5, 10, cell));
323        assert_eq!(buffer.get(5, 10).unwrap().grapheme(), Some("X"));
324    }
325
326    #[test]
327    fn test_buffer_bounds() {
328        let buffer = Buffer::new(80, 24);
329        assert!(buffer.get(79, 23).is_some());
330        assert!(buffer.get(80, 23).is_none());
331        assert!(buffer.get(79, 24).is_none());
332    }
333
334    #[test]
335    fn test_buffer_index_coords() {
336        let buffer = Buffer::new(80, 24);
337        assert_eq!(buffer.index_of(5, 10), Some(10 * 80 + 5));
338        assert_eq!(buffer.coords_of(10 * 80 + 5), Some((5, 10)));
339    }
340
341    #[test]
342    fn test_buffer_set_grapheme() {
343        let mut buffer = Buffer::new(80, 24);
344
345        // ASCII
346        let width = buffer.set_grapheme(0, 0, "A", Rgb::WHITE, Rgb::BLACK);
347        assert_eq!(width, 1);
348        assert_eq!(buffer.get_grapheme(0, 0), Some("A"));
349
350        // CJK (wide character)
351        let width = buffer.set_grapheme(5, 0, "ๆ—ฅ", Rgb::WHITE, Rgb::BLACK);
352        assert_eq!(width, 2);
353        assert_eq!(buffer.get_grapheme(5, 0), Some("ๆ—ฅ"));
354        // Continuation cell should return None
355        assert!(buffer.get(6, 0).unwrap().is_wide_continuation());
356    }
357
358    #[test]
359    fn test_buffer_overflow() {
360        let mut buffer = Buffer::new(80, 24);
361
362        // Complex emoji (> 4 bytes UTF-8)
363        let emoji = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ";
364        let width = buffer.set_grapheme(0, 0, emoji, Rgb::WHITE, Rgb::BLACK);
365        assert!(width > 0);
366        assert!(buffer.get(0, 0).unwrap().is_overflow());
367        assert_eq!(buffer.get_grapheme(0, 0), Some(emoji));
368    }
369
370    #[test]
371    fn test_buffer_fill_rect() {
372        let mut buffer = Buffer::new(80, 24);
373        let cell = Cell::new('X');
374        buffer.fill_rect(10, 5, 3, 2, cell);
375
376        assert_eq!(buffer.get(10, 5).unwrap().grapheme(), Some("X"));
377        assert_eq!(buffer.get(11, 5).unwrap().grapheme(), Some("X"));
378        assert_eq!(buffer.get(12, 5).unwrap().grapheme(), Some("X"));
379        assert_eq!(buffer.get(10, 6).unwrap().grapheme(), Some("X"));
380        assert_eq!(buffer.get(9, 5).unwrap().grapheme(), Some(" ")); // Outside rect
381    }
382
383    #[test]
384    fn test_buffer_clear() {
385        let mut buffer = Buffer::new(80, 24);
386        buffer.set(5, 5, Cell::new('X'));
387        buffer.clear();
388        assert_eq!(buffer.get(5, 5), Some(&Cell::EMPTY));
389    }
390
391    #[test]
392    fn test_buffer_resize() {
393        let mut buffer = Buffer::new(80, 24);
394        buffer.set(5, 5, Cell::new('X'));
395
396        buffer.resize(100, 30);
397        assert_eq!(buffer.width(), 100);
398        assert_eq!(buffer.height(), 30);
399        assert_eq!(buffer.get(5, 5).unwrap().grapheme(), Some("X")); // Preserved
400
401        buffer.resize(10, 10);
402        assert_eq!(buffer.get(5, 5).unwrap().grapheme(), Some("X")); // Still preserved
403        assert!(buffer.get(15, 15).is_none()); // Out of bounds now
404    }
405
406    #[test]
407    fn test_buffer_swap() {
408        let mut a = Buffer::new(80, 24);
409        let mut b = Buffer::new(80, 24);
410
411        a.set(0, 0, Cell::new('A'));
412        b.set(0, 0, Cell::new('B'));
413
414        a.swap(&mut b);
415
416        assert_eq!(a.get(0, 0).unwrap().grapheme(), Some("B"));
417        assert_eq!(b.get(0, 0).unwrap().grapheme(), Some("A"));
418    }
419
420    #[test]
421    fn test_buffer_memory_usage() {
422        let buffer = Buffer::new(200, 50);
423        let usage = buffer.memory_usage();
424        // 200 * 50 * 16 = 160,000 bytes for cells, plus overhead
425        assert!(usage >= 160_000);
426        assert!(usage < 200_000); // Shouldn't be too much more
427    }
428}