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};
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 a wide primary
197    ///   would land at the right edge of `target` with no room for its
198    ///   continuation.
199    ///
200    /// Implementations may override for a faster path (e.g. bulk line
201    /// copies), but must preserve the wide-cell invariants above.
202    ///
203    /// # Panics
204    ///
205    /// The default implementation does not panic unless the destination's
206    /// [`SurfaceMut::set_cell`] implementation panics.
207    ///
208    /// # Usage notes
209    ///
210    /// Destination clipping is delegated to `target`. Cells whose
211    /// destination coordinate lies outside the target bounds are passed to
212    /// `set_cell`, which is expected to ignore them.
213    fn draw<T: SurfaceMut + ?Sized>(&self, target: &mut T, at: Position) {
214        let b = self.bounds();
215        let tb = target.bounds();
216        let t_right = tb.x.saturating_add(tb.width);
217        for dy in 0..b.height {
218            let dst_y = at.y.saturating_add(dy);
219            let mut dx: u16 = 0;
220            while dx < b.width {
221                let src = Position::new(b.x + dx, b.y + dy);
222                let dst = Position::new(at.x.saturating_add(dx), dst_y);
223                let Some(cell) = self.cell(src) else {
224                    dx += 1;
225                    continue;
226                };
227
228                // Leading continuation with no primary in view: the
229                // source's left edge sliced a wide grapheme in half.
230                // Substitute a blank to avoid writing an orphan
231                // continuation into target.
232                if cell.is_continuation() {
233                    target.set_cell(dst, &Cell::BLANK);
234                    dx += 1;
235                    continue;
236                }
237
238                let w = (cell.width() as u16).max(1);
239
240                // Wide primary that won't fit — either source's right
241                // edge sliced through its continuation, or target's
242                // right edge cuts it off. Emit a blank instead.
243                let fits_in_src = dx + w <= b.width;
244                let fits_in_dst = dst.x.saturating_add(w) <= t_right;
245                if w > 1 && (!fits_in_src || !fits_in_dst) {
246                    target.set_cell(dst, &Cell::BLANK);
247                    dx += 1;
248                    continue;
249                }
250
251                target.set_cell(dst, cell);
252                dx += w;
253            }
254        }
255    }
256}
257
258/// A rectangular cell grid that can be modified.
259///
260/// `SurfaceMut` is the trait to use for render targets. It includes a
261/// primitive cell write plus higher-level operations used by terminal
262/// emulation and drawing code: fills, clears, line insertion/deletion, and
263/// cell insertion/deletion within a row.
264///
265/// Implementations are responsible for preserving the same wide-cell
266/// invariants as [`Buffer`](super::Buffer): a wide primary owns the
267/// following continuation slot, continuations should not become visible
268/// without their primary, and clipped wide writes should leave blanks
269/// rather than half a grapheme.
270pub trait SurfaceMut: Surface {
271    /// Place `cell` at `pos`. Implementations are responsible for
272    /// wide-cell semantics (continuation markers, blanking covered
273    /// cells) and any dirty tracking they care to do. Taking `&Cell`
274    /// lets implementations skip the clone when the destination
275    /// already matches.
276    ///
277    /// # Parameters
278    ///
279    /// - `pos`: destination coordinate in this surface's coordinate space.
280    /// - `cell`: cell to write.
281    ///
282    /// # Returns
283    ///
284    /// Nothing.
285    ///
286    /// # Panics
287    ///
288    /// Implementations should not panic for out-of-bounds positions.
289    ///
290    /// # Usage notes
291    ///
292    /// Out-of-bounds writes should be ignored. Use this method instead of
293    /// [`Self::cell_mut`] whenever changing content could affect display
294    /// width or neighboring continuation slots.
295    fn set_cell(&mut self, pos: Position, cell: &Cell);
296
297    /// Mutable handle to the cell at `pos`. Returns `None` for
298    /// out-of-bounds positions.
299    ///
300    /// # Parameters
301    ///
302    /// - `pos`: coordinate in this surface's coordinate space.
303    ///
304    /// # Returns
305    ///
306    /// `Some(&mut Cell)` for an in-bounds cell, or `None` when `pos` is not
307    /// writable.
308    ///
309    /// # Panics
310    ///
311    /// Implementations should not panic for out-of-bounds positions.
312    ///
313    /// # Usage notes
314    ///
315    /// Implementations that track dirty state must mark the cell as
316    /// touched eagerly — the caller may mutate any field through this
317    /// handle and the surface has no way to observe a write. Callers
318    /// must not change the cell's display width through this handle; use
319    /// [`Self::set_cell`] for wide-cell writes that need
320    /// continuation-column accounting.
321    fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell>;
322
323    /// Fill the entire surface bounds with `cell`.
324    ///
325    /// # Parameters
326    ///
327    /// - `cell`: fill cell to write repeatedly.
328    ///
329    /// # Returns
330    ///
331    /// Nothing.
332    ///
333    /// # Panics
334    ///
335    /// Does not panic unless [`Self::fill_rect`] panics.
336    ///
337    /// # Usage notes
338    ///
339    /// This delegates to [`Self::fill_rect`] with [`Bounded::bounds`].
340    /// Wide fills are handled by `fill_rect`.
341    fn fill(&mut self, cell: &Cell) {
342        let b = self.bounds();
343        self.fill_rect(b, cell);
344    }
345
346    /// Fill the intersection of `rect` and [`Bounded::bounds`] with
347    /// `cell`.
348    ///
349    /// # Parameters
350    ///
351    /// - `rect`: requested fill rectangle in this surface's coordinate
352    ///   space.
353    /// - `cell`: fill cell to write.
354    ///
355    /// # Behavior
356    ///
357    /// Stepped by `cell.width()` so wide cells lay down clean
358    /// primary/continuation pairs; a trailing partial slot at the
359    /// right edge falls back to a blank. Implementations may override
360    /// for a bulk-blit fast path.
361    ///
362    /// # Panics
363    ///
364    /// The default implementation does not panic unless [`Self::set_cell`]
365    /// panics.
366    ///
367    /// # Usage notes
368    ///
369    /// Empty intersections are no-ops. A wide fill into an odd-width region
370    /// leaves the final single column blank because a two-column grapheme
371    /// cannot fit there.
372    fn fill_rect(&mut self, rect: Rect, cell: &Cell) {
373        let clipped = self.bounds().intersection(rect);
374        let step = (cell.width() as u16).max(1);
375        for y in clipped.top()..clipped.bottom() {
376            let mut x = clipped.left();
377            while x + step <= clipped.right() {
378                self.set_cell(Position::new(x, y), cell);
379                x += step;
380            }
381            while x < clipped.right() {
382                self.set_cell(Position::new(x, y), &Cell::BLANK);
383                x += 1;
384            }
385        }
386    }
387
388    /// Clear the entire surface bounds to [`Cell::BLANK`].
389    ///
390    /// # Returns
391    ///
392    /// Nothing.
393    ///
394    /// # Panics
395    ///
396    /// Does not panic unless [`Self::fill`] panics.
397    ///
398    /// # Usage notes
399    ///
400    /// This is equivalent to `self.fill(&Cell::BLANK)`.
401    fn clear(&mut self) {
402        self.fill(&Cell::BLANK);
403    }
404
405    /// Clear a rectangle to [`Cell::BLANK`].
406    ///
407    /// # Parameters
408    ///
409    /// - `rect`: requested clear rectangle in this surface's coordinate
410    ///   space.
411    ///
412    /// # Returns
413    ///
414    /// Nothing.
415    ///
416    /// # Panics
417    ///
418    /// Does not panic unless [`Self::fill_rect`] panics.
419    ///
420    /// # Usage notes
421    ///
422    /// Only the intersection of `rect` and [`Bounded::bounds`] is modified.
423    fn clear_rect(&mut self, rect: Rect) {
424        self.fill_rect(rect, &Cell::BLANK);
425    }
426
427    /// Insert `n` blank rows at `y`, pushing existing rows down within
428    /// `[y, bounds_bottom)`. Rows pushed past `bounds_bottom` are lost.
429    /// Freed top rows are filled with `fill`.
430    ///
431    /// # Parameters
432    ///
433    /// - `y`: first row to insert at, in this surface's coordinate space.
434    /// - `n`: number of rows to insert.
435    /// - `bounds_bottom`: exclusive lower row bound for the affected region.
436    /// - `fill`: cell used to fill the newly opened rows.
437    ///
438    /// # Behavior
439    ///
440    /// The default implementation copies cells through [`Self::set_cell`]
441    /// row-by-row from the bottom up, advancing along each source row by
442    /// the source cell's width so wide-primary cells move as a unit and
443    /// their continuation columns are not independently rewritten.
444    /// Wide primaries that no longer fit are replaced by a blank.
445    /// Implementations backed by contiguous row storage may override
446    /// with a row-swap fast path.
447    ///
448    /// # Panics
449    ///
450    /// The default implementation does not panic unless [`Self::set_cell`]
451    /// panics.
452    ///
453    /// # Usage notes
454    ///
455    /// `bounds_bottom` is clamped to the surface height. Calls with
456    /// `n == 0` or `y >= bounds_bottom` are no-ops.
457    fn insert_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
458        let h = self.bounds().height;
459        let bottom = bounds_bottom.min(h);
460        if y >= bottom || n == 0 {
461            return;
462        }
463        let n = n.min(bottom - y);
464        let width = self.bounds().width;
465        let mut dst = bottom;
466        while dst > y + n {
467            dst -= 1;
468            let src = dst - n;
469            copy_row(self, src, dst, width);
470        }
471        for row in y..y + n {
472            blank_row(self, row, width, fill);
473        }
474    }
475
476    /// Delete `n` rows at `y`, pulling existing rows up within
477    /// `[y, bounds_bottom)`. The bottom `n` rows of the window are
478    /// filled with `fill`.
479    ///
480    /// # Parameters
481    ///
482    /// - `y`: first row to delete, in this surface's coordinate space.
483    /// - `n`: number of rows to delete.
484    /// - `bounds_bottom`: exclusive lower row bound for the affected region.
485    /// - `fill`: cell used to fill the freed bottom rows.
486    ///
487    /// # Behavior
488    ///
489    /// The default implementation copies cells through [`Self::set_cell`]
490    /// row-by-row from top down, advancing along each source row by the
491    /// source cell's width so wide-primary cells move as a unit and
492    /// their continuation columns are not independently rewritten.
493    /// Wide primaries that no longer fit are replaced by a blank.
494    /// Implementations backed by contiguous row storage may override
495    /// with a row-swap fast path.
496    ///
497    /// # Panics
498    ///
499    /// The default implementation does not panic unless [`Self::set_cell`]
500    /// panics.
501    ///
502    /// # Usage notes
503    ///
504    /// `bounds_bottom` is clamped to the surface height. Calls with
505    /// `n == 0` or `y >= bounds_bottom` are no-ops.
506    fn delete_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
507        let h = self.bounds().height;
508        let bottom = bounds_bottom.min(h);
509        if y >= bottom || n == 0 {
510            return;
511        }
512        let n = n.min(bottom - y);
513        let width = self.bounds().width;
514        for dst in y..bottom - n {
515            let src = dst + n;
516            copy_row(self, src, dst, width);
517        }
518        for row in bottom - n..bottom {
519            blank_row(self, row, width, fill);
520        }
521    }
522
523    /// Insert `n` blank cells at `pos`, pushing cells in
524    /// `[pos.x, bounds_right)` right within row `pos.y`. Cells pushed
525    /// past `bounds_right` are lost. The freed slots `[pos.x, pos.x + n)`
526    /// are filled with `fill`.
527    ///
528    /// # Parameters
529    ///
530    /// - `pos`: row and starting column for insertion.
531    /// - `n`: number of cells to insert.
532    /// - `bounds_right`: exclusive right column bound for the affected row
533    ///   region.
534    /// - `fill`: cell used to fill the newly opened slots.
535    ///
536    /// # Behavior
537    ///
538    /// The default implementation snapshots the row's primary cells in
539    /// the affected window through [`Surface::cell`], blanks the window
540    /// via [`Self::set_cell`], then replays each surviving primary at
541    /// its shifted position. Wide primaries whose new footprint would
542    /// cross `bounds_right` are dropped. Implementations backed by a
543    /// contiguous row slice may override with an in-place rotate.
544    ///
545    /// # Panics
546    ///
547    /// The default implementation does not panic unless [`Self::set_cell`]
548    /// panics.
549    ///
550    /// # Usage notes
551    ///
552    /// `bounds_right` is clamped to the surface width. Calls with `n == 0`,
553    /// an out-of-bounds row, or `pos.x >= bounds_right` are no-ops.
554    fn insert_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
555        let bounds = self.bounds();
556        if pos.y >= bounds.height || pos.x >= bounds.width || n == 0 {
557            return;
558        }
559        let right = bounds_right.min(bounds.width);
560        if pos.x >= right {
561            return;
562        }
563        let n = n.min(right - pos.x);
564
565        let primaries = collect_primaries(self, pos.y, pos.x, right);
566        blank_span(self, pos.y, pos.x, right);
567
568        for (src_x, cell) in primaries.iter().rev() {
569            let dst_x = src_x.saturating_add(n);
570            let cw = (cell.width() as u16).max(1);
571            if dst_x >= right || dst_x + cw > right {
572                continue;
573            }
574            self.set_cell(Position::new(dst_x, pos.y), cell);
575        }
576
577        fill_span(self, pos.y, pos.x, pos.x + n, fill);
578    }
579
580    /// Delete `n` cells at `pos`, pulling cells in
581    /// `[pos.x + n, bounds_right)` left within row `pos.y`. The freed
582    /// slots `[bounds_right - n, bounds_right)` are filled with `fill`.
583    ///
584    /// # Parameters
585    ///
586    /// - `pos`: row and starting column for deletion.
587    /// - `n`: number of cells to delete.
588    /// - `bounds_right`: exclusive right column bound for the affected row
589    ///   region.
590    /// - `fill`: cell used to fill the freed right-edge slots.
591    ///
592    /// # Behavior
593    ///
594    /// The default implementation snapshots the row's primary cells in
595    /// the affected window through [`Surface::cell`], blanks the window
596    /// via [`Self::set_cell`], then replays each surviving primary at
597    /// its shifted position. Primaries whose source column falls inside
598    /// `[pos.x, pos.x + n)` are dropped. Implementations backed by a
599    /// contiguous row slice may override with an in-place rotate.
600    ///
601    /// # Panics
602    ///
603    /// The default implementation does not panic unless [`Self::set_cell`]
604    /// panics.
605    ///
606    /// # Usage notes
607    ///
608    /// `bounds_right` is clamped to the surface width. Calls with `n == 0`,
609    /// an out-of-bounds row, or `pos.x >= bounds_right` are no-ops.
610    fn delete_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
611        let bounds = self.bounds();
612        if pos.y >= bounds.height || pos.x >= bounds.width || n == 0 {
613            return;
614        }
615        let right = bounds_right.min(bounds.width);
616        if pos.x >= right {
617            return;
618        }
619        let n = n.min(right - pos.x);
620
621        let primaries = collect_primaries(self, pos.y, pos.x, right);
622        blank_span(self, pos.y, pos.x, right);
623
624        let cut = pos.x + n;
625        for (src_x, cell) in primaries.iter() {
626            if *src_x < cut {
627                continue;
628            }
629            let dst_x = src_x - n;
630            let cw = (cell.width() as u16).max(1);
631            if dst_x + cw > right {
632                continue;
633            }
634            self.set_cell(Position::new(dst_x, pos.y), cell);
635        }
636
637        fill_span(self, pos.y, right - n, right, fill);
638    }
639}
640
641fn collect_primaries<S: SurfaceMut + ?Sized>(
642    s: &S,
643    y: u16,
644    left: u16,
645    right: u16,
646) -> Vec<(u16, Cell)> {
647    let mut out = Vec::new();
648    let mut x = left;
649    while x < right {
650        if let Some(cell) = s.cell(Position::new(x, y))
651            && !cell.is_continuation()
652        {
653            let cw = (cell.width() as u16).max(1);
654            out.push((x, cell.clone()));
655            x = x.saturating_add(cw);
656            continue;
657        }
658        x = x.saturating_add(1);
659    }
660    out
661}
662
663fn blank_span<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, left: u16, right: u16) {
664    for x in left..right {
665        s.set_cell(Position::new(x, y), &Cell::BLANK);
666    }
667}
668
669fn fill_span<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, left: u16, right: u16, fill: &Cell) {
670    if left >= right {
671        return;
672    }
673    let fill_w = (fill.width() as u16).max(1);
674    let mut col = left;
675    while col + fill_w <= right {
676        s.set_cell(Position::new(col, y), fill);
677        col += fill_w;
678    }
679    while col < right {
680        s.set_cell(Position::new(col, y), &Cell::BLANK);
681        col += 1;
682    }
683}
684
685fn copy_row<S: SurfaceMut + ?Sized>(s: &mut S, src_y: u16, dst_y: u16, width: u16) {
686    let mut x: u16 = 0;
687    while x < width {
688        let src = Position::new(x, src_y);
689        let dst = Position::new(x, dst_y);
690        let Some(cell) = s.cell(src).cloned() else {
691            x += 1;
692            continue;
693        };
694        if cell.is_continuation() {
695            s.set_cell(dst, &Cell::BLANK);
696            x += 1;
697            continue;
698        }
699        let cw = (cell.width() as u16).max(1);
700        if cw > 1 && x + cw > width {
701            s.set_cell(dst, &Cell::BLANK);
702            x += 1;
703        } else {
704            s.set_cell(dst, &cell);
705            x += cw;
706        }
707    }
708}
709
710fn blank_row<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, width: u16, fill: &Cell) {
711    for x in 0..width {
712        s.set_cell(Position::new(x, y), fill);
713    }
714}
715
716impl Bounded for Rect {
717    fn bounds(&self) -> Rect {
718        *self
719    }
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725    use crate::buffer::Buffer;
726
727    fn wide(s: &str) -> Cell {
728        Cell::wide(s)
729    }
730
731    fn cont() -> Cell {
732        Cell::continuation()
733    }
734
735    #[test]
736    fn draw_copies_normal_cells() {
737        let mut src = Buffer::new(2, 1);
738        src.set((0, 0), &Cell::narrow("A"));
739        src.set((1, 0), &Cell::narrow("B"));
740        let mut dst = Buffer::new(4, 1);
741        src.draw(&mut dst, Position::new(1, 0));
742        assert_eq!(dst.cell(Position::new(0, 0)).unwrap().content(), " ");
743        assert_eq!(dst.cell(Position::new(1, 0)).unwrap().content(), "A");
744        assert_eq!(dst.cell(Position::new(2, 0)).unwrap().content(), "B");
745    }
746
747    #[test]
748    fn draw_preserves_wide_pair() {
749        let mut src = Buffer::new(2, 1);
750        src.set((0, 0), &wide("世"));
751        // Buffer::set already wrote the continuation at (1,0).
752        let mut dst = Buffer::new(2, 1);
753        src.draw(&mut dst, Position::new(0, 0));
754        let primary = dst.cell(Position::new(0, 0)).unwrap();
755        let cont_cell = dst.cell(Position::new(1, 0)).unwrap();
756        assert_eq!(primary.content(), "世");
757        assert_eq!(primary.width(), 2);
758        assert!(cont_cell.is_continuation());
759    }
760
761    #[test]
762    fn draw_blanks_leading_continuation_in_source_slice() {
763        // Construct a source whose first column is the orphan
764        // continuation half of a wide cell that lives outside the
765        // slice. The default must not propagate the orphan.
766        let mut src = Buffer::new(2, 1);
767        src.set((0, 0), &cont());
768        src.set((1, 0), &Cell::narrow("X"));
769
770        let mut dst = Buffer::new(2, 1);
771        // Pre-seed target with an unrelated wide cell to make sure
772        // we'd notice a corruption.
773        dst.set((0, 0), &Cell::narrow("Y"));
774        dst.set((1, 0), &Cell::narrow("Z"));
775
776        src.draw(&mut dst, Position::new(0, 0));
777
778        // The orphan continuation became a blank — no spurious
779        // continuation marker carried over.
780        assert!(!dst.cell(Position::new(0, 0)).unwrap().is_continuation());
781        assert_eq!(dst.cell(Position::new(0, 0)).unwrap().content(), " ");
782        assert_eq!(dst.cell(Position::new(1, 0)).unwrap().content(), "X");
783    }
784
785    #[test]
786    fn draw_blanks_wide_primary_with_no_room_in_target() {
787        // Wide primary lands at the last column of the target — its
788        // continuation would fall outside target bounds. Substitute
789        // a blank rather than emitting a half-drawn grapheme.
790        let mut src = Buffer::new(2, 1);
791        src.set((0, 0), &wide("世"));
792
793        let mut dst = Buffer::new(2, 1);
794        src.draw(&mut dst, Position::new(1, 0));
795
796        let landed = dst.cell(Position::new(1, 0)).unwrap();
797        assert_eq!(landed.content(), " ");
798        assert_eq!(landed.width(), 1);
799    }
800
801    // The remaining tests exercise the SurfaceMut default implementations
802    // of insert/delete lines and insert/delete cells via Window, which
803    // does not override them.
804    use crate::buffer::Window;
805
806    fn window_with(content: &[&str]) -> Window {
807        let w = content[0].chars().count() as u16;
808        let h = content.len() as u16;
809        let mut win = Window::new(w, h);
810        for (y, row) in content.iter().enumerate() {
811            for (x, ch) in row.chars().enumerate() {
812                win.set_cell(
813                    Position::new(x as u16, y as u16),
814                    &Cell::narrow(ch.to_string()),
815                );
816            }
817        }
818        win
819    }
820
821    fn row_content(win: &Window, y: u16) -> String {
822        (0..win.bounds().width)
823            .map(|x| {
824                win.cell(Position::new(x, y))
825                    .map(|c| c.content().to_string())
826                    .unwrap_or_default()
827            })
828            .collect()
829    }
830
831    #[test]
832    fn default_insert_lines_shifts_down_and_blanks_top() {
833        let mut win = window_with(&["AAA", "BBB", "CCC", "DDD"]);
834        SurfaceMut::insert_lines(&mut win, 1, 1, 4, &Cell::BLANK);
835        assert_eq!(row_content(&win, 0), "AAA");
836        assert_eq!(row_content(&win, 1), "   ");
837        assert_eq!(row_content(&win, 2), "BBB");
838        assert_eq!(row_content(&win, 3), "CCC");
839    }
840
841    #[test]
842    fn default_delete_lines_pulls_up_and_blanks_bottom() {
843        let mut win = window_with(&["AAA", "BBB", "CCC", "DDD"]);
844        SurfaceMut::delete_lines(&mut win, 1, 1, 4, &Cell::BLANK);
845        assert_eq!(row_content(&win, 0), "AAA");
846        assert_eq!(row_content(&win, 1), "CCC");
847        assert_eq!(row_content(&win, 2), "DDD");
848        assert_eq!(row_content(&win, 3), "   ");
849    }
850
851    #[test]
852    fn default_insert_cells_shifts_right_and_blanks_left() {
853        let mut win = window_with(&["ABCDE"]);
854        SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 2, 5, &Cell::BLANK);
855        assert_eq!(row_content(&win, 0), "A  BC");
856    }
857
858    #[test]
859    fn default_delete_cells_pulls_left_and_blanks_right() {
860        let mut win = window_with(&["ABCDE"]);
861        SurfaceMut::delete_cells(&mut win, Position::new(1, 0), 2, 5, &Cell::BLANK);
862        assert_eq!(row_content(&win, 0), "ADE  ");
863    }
864
865    #[test]
866    fn default_insert_cells_drops_wide_that_no_longer_fits() {
867        // Row: A 世(prim) 世(cont) B, then insert 1 at col 1 with right=4.
868        // 世's new primary would be at col 2, continuation at col 3.
869        // That still fits (col 3 < right=4). So 世 is preserved.
870        let mut win = Window::new(4, 1);
871        win.set_cell(Position::new(0, 0), &Cell::narrow("A"));
872        win.set_cell(Position::new(1, 0), &wide("世"));
873        win.set_cell(Position::new(3, 0), &Cell::narrow("B"));
874        SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 1, 4, &Cell::BLANK);
875
876        assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
877        assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), " ");
878        assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), "世");
879        assert!(win.cell(Position::new(3, 0)).unwrap().is_continuation());
880    }
881
882    #[test]
883    fn default_insert_cells_blanks_wide_that_overflows() {
884        // Row: A B 世(prim) 世(cont), then insert 1 at col 1 with right=4.
885        // 世 would move from col 2 to col 3, continuation to col 4 (out).
886        // So 世 must be dropped, leaving a blank at col 3.
887        let mut win = Window::new(4, 1);
888        win.set_cell(Position::new(0, 0), &Cell::narrow("A"));
889        win.set_cell(Position::new(1, 0), &Cell::narrow("B"));
890        win.set_cell(Position::new(2, 0), &wide("世"));
891        SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 1, 4, &Cell::BLANK);
892
893        assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
894        assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), " ");
895        assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), "B");
896        assert_eq!(win.cell(Position::new(3, 0)).unwrap().content(), " ");
897    }
898
899    #[test]
900    fn default_delete_cells_drops_primary_in_deletion_window() {
901        // Row: A 世(prim) 世(cont) B, delete 2 at col 1 with right=4.
902        // 世's primary falls inside [1, 3) → dropped entirely.
903        let mut win = Window::new(4, 1);
904        win.set_cell(Position::new(0, 0), &Cell::narrow("A"));
905        win.set_cell(Position::new(1, 0), &wide("世"));
906        win.set_cell(Position::new(3, 0), &Cell::narrow("B"));
907        SurfaceMut::delete_cells(&mut win, Position::new(1, 0), 2, 4, &Cell::BLANK);
908
909        assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
910        assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), "B");
911        assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), " ");
912        assert_eq!(win.cell(Position::new(3, 0)).unwrap().content(), " ");
913    }
914}