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}