Skip to main content

uncurses/buffer/
surface.rs

1//! Cell-grid surface traits.
2//!
3//! Buffer-like types ([`Buffer`](super::Buffer), [`Window`](super::Window),
4//! [`View`](super::View), [`TextBuffer`](super::TextBuffer), and
5//! [`Screen`](crate::screen::Screen)) all expose a rectangular region of
6//! [`Cell`]s. This module defines the traits that let drawing code use
7//! those regions without depending on a concrete storage type.
8//!
9//! ## Trait layers
10//!
11//! | trait          | adds                            | required methods                         |
12//! |----------------|---------------------------------|------------------------------------------|
13//! | [`Bounded`]    | rectangular extent              | [`bounds`](Bounded::bounds)              |
14//! | [`Surface`]    | read-only cell access           | [`cell`](Surface::cell)                  |
15//! | [`SurfaceMut`] | writes and terminal operations  | [`set_cell`](SurfaceMut::set_cell), [`cell_mut`](SurfaceMut::cell_mut) |
16//!
17//! [`Surface`] extends [`Bounded`], and [`SurfaceMut`] extends
18//! [`Surface`]. Default methods supply common behavior such as
19//! [`Surface::draw`], [`SurfaceMut::fill_rect`],
20//! [`SurfaceMut::insert_lines`], and [`SurfaceMut::delete_cells`].
21//! Implementations may override defaults for storage-specific fast paths
22//! as long as they preserve the same visible semantics.
23//!
24//! ## Coordinates and bounds
25//!
26//! Coordinates are terminal-cell coordinates in the implementer's own
27//! coordinate space. A surface reports the rectangle where reads and writes
28//! are meaningful through [`Bounded::bounds`]; attempts outside that region
29//! should either return `None` or have no effect, as documented by each
30//! method.
31//!
32//! ```text
33//! Rect::new(2, 1, 4, 2)
34//!
35//! y=1      x=2 x=3 x=4 x=5
36//!        ┌───┬───┬───┬───┐
37//!        │   │   │   │   │
38//! y=2    ├───┼───┼───┼───┤
39//!        │   │   │   │   │
40//!        └───┴───┴───┴───┘
41//! ```
42//!
43//! ## Wide-cell invariants
44//!
45//! A wide grapheme occupies a primary cell plus a continuation placeholder
46//! in the following column. Methods that copy or fill cells step by the
47//! primary cell's display width and substitute blanks when a wide grapheme
48//! would be split by a source slice, a destination edge, or a fill region.
49
50use crate::cell::Cell;
51use crate::layout::{Position, Rect, overruns};
52
53/// A value with a rectangular extent in terminal-cell coordinates.
54///
55/// `Bounded` is the base trait for readable and writable surfaces. It does
56/// not imply that cells can be inspected or modified; it only describes the
57/// region where a value exists in its own coordinate space.
58///
59/// Implement this trait for types that can answer "what rectangle do I
60/// cover?" and then add [`Surface`] or [`SurfaceMut`] when cell access is
61/// available.
62pub trait Bounded {
63    /// Return the valid region in this value's own coordinate space.
64    ///
65    /// # Returns
66    ///
67    /// A [`Rect`] whose `x`/`y` form the top-left coordinate and whose
68    /// `width`/`height` form the extent in terminal cells.
69    ///
70    /// # Panics
71    ///
72    /// Implementations should not panic.
73    ///
74    /// # Usage notes
75    ///
76    /// For most storage-backed surfaces the origin is `(0, 0)`. Clipping
77    /// adaptors may report non-zero origins when they expose a sub-rectangle
78    /// in the wrapped surface's coordinate space.
79    fn bounds(&self) -> Rect;
80
81    /// Return the width of [`Self::bounds`] in terminal cell columns.
82    ///
83    /// # Returns
84    ///
85    /// `self.bounds().width`.
86    ///
87    /// # Panics
88    ///
89    /// Never panics unless [`Self::bounds`] panics.
90    ///
91    /// # Usage notes
92    ///
93    /// This is a convenience method; override only if computing full bounds
94    /// is meaningfully more expensive than returning the width.
95    fn width(&self) -> u16 {
96        self.bounds().width
97    }
98
99    /// Return the height of [`Self::bounds`] in terminal cell rows.
100    ///
101    /// # Returns
102    ///
103    /// `self.bounds().height`.
104    ///
105    /// # Panics
106    ///
107    /// Never panics unless [`Self::bounds`] panics.
108    ///
109    /// # Usage notes
110    ///
111    /// This is a convenience method; override only if computing full bounds
112    /// is meaningfully more expensive than returning the height.
113    fn height(&self) -> u16 {
114        self.bounds().height
115    }
116
117    /// Test whether a position lies inside [`Self::bounds`].
118    ///
119    /// # Parameters
120    ///
121    /// - `pos`: coordinate in this value's coordinate space.
122    ///
123    /// # Returns
124    ///
125    /// `true` when `pos` is inside the half-open rectangle described by
126    /// [`Self::bounds`], otherwise `false`.
127    ///
128    /// # Panics
129    ///
130    /// Never panics unless [`Self::bounds`] panics.
131    ///
132    /// # Usage notes
133    ///
134    /// Surfaces commonly use this to clip reads and writes before touching
135    /// their backing storage.
136    fn contains(&self, pos: Position) -> bool {
137        self.bounds().contains(pos)
138    }
139}
140
141/// A rectangular cell grid that can be read.
142///
143/// `Surface` adds immutable cell access to [`Bounded`]. It is the trait to
144/// use for render sources, snapshots, and helpers that only need to inspect
145/// cells or copy them into another surface.
146///
147/// The provided [`Surface::draw`] method is intentionally conservative
148/// about wide cells: it avoids producing orphan continuation columns and
149/// blanks wide primaries that would be split by source or destination
150/// bounds.
151pub trait Surface: Bounded {
152    /// Read the cell at a position.
153    ///
154    /// # Parameters
155    ///
156    /// - `pos`: coordinate in this surface's coordinate space.
157    ///
158    /// # Returns
159    ///
160    /// `Some(&Cell)` when `pos` is readable, or `None` when it is outside
161    /// [`Bounded::bounds`] or otherwise unavailable.
162    ///
163    /// # Panics
164    ///
165    /// Implementations should not panic for out-of-bounds positions.
166    ///
167    /// # Usage notes
168    ///
169    /// A returned cell may be a wide primary or a continuation placeholder.
170    /// Callers that walk rows should advance by `cell.width().max(1)` for
171    /// primary cells and handle continuations explicitly.
172    fn cell(&self, pos: Position) -> Option<&Cell>;
173
174    /// Copy `self`'s cells into `target`, mapping the top-left of
175    /// `self.bounds()` to `at` in target coordinates.
176    ///
177    /// # Parameters
178    ///
179    /// - `target`: writable destination surface.
180    /// - `at`: destination coordinate for the source bounds' top-left
181    ///   corner.
182    ///
183    /// # Behavior
184    ///
185    /// The default walks the source row by row and emits one
186    /// [`SurfaceMut::set_cell`] call per source cell. Wide-cell
187    /// semantics are preserved:
188    ///
189    /// * Wide-primary cells advance the column cursor by their full
190    ///   `width` so the source's continuation cells are not written
191    ///   over independently.
192    /// * If the source has been sliced through the middle of a wide
193    ///   grapheme (a leading continuation with no primary in view, or
194    ///   a trailing primary with no room for its continuation), the
195    ///   default substitutes a blank rather than emitting an orphan
196    ///   half. The same blank substitution applies when either horizontal
197    ///   edge of `target` splits a wide cell.
198    ///
199    /// Implementations may override for a faster path (e.g. bulk line
200    /// copies), but must preserve the wide-cell invariants above.
201    ///
202    /// # Panics
203    ///
204    /// The default implementation does not panic unless the destination's
205    /// [`SurfaceMut::set_cell`] implementation panics.
206    ///
207    /// # Usage notes
208    ///
209    /// Destination clipping is delegated to `target`. Cells whose
210    /// destination coordinate lies outside the target bounds are passed to
211    /// `set_cell`, which is expected to ignore them.
212    fn draw<T: SurfaceMut + ?Sized>(&self, target: &mut T, at: Position) {
213        let b = self.bounds();
214        let tb = target.bounds();
215        let t_right = tb.x.saturating_add(tb.width);
216        for dy in 0..b.height {
217            let dst_y = at.y.saturating_add(dy);
218            let mut dx: u16 = 0;
219            while dx < b.width {
220                let src = Position::new(b.x + dx, b.y + dy);
221                let dst = Position::new(at.x.saturating_add(dx), dst_y);
222                let Some(cell) = self.cell(src) else {
223                    dx += 1;
224                    continue;
225                };
226
227                // Leading continuation with no primary in view: the
228                // source's left edge sliced a wide grapheme in half.
229                // Substitute a blank to avoid writing an orphan
230                // continuation into target.
231                if cell.is_continuation() {
232                    target.set_cell(dst, &Cell::BLANK);
233                    dx += 1;
234                    continue;
235                }
236
237                let w = (cell.width() as u16).max(1);
238
239                // Wide primary that won't fit — either source's right
240                // edge sliced through its continuation, or either edge
241                // of the target cuts it off. Emit a blank instead.
242                let fits_in_src = dx + w <= b.width;
243                let fits_in_dst = dst.x >= tb.x && dst.x.saturating_add(w) <= t_right;
244                if w > 1 && (!fits_in_src || !fits_in_dst) {
245                    target.set_cell(dst, &Cell::BLANK);
246                    dx += 1;
247                    continue;
248                }
249
250                target.set_cell(dst, cell);
251                dx += w;
252            }
253        }
254    }
255}
256
257/// A rectangular cell grid that can be modified.
258///
259/// `SurfaceMut` is the trait to use for render targets. It includes a
260/// primitive cell write plus higher-level operations used by terminal
261/// emulation and drawing code: fills, clears, line insertion/deletion, and
262/// cell insertion/deletion within a row.
263///
264/// Implementations are responsible for preserving the same wide-cell
265/// invariants as [`Buffer`](super::Buffer): a wide primary owns the
266/// following continuation slot, continuations should not become visible
267/// without their primary, and clipped wide writes should leave blanks
268/// rather than half a grapheme.
269pub trait SurfaceMut: Surface {
270    /// Place `cell` at `pos`. Implementations are responsible for
271    /// wide-cell semantics (continuation markers, blanking covered
272    /// cells) and any dirty tracking they care to do. Taking `&Cell`
273    /// lets implementations skip the clone when the destination
274    /// already matches.
275    ///
276    /// # Parameters
277    ///
278    /// - `pos`: destination coordinate in this surface's coordinate space.
279    /// - `cell`: cell to write.
280    ///
281    /// # Returns
282    ///
283    /// Nothing.
284    ///
285    /// # Panics
286    ///
287    /// Implementations should not panic for out-of-bounds positions.
288    ///
289    /// # Usage notes
290    ///
291    /// Out-of-bounds writes should be ignored. Use this method instead of
292    /// [`Self::cell_mut`] whenever changing content could affect display
293    /// width or neighboring continuation slots.
294    // A continuation belongs to the wide cell on its left and is placed by
295    // that cell's own write, so an implementor ignores one passed on its
296    // own rather than storing it.
297    fn set_cell(&mut self, pos: Position, cell: &Cell);
298
299    /// Mutable handle to the cell at `pos`. Returns `None` for
300    /// out-of-bounds positions.
301    ///
302    /// # Parameters
303    ///
304    /// - `pos`: coordinate in this surface's coordinate space.
305    ///
306    /// # Returns
307    ///
308    /// `Some(&mut Cell)` for an in-bounds cell, or `None` when `pos` is not
309    /// writable.
310    ///
311    /// # Panics
312    ///
313    /// Implementations should not panic for out-of-bounds positions.
314    ///
315    /// # Usage notes
316    ///
317    /// Implementations that track dirty state must mark the cell as
318    /// touched eagerly — the caller may mutate any field through this
319    /// handle and the surface has no way to observe a write. Callers
320    /// must not change the cell's display width through this handle; use
321    /// [`Self::set_cell`] for wide-cell writes that need
322    /// continuation-column accounting.
323    fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell>;
324
325    /// Fill the entire surface bounds with `cell`.
326    ///
327    /// # Parameters
328    ///
329    /// - `cell`: fill cell to write repeatedly.
330    ///
331    /// # Returns
332    ///
333    /// Nothing.
334    ///
335    /// # Panics
336    ///
337    /// Does not panic unless [`Self::fill_rect`] panics.
338    ///
339    /// # Usage notes
340    ///
341    /// This delegates to [`Self::fill_rect`] with [`Bounded::bounds`].
342    /// Wide fills are handled by `fill_rect`.
343    fn fill(&mut self, cell: &Cell) {
344        let b = self.bounds();
345        self.fill_rect(b, cell);
346    }
347
348    /// Fill the intersection of `rect` and [`Bounded::bounds`] with
349    /// `cell`.
350    ///
351    /// # Parameters
352    ///
353    /// - `rect`: requested fill rectangle in this surface's coordinate
354    ///   space.
355    /// - `cell`: fill cell to write.
356    ///
357    /// # Behavior
358    ///
359    /// Stepped by `cell.width()` so a wide cell lays down a clean primary
360    /// and its continuations; whatever region is left at the right edge,
361    /// narrower than one more cell, falls back to blanks with `cell`'s style.
362    /// Implementations may override for a bulk-blit fast path.
363    ///
364    /// # Panics
365    ///
366    /// The default implementation does not panic unless [`Self::set_cell`]
367    /// panics.
368    ///
369    /// # Usage notes
370    ///
371    /// Empty intersections are no-ops. A region whose width is not a
372    /// multiple of `cell.width()` ends in blanks, because the cell cannot be
373    /// drawn in fewer columns than it takes: a width-8 fill into ten columns
374    /// leaves the last two blank.
375    fn fill_rect(&mut self, rect: Rect, cell: &Cell) {
376        let clipped = self.bounds().intersection(rect);
377        let step = (cell.width() as u16).max(1);
378        let blank = Cell::BLANK.style(cell.style.clone());
379        for y in clipped.top()..clipped.bottom() {
380            let mut x = clipped.left();
381            while !overruns(x, step, clipped.right()) {
382                self.set_cell(Position::new(x, y), cell);
383                x += step;
384            }
385            while x < clipped.right() {
386                self.set_cell(Position::new(x, y), &blank);
387                x += 1;
388            }
389        }
390    }
391
392    /// Clear the entire surface bounds to [`Cell::BLANK`].
393    ///
394    /// # Returns
395    ///
396    /// Nothing.
397    ///
398    /// # Panics
399    ///
400    /// Does not panic unless [`Self::fill`] panics.
401    ///
402    /// # Usage notes
403    ///
404    /// This is equivalent to `self.fill(&Cell::BLANK)`.
405    fn clear(&mut self) {
406        self.fill(&Cell::BLANK);
407    }
408
409    /// Clear a rectangle to [`Cell::BLANK`].
410    ///
411    /// # Parameters
412    ///
413    /// - `rect`: requested clear rectangle in this surface's coordinate
414    ///   space.
415    ///
416    /// # Returns
417    ///
418    /// Nothing.
419    ///
420    /// # Panics
421    ///
422    /// Does not panic unless [`Self::fill_rect`] panics.
423    ///
424    /// # Usage notes
425    ///
426    /// Only the intersection of `rect` and [`Bounded::bounds`] is modified.
427    fn clear_rect(&mut self, rect: Rect) {
428        self.fill_rect(rect, &Cell::BLANK);
429    }
430
431    /// Insert `n` blank rows at `y`, pushing existing rows down within
432    /// `[y, bounds_bottom)`. Rows pushed past `bounds_bottom` are lost.
433    /// Freed top rows are filled with `fill`.
434    ///
435    /// # Parameters
436    ///
437    /// - `y`: first row to insert at, in this surface's coordinate space.
438    /// - `n`: number of rows to insert.
439    /// - `bounds_bottom`: exclusive lower row bound for the affected region.
440    /// - `fill`: cell used to fill the newly opened rows.
441    ///
442    /// # Behavior
443    ///
444    /// The default implementation copies cells through [`Self::set_cell`]
445    /// row-by-row from the bottom up, advancing along each source row by
446    /// the source cell's width so wide-primary cells move as a unit and
447    /// their continuation columns are not independently rewritten.
448    /// Wide primaries that no longer fit are replaced by a blank.
449    /// Implementations backed by contiguous row storage may override
450    /// with a row-swap fast path.
451    ///
452    /// # Panics
453    ///
454    /// The default implementation does not panic unless [`Self::set_cell`]
455    /// panics.
456    ///
457    /// # Usage notes
458    ///
459    /// `bounds_bottom` is clamped to the surface height. Calls with
460    /// `n == 0` or `y >= bounds_bottom` are no-ops.
461    fn insert_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
462        let h = self.bounds().height;
463        let bottom = bounds_bottom.min(h);
464        if y >= bottom || n == 0 {
465            return;
466        }
467        let n = n.min(bottom - y);
468        let width = self.bounds().width;
469        let mut dst = bottom;
470        while dst > y + n {
471            dst -= 1;
472            let src = dst - n;
473            copy_row(self, src, dst, width);
474        }
475        for row in y..y + n {
476            blank_row(self, row, width, fill);
477        }
478    }
479
480    /// Delete `n` rows at `y`, pulling existing rows up within
481    /// `[y, bounds_bottom)`. The bottom `n` rows of the window are
482    /// filled with `fill`.
483    ///
484    /// # Parameters
485    ///
486    /// - `y`: first row to delete, in this surface's coordinate space.
487    /// - `n`: number of rows to delete.
488    /// - `bounds_bottom`: exclusive lower row bound for the affected region.
489    /// - `fill`: cell used to fill the freed bottom rows.
490    ///
491    /// # Behavior
492    ///
493    /// The default implementation copies cells through [`Self::set_cell`]
494    /// row-by-row from top down, advancing along each source row by the
495    /// source cell's width so wide-primary cells move as a unit and
496    /// their continuation columns are not independently rewritten.
497    /// Wide primaries that no longer fit are replaced by a blank.
498    /// Implementations backed by contiguous row storage may override
499    /// with a row-swap fast path.
500    ///
501    /// # Panics
502    ///
503    /// The default implementation does not panic unless [`Self::set_cell`]
504    /// panics.
505    ///
506    /// # Usage notes
507    ///
508    /// `bounds_bottom` is clamped to the surface height. Calls with
509    /// `n == 0` or `y >= bounds_bottom` are no-ops.
510    fn delete_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
511        let h = self.bounds().height;
512        let bottom = bounds_bottom.min(h);
513        if y >= bottom || n == 0 {
514            return;
515        }
516        let n = n.min(bottom - y);
517        let width = self.bounds().width;
518        for dst in y..bottom - n {
519            let src = dst + n;
520            copy_row(self, src, dst, width);
521        }
522        for row in bottom - n..bottom {
523            blank_row(self, row, width, fill);
524        }
525    }
526
527    /// Insert `n` blank cells at `pos`, pushing cells in
528    /// `[pos.x, bounds_right)` right within row `pos.y`. Cells pushed
529    /// past `bounds_right` are lost. The freed slots `[pos.x, pos.x + n)`
530    /// are filled with `fill`.
531    ///
532    /// # Parameters
533    ///
534    /// - `pos`: row and starting column for insertion.
535    /// - `n`: number of cells to insert.
536    /// - `bounds_right`: exclusive right column bound for the affected row
537    ///   region.
538    /// - `fill`: cell used to fill the newly opened slots.
539    ///
540    /// # Behavior
541    ///
542    /// The default implementation snapshots the row's primary cells in
543    /// the affected window through [`Surface::cell`], blanks the window
544    /// via [`Self::set_cell`], then replays each surviving primary at
545    /// its shifted position. Wide primaries whose new footprint would
546    /// cross `bounds_right` are dropped. Implementations backed by a
547    /// contiguous row slice may override with an in-place rotate.
548    ///
549    /// # Panics
550    ///
551    /// The default implementation does not panic unless [`Self::set_cell`]
552    /// panics.
553    ///
554    /// # Usage notes
555    ///
556    /// `bounds_right` is clamped to the surface width. Calls with `n == 0`,
557    /// an out-of-bounds row, or `pos.x >= bounds_right` are no-ops.
558    fn insert_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
559        let bounds = self.bounds();
560        if pos.y >= bounds.height || pos.x >= bounds.width || n == 0 {
561            return;
562        }
563        let right = bounds_right.min(bounds.width);
564        if pos.x >= right {
565            return;
566        }
567        let n = n.min(right - pos.x);
568
569        let primaries = collect_primaries(self, pos.y, pos.x, right);
570        blank_span(self, pos.y, pos.x, right);
571
572        for (src_x, cell) in primaries.iter().rev() {
573            let dst_x = src_x.saturating_add(n);
574            let cw = (cell.width() as u16).max(1);
575            if overruns(dst_x, cw, right) {
576                continue;
577            }
578            self.set_cell(Position::new(dst_x, pos.y), cell);
579        }
580
581        fill_span(self, pos.y, pos.x, pos.x + n, fill);
582    }
583
584    /// Delete `n` cells at `pos`, pulling cells in
585    /// `[pos.x + n, bounds_right)` left within row `pos.y`. The freed
586    /// slots `[bounds_right - n, bounds_right)` are filled with `fill`.
587    ///
588    /// # Parameters
589    ///
590    /// - `pos`: row and starting column for deletion.
591    /// - `n`: number of cells to delete.
592    /// - `bounds_right`: exclusive right column bound for the affected row
593    ///   region.
594    /// - `fill`: cell used to fill the freed right-edge slots.
595    ///
596    /// # Behavior
597    ///
598    /// The default implementation snapshots the row's primary cells in
599    /// the affected window through [`Surface::cell`], blanks the window
600    /// via [`Self::set_cell`], then replays each surviving primary at
601    /// its shifted position. Primaries whose source column falls inside
602    /// `[pos.x, pos.x + n)` are dropped. Implementations backed by a
603    /// contiguous row slice may override with an in-place rotate.
604    ///
605    /// # Panics
606    ///
607    /// The default implementation does not panic unless [`Self::set_cell`]
608    /// panics.
609    ///
610    /// # Usage notes
611    ///
612    /// `bounds_right` is clamped to the surface width. Calls with `n == 0`,
613    /// an out-of-bounds row, or `pos.x >= bounds_right` are no-ops.
614    fn delete_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
615        let bounds = self.bounds();
616        if pos.y >= bounds.height || pos.x >= bounds.width || n == 0 {
617            return;
618        }
619        let right = bounds_right.min(bounds.width);
620        if pos.x >= right {
621            return;
622        }
623        let n = n.min(right - pos.x);
624
625        let primaries = collect_primaries(self, pos.y, pos.x, right);
626        blank_span(self, pos.y, pos.x, right);
627
628        let cut = pos.x + n;
629        for (src_x, cell) in primaries.iter() {
630            if *src_x < cut {
631                continue;
632            }
633            let dst_x = src_x - n;
634            let cw = (cell.width() as u16).max(1);
635            if overruns(dst_x, cw, right) {
636                continue;
637            }
638            self.set_cell(Position::new(dst_x, pos.y), cell);
639        }
640
641        fill_span(self, pos.y, right - n, right, fill);
642    }
643}
644
645fn collect_primaries<S: SurfaceMut + ?Sized>(
646    s: &S,
647    y: u16,
648    left: u16,
649    right: u16,
650) -> Vec<(u16, Cell)> {
651    let mut out = Vec::new();
652    let mut x = left;
653    while x < right {
654        if let Some(cell) = s.cell(Position::new(x, y))
655            && !cell.is_continuation()
656        {
657            let cw = (cell.width() as u16).max(1);
658            out.push((x, cell.clone()));
659            x = x.saturating_add(cw);
660            continue;
661        }
662        x = x.saturating_add(1);
663    }
664    out
665}
666
667fn blank_span<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, left: u16, right: u16) {
668    for x in left..right {
669        s.set_cell(Position::new(x, y), &Cell::BLANK);
670    }
671}
672
673fn fill_span<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, left: u16, right: u16, fill: &Cell) {
674    if left >= right {
675        return;
676    }
677    let fill_w = (fill.width() as u16).max(1);
678    let mut col = left;
679    while !overruns(col, fill_w, right) {
680        s.set_cell(Position::new(col, y), fill);
681        col += fill_w;
682    }
683    while col < right {
684        s.set_cell(Position::new(col, y), &Cell::BLANK);
685        col += 1;
686    }
687}
688
689fn copy_row<S: SurfaceMut + ?Sized>(s: &mut S, src_y: u16, dst_y: u16, width: u16) {
690    let mut x: u16 = 0;
691    while x < width {
692        let src = Position::new(x, src_y);
693        let dst = Position::new(x, dst_y);
694        let Some(cell) = s.cell(src).cloned() else {
695            x += 1;
696            continue;
697        };
698        if cell.is_continuation() {
699            s.set_cell(dst, &Cell::BLANK);
700            x += 1;
701            continue;
702        }
703        let cw = (cell.width() as u16).max(1);
704        if cw > 1 && x + cw > width {
705            s.set_cell(dst, &Cell::BLANK);
706            x += 1;
707        } else {
708            s.set_cell(dst, &cell);
709            x += cw;
710        }
711    }
712}
713
714fn blank_row<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, width: u16, fill: &Cell) {
715    for x in 0..width {
716        s.set_cell(Position::new(x, y), fill);
717    }
718}
719
720impl Bounded for Rect {
721    fn bounds(&self) -> Rect {
722        *self
723    }
724}
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729    use crate::buffer::Buffer;
730    use crate::buffer::View;
731
732    #[test]
733    fn the_default_row_edits_fill_a_row_as_wide_as_the_address_space() {
734        // `Buffer` overrides both of these, so the default walks need their
735        // own cover. Both fill a freed span with a cell that can claim 255
736        // columns, and near the end of the address space the column plus
737        // that width no longer fits a `u16`.
738        let mut buf = Buffer::new(u16::MAX, 1);
739        let mut view = View::new(&mut buf, Rect::new(0, 0, u16::MAX, 1));
740        let wide = Cell::new("\u{1f468}\u{200d}\u{1f469}", 255);
741
742        view.insert_cells(Position::new(u16::MAX - 1, 0), 1, u16::MAX, &wide);
743        view.delete_cells(Position::new(0, 0), 1, u16::MAX, &wide);
744
745        assert!(!buf.cell(Position::new(u16::MAX - 1, 0)).unwrap().is_wide());
746    }
747
748    #[test]
749    fn the_default_wide_fill_spans_a_row_as_wide_as_the_address_space() {
750        // `Buffer` inlines its own copy of this walk, so the two have to
751        // agree about a row wide enough that the column plus the step no
752        // longer fits a `u16`.
753        let mut buf = Buffer::new(u16::MAX, 1);
754        let mut view = View::new(&mut buf, Rect::new(0, 0, u16::MAX, 1));
755        view.fill_rect(
756            Rect::new(0, 0, u16::MAX, 1),
757            &Cell::new("\u{1f468}\u{200d}\u{1f469}", 255),
758        );
759
760        assert!(buf.cell(Position::new(0, 0)).unwrap().is_wide());
761    }
762
763    fn wide(s: &str) -> Cell {
764        Cell::new(s, 2)
765    }
766
767    fn cont() -> Cell {
768        Cell::CONTINUATION
769    }
770
771    #[test]
772    fn draw_copies_normal_cells() {
773        let mut src = Buffer::new(2, 1);
774        src.set((0, 0), &Cell::new("A", 1));
775        src.set((1, 0), &Cell::new("B", 1));
776        let mut dst = Buffer::new(4, 1);
777        src.draw(&mut dst, Position::new(1, 0));
778        assert_eq!(dst.cell(Position::new(0, 0)).unwrap().content(), " ");
779        assert_eq!(dst.cell(Position::new(1, 0)).unwrap().content(), "A");
780        assert_eq!(dst.cell(Position::new(2, 0)).unwrap().content(), "B");
781    }
782
783    #[test]
784    fn draw_preserves_wide_pair() {
785        let mut src = Buffer::new(2, 1);
786        src.set((0, 0), &wide("世"));
787        // Buffer::set already wrote the continuation at (1,0).
788        let mut dst = Buffer::new(2, 1);
789        src.draw(&mut dst, Position::new(0, 0));
790        let primary = dst.cell(Position::new(0, 0)).unwrap();
791        let cont_cell = dst.cell(Position::new(1, 0)).unwrap();
792        assert_eq!(primary.content(), "世");
793        assert_eq!(primary.width(), 2);
794        assert!(cont_cell.is_continuation());
795    }
796
797    #[test]
798    fn draw_blanks_leading_continuation_in_source_slice() {
799        // Construct a source whose first column is the orphan
800        // continuation half of a wide cell that lives outside the
801        // slice. The default must not propagate the orphan.
802        let mut src = Buffer::new(2, 1);
803        src.set((0, 0), &cont());
804        src.set((1, 0), &Cell::new("X", 1));
805
806        let mut dst = Buffer::new(2, 1);
807        // Pre-seed target with an unrelated wide cell to make sure
808        // we'd notice a corruption.
809        dst.set((0, 0), &Cell::new("Y", 1));
810        dst.set((1, 0), &Cell::new("Z", 1));
811
812        src.draw(&mut dst, Position::new(0, 0));
813
814        // The orphan continuation became a blank — no spurious
815        // continuation marker carried over.
816        assert!(!dst.cell(Position::new(0, 0)).unwrap().is_continuation());
817        assert_eq!(dst.cell(Position::new(0, 0)).unwrap().content(), " ");
818        assert_eq!(dst.cell(Position::new(1, 0)).unwrap().content(), "X");
819    }
820
821    #[test]
822    fn draw_blanks_wide_primary_with_no_room_in_target() {
823        // Wide primary lands at the last column of the target — its
824        // continuation would fall outside target bounds. Substitute
825        // a blank rather than emitting a half-drawn grapheme.
826        let mut src = Buffer::new(2, 1);
827        src.set((0, 0), &wide("世"));
828
829        let mut dst = Buffer::new(2, 1);
830        src.draw(&mut dst, Position::new(1, 0));
831
832        let landed = dst.cell(Position::new(1, 0)).unwrap();
833        assert_eq!(landed.content(), " ");
834        assert_eq!(landed.width(), 1);
835    }
836
837    #[test]
838    fn draw_clears_a_wide_cell_clipped_by_the_target_left_edge() {
839        use crate::buffer::View;
840
841        for (text, width) in [("中", 2), ("👩‍👩", 4), ("👩‍👩‍👧‍👦", 8)] {
842            let cell = Cell::new(text, width);
843            let width = u16::from(width);
844            for origin in [Position::ORIGIN, Position::new(2, 1)] {
845                let mut source = Buffer::new(origin.x + width + 1, origin.y + 1);
846                source.set(origin, &cell);
847                source.set((origin.x + width, origin.y), &Cell::new("M", 1));
848                let source = View::new(&mut source, Rect::new(origin.x, origin.y, width + 1, 1));
849
850                for at in [Position::ORIGIN, Position::new(3, 2)] {
851                    for clipped in 0..=width {
852                        let mut target = Buffer::new(at.x + width + 2, at.y + 2);
853                        target.fill(&Cell::new("Z", 1));
854                        let mut expected = target.clone();
855                        for x in at.x + clipped..at.x + width {
856                            expected.set((x, at.y), &Cell::BLANK);
857                        }
858                        if clipped == 0 {
859                            expected.set(at, &cell);
860                        }
861                        expected.set((at.x + width, at.y), &Cell::new("M", 1));
862
863                        let mut view = View::new(
864                            &mut target,
865                            Rect::new(at.x + clipped, at.y, width + 1 - clipped, 1),
866                        );
867                        source.draw(&mut view, at);
868
869                        for y in 0..target.height() {
870                            assert_eq!(
871                                target.line(y),
872                                expected.line(y),
873                                "width {width}, clipped {clipped}, origin {origin:?}, at {at:?}, row {y}",
874                            );
875                        }
876                    }
877                }
878            }
879        }
880    }
881
882    // The remaining tests exercise the SurfaceMut default implementations
883    // of insert/delete lines and insert/delete cells via Window, which
884    // does not override them.
885    use crate::buffer::Window;
886
887    fn window_with(content: &[&str]) -> Window {
888        let w = content[0].chars().count() as u16;
889        let h = content.len() as u16;
890        let mut win = Window::new(w, h);
891        for (y, row) in content.iter().enumerate() {
892            for (x, ch) in row.chars().enumerate() {
893                win.set_cell(
894                    Position::new(x as u16, y as u16),
895                    &Cell::new(ch.to_string(), 1),
896                );
897            }
898        }
899        win
900    }
901
902    fn row_content(win: &Window, y: u16) -> String {
903        (0..win.bounds().width)
904            .map(|x| {
905                win.cell(Position::new(x, y))
906                    .map(|c| c.content().to_string())
907                    .unwrap_or_default()
908            })
909            .collect()
910    }
911
912    #[test]
913    fn default_insert_lines_shifts_down_and_blanks_top() {
914        let mut win = window_with(&["AAA", "BBB", "CCC", "DDD"]);
915        SurfaceMut::insert_lines(&mut win, 1, 1, 4, &Cell::BLANK);
916        assert_eq!(row_content(&win, 0), "AAA");
917        assert_eq!(row_content(&win, 1), "   ");
918        assert_eq!(row_content(&win, 2), "BBB");
919        assert_eq!(row_content(&win, 3), "CCC");
920    }
921
922    #[test]
923    fn default_delete_lines_pulls_up_and_blanks_bottom() {
924        let mut win = window_with(&["AAA", "BBB", "CCC", "DDD"]);
925        SurfaceMut::delete_lines(&mut win, 1, 1, 4, &Cell::BLANK);
926        assert_eq!(row_content(&win, 0), "AAA");
927        assert_eq!(row_content(&win, 1), "CCC");
928        assert_eq!(row_content(&win, 2), "DDD");
929        assert_eq!(row_content(&win, 3), "   ");
930    }
931
932    #[test]
933    fn default_insert_cells_shifts_right_and_blanks_left() {
934        let mut win = window_with(&["ABCDE"]);
935        SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 2, 5, &Cell::BLANK);
936        assert_eq!(row_content(&win, 0), "A  BC");
937    }
938
939    #[test]
940    fn default_delete_cells_pulls_left_and_blanks_right() {
941        let mut win = window_with(&["ABCDE"]);
942        SurfaceMut::delete_cells(&mut win, Position::new(1, 0), 2, 5, &Cell::BLANK);
943        assert_eq!(row_content(&win, 0), "ADE  ");
944    }
945
946    #[test]
947    fn default_insert_cells_drops_wide_that_no_longer_fits() {
948        // Row: A 世(prim) 世(cont) B, then insert 1 at col 1 with right=4.
949        // 世's new primary would be at col 2, continuation at col 3.
950        // That still fits (col 3 < right=4). So 世 is preserved.
951        let mut win = Window::new(4, 1);
952        win.set_cell(Position::new(0, 0), &Cell::new("A", 1));
953        win.set_cell(Position::new(1, 0), &wide("世"));
954        win.set_cell(Position::new(3, 0), &Cell::new("B", 1));
955        SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 1, 4, &Cell::BLANK);
956
957        assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
958        assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), " ");
959        assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), "世");
960        assert!(win.cell(Position::new(3, 0)).unwrap().is_continuation());
961    }
962
963    #[test]
964    fn default_insert_cells_blanks_wide_that_overflows() {
965        // Row: A B 世(prim) 世(cont), then insert 1 at col 1 with right=4.
966        // 世 would move from col 2 to col 3, continuation to col 4 (out).
967        // So 世 must be dropped, leaving a blank at col 3.
968        let mut win = Window::new(4, 1);
969        win.set_cell(Position::new(0, 0), &Cell::new("A", 1));
970        win.set_cell(Position::new(1, 0), &Cell::new("B", 1));
971        win.set_cell(Position::new(2, 0), &wide("世"));
972        SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 1, 4, &Cell::BLANK);
973
974        assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
975        assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), " ");
976        assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), "B");
977        assert_eq!(win.cell(Position::new(3, 0)).unwrap().content(), " ");
978    }
979
980    #[test]
981    fn default_delete_cells_drops_primary_in_deletion_window() {
982        // Row: A 世(prim) 世(cont) B, delete 2 at col 1 with right=4.
983        // 世's primary falls inside [1, 3) → dropped entirely.
984        let mut win = Window::new(4, 1);
985        win.set_cell(Position::new(0, 0), &Cell::new("A", 1));
986        win.set_cell(Position::new(1, 0), &wide("世"));
987        win.set_cell(Position::new(3, 0), &Cell::new("B", 1));
988        SurfaceMut::delete_cells(&mut win, Position::new(1, 0), 2, 4, &Cell::BLANK);
989
990        assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
991        assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), "B");
992        assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), " ");
993        assert_eq!(win.cell(Position::new(3, 0)).unwrap().content(), " ");
994    }
995}