Skip to main content

uncurses/buffer/
mod.rs

1//! Cell-grid storage and surface traits.
2//!
3//! This module provides [`Buffer`], row helpers, rectangular views, and
4//! owned [`Window`]s for working with terminal cells. Use it when code
5//! needs an off-screen grid, a clipped view of an existing grid, or a
6//! common [`Surface`] / [`SurfaceMut`] API that drawing code can target.
7//!
8//! ## Surface trait family
9//!
10//! The surface traits describe terminal-cell grids in layers:
11//!
12//! - [`Bounded`] exposes the rectangular extent of a grid.
13//! - [`Surface`] adds read-only cell access and the default
14//!   [`draw`](Surface::draw) blit operation.
15//! - [`SurfaceMut`] adds mutation, clearing, rectangular fills, and
16//!   terminal-style insert/delete operations.
17//!
18//! Code written against [`Surface`] or [`SurfaceMut`] can operate on a
19//! [`Buffer`], [`TextBuffer`], [`Window`], [`View`], or
20//! [`Screen`](crate::screen::Screen) without caring where the cells are
21//! stored. The shared contract is a rectangular coordinate space of
22//! [`Cell`] values addressed by
23//! [`Position`].
24//!
25//! ```rust,ignore
26//! use uncurses::buffer::{Buffer, Surface, SurfaceMut};
27//! use uncurses::cell::Cell;
28//! use uncurses::layout::Position;
29//!
30//! let mut buf = Buffer::new(4, 2);
31//! buf.set_cell(Position::new(0, 0), &Cell::new("x", 1));
32//! assert_eq!(buf.cell(Position::new(0, 0)).unwrap().content(), "x");
33//! ```
34//!
35//! ## Buffer storage
36//!
37//! [`Buffer`] stores a fixed-size grid in one row-major `Vec<Cell>`.
38//! Column `x` and row `y` map to `cells[y * width + x]`, so each row is a
39//! contiguous slice and cloning the buffer requires only one allocation.
40//! Resizing allocates a new row-major backing store, copies the
41//! intersection of the old and new extents, and fills new slots with
42//! [`Cell::BLANK`](crate::cell::Cell::BLANK).
43//!
44//! ```text
45//!          col:  0   1   2   3
46//!              ┌───┬───┬───────┬───┐
47//! row 0 (y=0)  │ H │ i │ 日    │ ! │
48//!              └───┴───┴───────┴───┘
49//!                         ▲
50//!                         └─ wide primary at x=2 covers columns 2 and 3
51//! ```
52//!
53//! ## Wide cells and drawing
54//!
55//! A two-column grapheme is represented by a wide primary cell followed by
56//! a continuation placeholder in the next column. [`Buffer::set`] writes
57//! that placeholder automatically, blanks stale halves when overwriting an
58//! existing wide cell, and replaces a wide cell with a blank when it would
59//! not fit at the end of a row. The default [`Surface::draw`] implementation
60//! preserves the same invariant when blitting between surfaces: orphan
61//! continuations and clipped wide primaries are emitted as blanks.
62
63mod ops;
64mod surface;
65mod view;
66mod window;
67
68mod line;
69#[cfg(test)]
70mod tests;
71mod text_buffer;
72
73pub(crate) use line::fill_line_into;
74pub use line::{Line, blank_line, fill_line};
75pub use surface::{Bounded, Surface, SurfaceMut};
76pub use text_buffer::TextBuffer;
77pub use view::View;
78pub use window::Window;
79
80use crate::cell::Cell;
81use crate::layout::{Position, Rect, overruns};
82
83/// Off-screen storage for a rectangular grid of terminal cells.
84///
85/// A `Buffer` owns `width * height` [`Cell`] values in row-major order.
86/// Row `y` lives at `cells[y * width..(y + 1) * width]`, and column `x`
87/// within that row is addressed by [`Position::new`](crate::layout::Position::new)
88/// through the [`Surface`] and [`SurfaceMut`] APIs.
89///
90/// Use a buffer when rendering into memory before presenting the result to
91/// another surface, when composing with [`Window`]s, or when tests need a
92/// deterministic cell grid. Writes outside the buffer bounds are ignored;
93/// reads outside the bounds return `None`.
94///
95/// A wide primary [`Cell`] owns one continuation cell per additional column,
96/// for a total of `width - 1` continuations. Prefer [`Buffer::set`] or
97/// [`SurfaceMut::set_cell`] for writes so that continuation slots are kept
98/// consistent.
99#[derive(Debug, Clone)]
100pub struct Buffer {
101    cells: Vec<Cell>,
102    width: u16,
103    height: u16,
104}
105
106impl Buffer {
107    /// Create a new blank buffer.
108    ///
109    /// The returned buffer has `width * height` cells, all initialized to
110    /// [`Cell::BLANK`]. `width` and `height` are measured in terminal cell
111    /// columns and rows, not bytes or grapheme clusters.
112    ///
113    /// # Parameters
114    ///
115    /// - `width`: number of columns in each row.
116    /// - `height`: number of rows.
117    ///
118    /// # Returns
119    ///
120    /// A row-major [`Buffer`] with bounds `Rect::new(0, 0, width, height)`.
121    ///
122    /// # Panics
123    ///
124    /// Panics if `width * height` cannot be allocated.
125    ///
126    /// # Usage notes
127    ///
128    /// Zero-sized dimensions are valid. Accessors will then return empty
129    /// rows or `None` according to the resulting bounds.
130    pub fn new(width: u16, height: u16) -> Self {
131        let total = (width as usize) * (height as usize);
132        let cells = vec![Cell::BLANK; total];
133        Self {
134            cells,
135            width,
136            height,
137        }
138    }
139
140    /// Return the buffer width in terminal cell columns.
141    ///
142    /// # Returns
143    ///
144    /// The number of columns in each row.
145    ///
146    /// # Panics
147    ///
148    /// Never panics.
149    ///
150    /// # Usage notes
151    ///
152    /// This is the inherent accessor for the stored width. The [`Bounded`]
153    /// implementation reports the same value through [`Bounded::width`].
154    pub fn width(&self) -> u16 {
155        self.width
156    }
157
158    /// Return the buffer height in terminal cell rows.
159    ///
160    /// # Returns
161    ///
162    /// The number of rows in the buffer.
163    ///
164    /// # Panics
165    ///
166    /// Never panics.
167    ///
168    /// # Usage notes
169    ///
170    /// This is the inherent accessor for the stored height. The [`Bounded`]
171    /// implementation reports the same value through [`Bounded::height`].
172    pub fn height(&self) -> u16 {
173        self.height
174    }
175
176    /// Borrow one row as a contiguous slice.
177    ///
178    /// # Parameters
179    ///
180    /// - `y`: zero-based row index in buffer coordinates.
181    ///
182    /// # Returns
183    ///
184    /// `Some(&[Cell])` with length [`Buffer::width`] when `y` is inside the
185    /// buffer, or `None` when `y >= height`.
186    ///
187    /// # Panics
188    ///
189    /// Never panics.
190    ///
191    /// # Usage notes
192    ///
193    /// The returned slice may contain continuation cells that belong to
194    /// wide primaries earlier in the same row. Do not assume every slice
195    /// element starts an independent grapheme.
196    #[inline]
197    pub fn line(&self, y: u16) -> Option<&[Cell]> {
198        if y >= self.height {
199            return None;
200        }
201        let w = self.width as usize;
202        let start = (y as usize) * w;
203        Some(&self.cells[start..start + w])
204    }
205
206    /// Mutably borrow one row as a contiguous slice.
207    ///
208    /// # Parameters
209    ///
210    /// - `y`: zero-based row index in buffer coordinates.
211    ///
212    /// # Returns
213    ///
214    /// `Some(&mut [Cell])` with length [`Buffer::width`] when `y` is inside
215    /// the buffer, or `None` when `y >= height`.
216    ///
217    /// # Panics
218    ///
219    /// Never panics.
220    ///
221    /// # Usage notes
222    ///
223    /// Direct slice mutation bypasses the wide-cell accounting performed by
224    /// [`Buffer::set`]. It is appropriate for whole-row helpers that manage
225    /// continuations themselves; prefer [`Buffer::set`] for ordinary writes.
226    #[inline]
227    pub fn line_mut(&mut self, y: u16) -> Option<&mut [Cell]> {
228        if y >= self.height {
229            return None;
230        }
231        let w = self.width as usize;
232        let start = (y as usize) * w;
233        Some(&mut self.cells[start..start + w])
234    }
235
236    /// Swap the contents of rows `a` and `b` in place.
237    fn swap_rows(&mut self, a: usize, b: usize) {
238        if a == b {
239            return;
240        }
241        let w = self.width as usize;
242        if w == 0 {
243            return;
244        }
245        let (lo, hi) = if a < b { (a, b) } else { (b, a) };
246        let split = hi * w;
247        let (left, right) = self.cells.split_at_mut(split);
248        left[lo * w..(lo + 1) * w].swap_with_slice(&mut right[..w]);
249    }
250
251    /// Borrow one cell mutably.
252    ///
253    /// # Parameters
254    ///
255    /// - `pos`: zero-based cell coordinate in buffer coordinates.
256    ///
257    /// # Returns
258    ///
259    /// `Some(&mut Cell)` for an in-bounds position, or `None` when `pos`
260    /// lies outside the buffer.
261    ///
262    /// # Panics
263    ///
264    /// Never panics.
265    ///
266    /// # Usage notes
267    ///
268    /// Mutating a cell through this handle does not update neighboring
269    /// continuation cells. Do not change a cell from narrow to wide, wide to
270    /// narrow, or continuation to primary through this handle; use
271    /// [`Buffer::set`] or [`SurfaceMut::set_cell`] when the cell width may
272    /// change.
273    pub fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
274        if pos.y >= self.height || pos.x >= self.width {
275            return None;
276        }
277        let w = self.width as usize;
278        Some(&mut self.cells[(pos.y as usize) * w + (pos.x as usize)])
279    }
280
281    /// Set a cell at a position while preserving wide-cell invariants.
282    ///
283    /// # Parameters
284    ///
285    /// - `pos`: zero-based destination coordinate. Any type convertible into
286    ///   [`Position`] is accepted.
287    /// - `cell`: cell to clone into the destination.
288    ///
289    /// # Behavior
290    ///
291    /// In-bounds narrow writes replace exactly one column, blanking any stale
292    /// wide-cell halves they overwrite. In-bounds wide writes place `cell` at
293    /// `pos` and write continuation placeholders into the columns it covers.
294    /// If a wide cell would extend past the right edge of the row, the
295    /// destination column is set to [`Cell::BLANK`] instead.
296    ///
297    /// Out-of-bounds writes are ignored.
298    ///
299    /// # Panics
300    ///
301    /// Never panics.
302    ///
303    /// # Usage notes
304    ///
305    /// A continuation is placed by the wide cell that owns it, as part of
306    /// that cell's own write, so a continuation passed here is ignored and
307    /// the column keeps the value its owner gave it.
308    ///
309    /// This is the implementation behind [`SurfaceMut::set_cell`] for
310    /// `Buffer`. Use it instead of [`Buffer::cell_mut`] whenever the write
311    /// might affect the width or role of neighboring cells.
312    pub fn set(&mut self, pos: impl Into<Position>, cell: &Cell) {
313        let pos = pos.into();
314        let y = pos.y as usize;
315        let x = pos.x as usize;
316        let width = self.width as usize;
317
318        if pos.y >= self.height || x >= width {
319            return;
320        }
321
322        let line = &mut self.cells[y * width..(y + 1) * width];
323
324        // A continuation is placed by the wide cell to its left, as part of
325        // that cell's own write, so one arriving here has nothing to say
326        // that the owning write did not already say. Ignoring it keeps the
327        // column exactly as its owner left it, which is what a caller
328        // mirroring a buffer cell by cell wants: it walks continuations
329        // because they are in the grid, not because it means to move them.
330        //
331        // Ignoring is also the only safe answer. Storing the cell would let
332        // a column carry a style its owner never had. Blanking it would put
333        // a one-column cell where the owner accounts for two, so the row
334        // would emit more columns than it holds. Refusing loudly would make
335        // a redundant write in an otherwise correct mirror fatal.
336        if cell.is_continuation() {
337            return;
338        }
339
340        // If we're overwriting a wide cell's continuation, blank the primary
341        // cell that owns it, so the wide grapheme does not survive with one
342        // half replaced. The branch above already returned for an incoming
343        // continuation, so the cell arriving here is narrow or wide.
344        if x > 0 && line[x].is_continuation() {
345            // Walk backward to find the primary cell
346            let mut pc = x - 1;
347            while pc > 0 && line[pc].is_continuation() {
348                pc -= 1;
349            }
350            // Verify we found a wide primary; only Wide cells own
351            // the continuation slot to their right.
352            if line[pc].is_wide() {
353                let pw = line[pc].width() as usize;
354                let end = (pc + pw).min(width);
355                // Preserve the broken wide cell's bg/attributes on the
356                // blanks so clearing it doesn't punch a hole in a colored
357                // background.
358                let blank = Cell::BLANK.style(line[pc].style.clone());
359                line[pc..end].fill(blank);
360            } else if line[pc].is_continuation() {
361                // Buffer is corrupted — just blank the single cell
362                line[x] = Cell::BLANK;
363            }
364        }
365
366        // If we're overwriting the primary cell of a wide char, blank continuations
367        if line[x].is_wide() {
368            let w = line[x].width() as usize;
369            let end = (x + w).min(width);
370            let blank = Cell::BLANK.style(line[x].style.clone());
371            line[x + 1..end].fill(blank);
372        }
373
374        let cell_width = cell.width() as usize;
375
376        // If the new cell is wide, blank cells it will cover
377        if cell.is_wide() {
378            // Truncate at end of line: if the wide cell doesn't fit, replace
379            // it with a blank that keeps the wide cell's bg/attributes.
380            //
381            // This has to be settled before any continuation is written. A
382            // cell can claim more columns than the row has left, and laying
383            // the continuations down first would leave the row holding the
384            // tail of a cell whose primary this branch then replaces.
385            if x + cell_width > width {
386                line[x] = Cell::BLANK.style(cell.style.clone());
387                return;
388            }
389
390            for i in x + 1..x + cell_width {
391                // If we'd overwrite a wide cell's primary, blank its continuations
392                if line[i].is_wide() {
393                    let w = line[i].width() as usize;
394                    let end = (i + w).min(width);
395                    let blank = Cell::BLANK.style(line[i].style.clone());
396                    line[i + 1..end].fill(blank);
397                }
398                // Continuations inherit the wide primary's style so the
399                // cell's bg/attributes are coherent across both columns.
400                line[i] = Cell::CONTINUATION.style(cell.style.clone());
401            }
402        }
403
404        line[x] = cell.clone();
405    }
406
407    /// Resize the buffer, preserving the top-left intersection.
408    ///
409    /// # Parameters
410    ///
411    /// - `width`: new row width in terminal cell columns.
412    /// - `height`: new height in terminal cell rows.
413    ///
414    /// # Behavior
415    ///
416    /// Cells inside the intersection of the old and new bounds keep their
417    /// row and column. Newly exposed cells are filled with [`Cell::BLANK`],
418    /// and cells outside the new bounds are discarded.
419    ///
420    /// # Panics
421    ///
422    /// Panics if the resized backing store cannot be allocated.
423    ///
424    /// # Usage notes
425    ///
426    /// Resizing copies cells structurally and does not reflow wide cells. If
427    /// the new right edge cuts through a wide grapheme, the copied
428    /// continuation or primary remains as stored until later writes or draws
429    /// normalize the affected edge.
430    pub fn resize(&mut self, width: u16, height: u16) {
431        if width == self.width && height == self.height {
432            return;
433        }
434
435        let new_total = (width as usize) * (height as usize);
436        let mut new_cells = vec![Cell::BLANK; new_total];
437
438        let copy_w = width.min(self.width) as usize;
439        let copy_h = height.min(self.height) as usize;
440        let old_w = self.width as usize;
441        let new_w = width as usize;
442        for y in 0..copy_h {
443            let src = y * old_w;
444            let dst = y * new_w;
445            new_cells[dst..dst + copy_w].clone_from_slice(&self.cells[src..src + copy_w]);
446        }
447
448        self.cells = new_cells;
449        self.width = width;
450        self.height = height;
451    }
452}
453
454impl Bounded for Buffer {
455    fn bounds(&self) -> Rect {
456        Rect::new(0, 0, self.width, self.height)
457    }
458}
459
460impl Surface for Buffer {
461    fn cell(&self, pos: Position) -> Option<&Cell> {
462        if pos.y >= self.height || pos.x >= self.width {
463            return None;
464        }
465        let w = self.width as usize;
466        Some(&self.cells[(pos.y as usize) * w + (pos.x as usize)])
467    }
468}
469
470impl SurfaceMut for Buffer {
471    fn set_cell(&mut self, pos: Position, cell: &Cell) {
472        self.set(pos, cell);
473    }
474
475    fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
476        Buffer::cell_mut(self, pos)
477    }
478
479    /// Fill the clipped intersection of `rect` with `cell`. For
480    /// width-1 fills this collapses the trait default's per-cell
481    /// `set_cell` loop into one `slice::fill` per row, with explicit
482    /// wide-cell cleanup around the fill so any cell overlapping the
483    /// region leaves no orphan primary or continuation behind. A cell
484    /// wide enough can hold the whole region, in which case the cleanup
485    /// reaches past both edges of the fill. Wide fills (`cell.width() > 1`)
486    /// stay on the stepped `set_cell` path so primary/continuation
487    /// pairing and the trailing-partial-slot blank are placed by the
488    /// same wide-cell handling that `set` already implements. Partial slots
489    /// retain the fill style. Cleanup outside the rectangle retains the
490    /// old primary's style.
491    fn fill_rect(&mut self, rect: Rect, cell: &Cell) {
492        let clipped = self.bounds().intersection(rect);
493        if clipped.is_empty() {
494            return;
495        }
496
497        let step = (cell.width() as u16).max(1);
498        if step > 1 {
499            let blank = Cell::BLANK.style(cell.style.clone());
500            // Stepped wide-cell fill: identical to the trait default.
501            // Inlined here so the SurfaceMut::fill_rect dispatch goes
502            // through this impl in both arms.
503            for y in clipped.top()..clipped.bottom() {
504                let mut x = clipped.left();
505                while !overruns(x, step, clipped.right()) {
506                    self.set(Position::new(x, y), cell);
507                    x += step;
508                }
509                while x < clipped.right() {
510                    self.set(Position::new(x, y), &blank);
511                    x += 1;
512                }
513            }
514            return;
515        }
516
517        let lo = clipped.left() as usize;
518        let hi = clipped.right() as usize;
519        let row_width = self.width as usize;
520
521        for y in clipped.top()..clipped.bottom() {
522            let row_start = (y as usize) * row_width;
523            let line = &mut self.cells[row_start..row_start + row_width];
524
525            // Left-edge straddle: a wide cell whose primary sits
526            // before `lo` and whose continuation falls at `lo`. Walk
527            // back to the primary and blank it (and any siblings
528            // outside the fill region) before the bulk fill so the
529            // primary isn't left dangling once its continuations get
530            // overwritten.
531            if lo > 0 && line[lo].is_continuation() {
532                let mut p = lo;
533                while p > 0 && line[p].is_continuation() {
534                    p -= 1;
535                }
536                if !line[p].is_continuation() {
537                    let pw = line[p].width() as usize;
538                    let blank = Cell::BLANK.style(line[p].style.clone());
539                    let end = (p + pw).min(lo);
540                    line[p..end].fill(blank.clone());
541                    // The same cell can also reach past `hi`, which is what
542                    // happens when the fill lands wholly inside it. Those
543                    // columns just lost the primary that owned them, so
544                    // they cannot stay continuations. The right-edge pass
545                    // below will not reach them: it walks back only as far
546                    // as `lo`, and this primary sits before that.
547                    if p + pw > hi {
548                        let tail = (p + pw).min(row_width);
549                        line[hi..tail].fill(blank);
550                    }
551                }
552            }
553
554            // Right-edge straddle: a wide cell whose primary sits at
555            // some `p` in `[lo, hi)` and whose continuations extend
556            // past `hi - 1`. The primary itself will be overwritten by
557            // the bulk fill, but the orphan continuations at
558            // `[hi, p + pw)` need explicit blanking.
559            if hi < row_width && line[hi].is_continuation() {
560                let mut p = hi - 1;
561                while p > lo && line[p].is_continuation() {
562                    p -= 1;
563                }
564                if !line[p].is_continuation() && p + (line[p].width() as usize) > hi {
565                    let pw = line[p].width() as usize;
566                    let end = (p + pw).min(row_width);
567                    let blank = Cell::BLANK.style(line[p].style.clone());
568                    line[hi..end].fill(blank);
569                }
570            }
571
572            line[lo..hi].fill(cell.clone());
573        }
574    }
575
576    fn insert_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
577        Buffer::insert_lines(self, y, n, bounds_bottom, fill);
578    }
579
580    fn delete_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
581        Buffer::delete_lines(self, y, n, bounds_bottom, fill);
582    }
583
584    fn insert_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
585        Buffer::insert_cells(self, pos, n, bounds_right, fill);
586    }
587
588    fn delete_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
589        Buffer::delete_cells(self, pos, n, bounds_right, fill);
590    }
591}