Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/src/lib_tui/term/screen.rs

51.1 KiB, 129 runs

created by r1870400018:20811, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The grid, the cursor and everything that moves them.
2//!
3//! [`Screen`] owns a rectangle of [`Cell`]s, a cursor, a scrolling region, a set of tab stops and a
4//! bounded scrollback. It knows nothing about escape sequences: the emulator in [`super::emu`]
5//! translates the parser's output into calls on this type, which keeps the grammar of the stream
6//! and the geometry of the screen apart.
7//!
8//! Two conventions are worth stating because they are easy to get wrong.
9//!
10//! The cursor column is always within the grid, even when the last character printed filled the
11//! final column. That case is recorded by [`Cursor::wrap_pending`], which is the deferred wrap
12//! every real terminal implements: printing in the last column leaves the cursor there, and only
13//! the *next* printable character moves to the following row. Without it, a line exactly as wide
14//! as the screen scrolls one row too early.
15//!
16//! Erasure paints with the current background colour, not with the default one. An application
17//! that selects a background and then erases expects to see that background, and does not repaint
18//! it itself.
19//!
20//! Every row records whether the text on it ran into the row below, which is what [`Screen::resize`]
21//! needs in order to put a narrowed window's text back together rather than cutting it off. The
22//! record is written in one place only, where the wrap actually happens, and an erase that reaches
23//! the end of a row takes it away again.
24
25use crate::lib_tui::term::{
26 cell::{
27 Cell,
28 Pen,
29 Wide,
30 },
31 width::{
32 char_width,
33 CharWidth,
34 },
35};
36
37use oxedyne_fe2o3_core::prelude::*;
38use oxedyne_fe2o3_text::unicode::norm;
39
40use std::collections::VecDeque;
41
42
43/// The default number of lines of scrollback.
44pub const DEFAULT_SCROLLBACK: usize = 2000;
45
46/// The interval between the tab stops a reset installs.
47pub const TAB_INTERVAL: usize = 8;
48
49/// Which part of a line or screen an erase covers.
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum Erase {
52 /// From the cursor to the end.
53 ToEnd,
54 /// From the beginning to the cursor, inclusive.
55 ToStart,
56 /// The whole of it.
57 All,
58}
59
60impl Erase {
61 /// The erase named by a sequence parameter, or `None` for a parameter with no meaning.
62 pub fn from_param(p: u32) -> Option<Self> {
63 match p {
64 0 => Some(Self::ToEnd),
65 1 => Some(Self::ToStart),
66 2 => Some(Self::All),
67 _ => None,
68 }
69 }
70}
71
72/// One line of the grid or of the scrollback, and whether it ran into the one below.
73///
74/// The `wrapped` flag is what makes rewrapping on resize possible at all. Without a record of which
75/// rows are continuations of the row above, a terminal narrowing its window can only truncate,
76/// because it cannot tell a paragraph that ran on from two separate lines that happen to be
77/// adjacent. The flag is set where the wrap actually happens, in [`Screen::wrap`], and nowhere else.
78#[derive(Clone, Debug, Default)]
79pub struct Line {
80 /// The cells, which for a wrapped line are exactly as wide as the screen then was.
81 pub cells: Vec<Cell>,
82 /// Whether the text carried on into the line below rather than ending here.
83 pub wrapped: bool,
84}
85
86impl Line {
87 /// A blank line of `cols` cells.
88 pub fn blank(cols: usize) -> Self {
89 Self {
90 cells: vec![Cell::default(); cols],
91 wrapped: false,
92 }
93 }
94
95 /// The text of the line, with trailing blanks removed.
96 pub fn text(&self) -> String {
97 Screen::cells_text(&self.cells)
98 }
99}
100
101/// Which of the two grids is in front.
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum Surface {
104 /// The ordinary screen, which accumulates scrollback.
105 Primary,
106 /// The alternate screen, which does not.
107 Alternate,
108}
109
110/// Where the cursor is and how it behaves.
111#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112pub struct Cursor {
113 /// Row, counted from the top of the grid.
114 pub row: usize,
115 /// Column, always within the grid.
116 pub col: usize,
117 /// Whether the application has asked for the cursor to be drawn.
118 pub visible: bool,
119 /// Whether the last character printed filled the final column, so that the next one wraps.
120 pub wrap_pending: bool,
121}
122
123impl Default for Cursor {
124 fn default() -> Self {
125 Self {
126 row: 0,
127 col: 0,
128 visible: true,
129 wrap_pending: false,
130 }
131 }
132}
133
134impl Cursor {
135 /// The column a terminal would report, which is one past the grid when a wrap is pending.
136 ///
137 /// This is what `DSR` answers and what `tmux` shows in `#{cursor_x}`, and it is the value to
138 /// compare against another terminal.
139 pub fn reported_col(&self) -> usize {
140 self.col + if self.wrap_pending { 1 } else { 0 }
141 }
142}
143
144/// The cursor state a save and restore moves.
145#[derive(Clone, Copy, Debug)]
146struct SavedCursor {
147 cursor: Cursor,
148 pen: Pen,
149 origin: bool,
150}
151
152/// What has changed since a renderer last looked.
153///
154/// Redrawing a whole grid for every byte that arrives is what makes a terminal in a browser feel
155/// slow, so the model records the rows it touched and leaves the rest alone. A renderer clears the
156/// record with [`Screen::clear_damage`] once it has drawn.
157#[derive(Clone, Debug, Default)]
158pub struct Damage {
159 /// Whether each row needs redrawing.
160 rows: Vec<bool>,
161 /// Whether the whole surface must be redrawn, as after a resize or a buffer switch.
162 all: bool,
163 /// Whether the cursor moved or changed visibility.
164 cursor: bool,
165 /// How many lines have left the top of the screen into scrollback.
166 scrolled: usize,
167}
168
169impl Damage {
170 /// A record covering `rows` rows, with everything marked as needing a first draw.
171 pub fn new(rows: usize) -> Self {
172 Self {
173 rows: vec![true; rows],
174 all: true,
175 cursor: true,
176 scrolled: 0,
177 }
178 }
179
180 /// Whether anything at all changed.
181 pub fn any(&self) -> bool {
182 self.all || self.cursor || self.rows.iter().any(|d| *d)
183 }
184
185 /// Whether the whole surface must be redrawn.
186 pub fn is_all(&self) -> bool {
187 self.all
188 }
189
190 /// Whether the given row must be redrawn.
191 pub fn is_row(&self, row: usize) -> bool {
192 self.all || self.rows.get(row).copied().unwrap_or(false)
193 }
194
195 /// The rows that must be redrawn, in order.
196 pub fn dirty_rows(&self) -> Vec<usize> {
197 let mut out = Vec::new();
198 for (i, d) in self.rows.iter().enumerate() {
199 if self.all || *d {
200 out.push(i);
201 }
202 }
203 out
204 }
205
206 /// Whether the cursor moved or changed visibility.
207 pub fn cursor_moved(&self) -> bool {
208 self.cursor
209 }
210
211 /// How many lines left the top of the screen into scrollback, which a renderer holding its own
212 /// copy of the scrollback uses to know how many to collect.
213 pub fn scrolled(&self) -> usize {
214 self.scrolled
215 }
216}
217
218/// A terminal screen: a grid of cells, a cursor and a bounded scrollback.
219#[derive(Clone, Debug)]
220pub struct Screen {
221 /// Width in cells.
222 cols: usize,
223 /// Height in cells.
224 rows: usize,
225 /// The visible grid, `rows` by `cols`, in row major order.
226 grid: Vec<Cell>,
227 /// Whether each row of the visible grid ran into the row below.
228 wrapped: Vec<bool>,
229 /// The grid that is not in front, held while the other is shown.
230 spare: Vec<Cell>,
231 /// Whether each row of the grid that is not in front ran into the row below.
232 spare_wrapped: Vec<bool>,
233 /// Which grid is in front.
234 surface: Surface,
235 /// The cursor.
236 cursor: Cursor,
237 /// The pen printing uses.
238 pen: Pen,
239 /// The cursor saved by `DECSC` or by the switch to the alternate screen.
240 saved: Option<SavedCursor>,
241 /// The cursor of the grid that is not in front.
242 spare_cursor: Cursor,
243 /// The first and last rows of the scrolling region, inclusive.
244 region: (usize, usize),
245 /// Whether a tab stop is set at each column.
246 tabs: Vec<bool>,
247 /// Whether printing past the final column wraps.
248 autowrap: bool,
249 /// Whether printing pushes the rest of the line to the right.
250 insert: bool,
251 /// Whether row addressing is relative to the scrolling region.
252 origin: bool,
253 /// Lines that have left the top of the screen.
254 scrollback: VecDeque<Line>,
255 /// The most lines of scrollback that will be kept.
256 scrollback_max: usize,
257 /// How many lines of scrollback the viewport is shown above the live screen, where zero is the
258 /// live screen itself.
259 view: usize,
260 /// What has changed since a renderer last looked.
261 damage: Damage,
262}
263
264impl Screen {
265
266 // ┌─────────────────────────────┐
267 // │ CONSTRUCTION │
268 // └─────────────────────────────┘
269
270 /// A blank screen of the given size with the default scrollback bound.
271 ///
272 /// # Errors
273 /// Fails if either dimension is zero.
274 pub fn new(cols: usize, rows: usize) -> Outcome<Self> {
275 Self::with_scrollback(cols, rows, DEFAULT_SCROLLBACK)
276 }
277
278 /// A blank screen of the given size, keeping at most `scrollback_max` lines of scrollback.
279 ///
280 /// # Errors
281 /// Fails if either dimension is zero.
282 pub fn with_scrollback(cols: usize, rows: usize, scrollback_max: usize) -> Outcome<Self> {
283 if cols == 0 || rows == 0 {
284 return Err(err!(
285 "A screen must have at least one row and one column, was given {} by {}.",
286 cols, rows;
287 Invalid, Input));
288 }
289 Ok(Self {
290 cols,
291 rows,
292 grid: vec![Cell::default(); cols * rows],
293 wrapped: vec![false; rows],
294 spare: Vec::new(),
295 spare_wrapped: Vec::new(),
296 surface: Surface::Primary,
297 cursor: Cursor::default(),
298 pen: Pen::default(),
299 saved: None,
300 spare_cursor: Cursor::default(),
301 region: (0, rows - 1),
302 tabs: Self::default_tabs(cols),
303 autowrap: true,
304 insert: false,
305 origin: false,
306 scrollback: VecDeque::new(),
307 scrollback_max,
308 view: 0,
309 damage: Damage::new(rows),
310 })
311 }
312
313 /// The tab stops a reset installs: every [`TAB_INTERVAL`] columns.
314 fn default_tabs(cols: usize) -> Vec<bool> {
315 let mut tabs = vec![false; cols];
316 let mut i = TAB_INTERVAL;
317 while i < cols {
318 tabs[i] = true;
319 i += TAB_INTERVAL;
320 }
321 tabs
322 }
323
324 // ┌─────────────────────────────┐
325 // │ INSPECTION │
326 // └─────────────────────────────┘
327
328 /// Width in cells.
329 pub fn cols(&self) -> usize {
330 self.cols
331 }
332
333 /// Height in cells.
334 pub fn rows(&self) -> usize {
335 self.rows
336 }
337
338 /// The cursor.
339 pub fn cursor(&self) -> &Cursor {
340 &self.cursor
341 }
342
343 /// The pen printing currently uses.
344 pub fn pen(&self) -> &Pen {
345 &self.pen
346 }
347
348 /// Which grid is in front.
349 pub fn surface(&self) -> Surface {
350 self.surface
351 }
352
353 /// The first and last rows of the scrolling region, inclusive.
354 pub fn region(&self) -> (usize, usize) {
355 self.region
356 }
357
358 /// Whether printing past the final column wraps.
359 pub fn autowrap(&self) -> bool {
360 self.autowrap
361 }
362
363 /// The cells of a visible row, or `None` if the row is off the grid.
364 pub fn row(&self, row: usize) -> Option<&[Cell]> {
365 if row >= self.rows {
366 return None;
367 }
368 let a = row * self.cols;
369 Some(&self.grid[a..a + self.cols])
370 }
371
372 /// Whether a visible row ran into the row below rather than ending there.
373 pub fn row_wrapped(&self, row: usize) -> bool {
374 self.wrapped.get(row).copied().unwrap_or(false)
375 }
376
377 /// One cell, or `None` if it is off the grid.
378 pub fn cell(&self, col: usize, row: usize) -> Option<&Cell> {
379 if col >= self.cols || row >= self.rows {
380 return None;
381 }
382 self.grid.get(row * self.cols + col)
383 }
384
385 /// The text of a visible row, with trailing blanks removed and the right hand halves of wide
386 /// characters omitted.
387 pub fn row_text(&self, row: usize) -> String {
388 match self.row(row) {
389 Some(cells) => Self::cells_text(cells),
390 None => fmt!(""),
391 }
392 }
393
394 /// The text of the whole visible grid, one row per line, with trailing blanks removed.
395 pub fn text(&self) -> String {
396 let mut out = String::new();
397 for r in 0..self.rows {
398 if r > 0 {
399 out.push('\n');
400 }
401 out.push_str(&self.row_text(r));
402 }
403 out
404 }
405
406 /// The text of a run of cells, with trailing blanks removed.
407 pub fn cells_text(cells: &[Cell]) -> String {
408 let mut end = cells.len();
409 while end > 0
410 && cells[end - 1].chr == ' '
411 && (cells[end - 1].wide == Wide::No || cells[end - 1].wide == Wide::Filler)
412 {
413 end -= 1;
414 }
415 let mut out = String::new();
416 for cell in &cells[..end] {
417 match cell.wide {
418 // Neither the right hand half of a wide character nor a leftover column at the
419 // end of a row carries text of its own.
420 Wide::Trail => {}
421 Wide::Filler => out.push(' '),
422 _ => out.push(cell.chr),
423 }
424 }
425 out
426 }
427
428 /// How many lines of scrollback are held.
429 pub fn scrollback_len(&self) -> usize {
430 self.scrollback.len()
431 }
432
433 /// The most lines of scrollback that will be kept.
434 pub fn scrollback_max(&self) -> usize {
435 self.scrollback_max
436 }
437
438 /// A line of scrollback, counting zero as the oldest.
439 ///
440 /// Scrollback lines have their trailing blanks trimmed when they are stored, so the slice
441 /// returned is usually shorter than the screen is wide, and is never longer than the screen was
442 /// wide when the line left it.
443 pub fn scrollback_line(&self, i: usize) -> Option<&[Cell]> {
444 self.scrollback.get(i).map(|l| l.cells.as_slice())
445 }
446
447 /// The text of a line of scrollback, counting zero as the oldest.
448 pub fn scrollback_text(&self, i: usize) -> Option<String> {
449 self.scrollback.get(i).map(|l| Self::cells_text(&l.cells))
450 }
451
452 /// Whether a line of scrollback ran into the line below rather than ending there.
453 pub fn scrollback_wrapped(&self, i: usize) -> bool {
454 self.scrollback.get(i).map(|l| l.wrapped).unwrap_or(false)
455 }
456
457 /// How many lines of scrollback the viewport is shown above the live screen.
458 ///
459 /// Zero is the live screen. The value never exceeds [`Screen::scrollback_len`], and a resize
460 /// keeps the viewport on the same text rather than on the same number.
461 pub fn view_offset(&self) -> usize {
462 self.view
463 }
464
465 /// Scrolls the viewport back by `n` lines, clamped to what the scrollback holds.
466 pub fn set_view_offset(&mut self, n: usize) {
467 let want = n.min(self.scrollback.len());
468 if want != self.view {
469 self.view = want;
470 self.touch_all();
471 }
472 }
473
474 /// The cells of a row of the viewport, which is the live screen unless the view is scrolled
475 /// back, or `None` if the row is off the viewport.
476 pub fn view_row(&self, row: usize) -> Option<&[Cell]> {
477 if row >= self.rows {
478 return None;
479 }
480 if row < self.view {
481 // A row from the scrollback, counting up from the oldest line still shown.
482 let i = self.scrollback.len() - self.view + row;
483 return self.scrollback.get(i).map(|l| l.cells.as_slice());
484 }
485 self.row(row - self.view)
486 }
487
488 /// The text of a row of the viewport, with trailing blanks removed.
489 pub fn view_row_text(&self, row: usize) -> String {
490 match self.view_row(row) {
491 Some(cells) => Self::cells_text(cells),
492 None => fmt!(""),
493 }
494 }
495
496 /// What has changed since a renderer last looked.
497 pub fn damage(&self) -> &Damage {
498 &self.damage
499 }
500
501 /// Declares the screen drawn, so that damage accumulates afresh.
502 pub fn clear_damage(&mut self) {
503 for d in self.damage.rows.iter_mut() {
504 *d = false;
505 }
506 self.damage.all = false;
507 self.damage.cursor = false;
508 self.damage.scrolled = 0;
509 }
510
511 // ┌─────────────────────────────┐
512 // │ DAMAGE │
513 // └─────────────────────────────┘
514
515 /// Marks a row as needing redrawing.
516 fn touch(&mut self, row: usize) {
517 if let Some(d) = self.damage.rows.get_mut(row) {
518 *d = true;
519 }
520 }
521
522 /// Marks every row as needing redrawing.
523 fn touch_all(&mut self) {
524 self.damage.all = true;
525 }
526
527 /// Marks the cursor as moved.
528 fn touch_cursor(&mut self) {
529 self.damage.cursor = true;
530 }
531
532 // ┌─────────────────────────────┐
533 // │ PEN │
534 // └─────────────────────────────┘
535
536 /// Sets the pen printing uses.
537 pub fn set_pen(&mut self, pen: Pen) {
538 self.pen = pen;
539 }
540
541 /// The pen an erase paints with: the current background, and nothing else.
542 fn erase_pen(&self) -> Pen {
543 Pen {
544 fore: Default::default(),
545 back: self.pen.back,
546 attrs: Default::default(),
547 }
548 }
549
550 // ┌─────────────────────────────┐
551 // │ PRINTING │
552 // └─────────────────────────────┘
553
554 /// Places a character at the cursor and advances.
555 ///
556 /// A zero width character composes with the cell before the cursor where a composed form
557 /// exists, and is otherwise dropped; the grid holds one character per cell, and a cell wide
558 /// enough to hold an arbitrary sequence of combining marks would multiply the cost of the
559 /// scrollback several times over for a case that a terminal rarely meets.
560 pub fn print(&mut self, c: char) {
561 match char_width(c) {
562 CharWidth::Zero => self.combine(c),
563 CharWidth::Narrow => self.place(c, 1),
564 CharWidth::Wide => self.place(c, 2),
565 }
566 }
567
568 /// Composes a combining mark into the cell before the cursor.
569 fn combine(&mut self, mark: char) {
570 let col = if self.cursor.wrap_pending {
571 self.cursor.col
572 } else if self.cursor.col > 0 {
573 self.cursor.col - 1
574 } else {
575 return;
576 };
577 let row = self.cursor.row;
578 // Step back over the right hand half of a wide character.
579 let col = match self.cell(col, row) {
580 Some(cell) if cell.wide == Wide::Trail && col > 0 => col - 1,
581 _ => col,
582 };
583 let base = match self.cell(col, row) {
584 Some(cell) => cell.chr,
585 None => return,
586 };
587 if let Some(composed) = compose(base, mark) {
588 let i = row * self.cols + col;
589 if let Some(cell) = self.grid.get_mut(i) {
590 cell.chr = composed;
591 }
592 self.touch(row);
593 }
594 }
595
596 /// Places a character of the given cell width at the cursor and advances.
597 fn place(&mut self, c: char, n: usize) {
598 if n > self.cols {
599 return;
600 }
601 if self.cursor.wrap_pending && self.autowrap {
602 self.wrap();
603 }
604 if self.cursor.col + n > self.cols {
605 if self.autowrap {
606 // The character will not fit in the columns left, so those columns are marked
607 // as the padding they are and the character starts the next row whole. Without
608 // the mark a later rewrap cannot tell them from spaces somebody printed.
609 self.pad_to_edge();
610 self.wrap();
611 } else {
612 self.cursor.col = self.cols - n;
613 }
614 }
615 if self.insert {
616 self.insert_cells(n);
617 }
618 let row = self.cursor.row;
619 let col = self.cursor.col;
620 self.clear_overlapped(col, n);
621 let pen = self.pen;
622 let base = row * self.cols + col;
623 if let Some(cell) = self.grid.get_mut(base) {
624 *cell = Cell {
625 chr: c,
626 pen,
627 wide: if n == 2 { Wide::Lead } else { Wide::No },
628 };
629 }
630 if n == 2 {
631 if let Some(cell) = self.grid.get_mut(base + 1) {
632 *cell = Cell {
633 chr: ' ',
634 pen,
635 wide: Wide::Trail,
636 };
637 }
638 }
639 self.touch(row);
640 self.cursor.col += n;
641 if self.cursor.col >= self.cols {
642 self.cursor.col = self.cols - 1;
643 self.cursor.wrap_pending = self.autowrap;
644 } else {
645 self.cursor.wrap_pending = false;
646 }
647 self.touch_cursor();
648 }
649
650 /// Marks the columns from the cursor to the end of the row as leftover padding.
651 fn pad_to_edge(&mut self) {
652 let row = self.cursor.row;
653 let base = row * self.cols;
654 for col in self.cursor.col..self.cols {
655 if let Some(cell) = self.grid.get_mut(base + col) {
656 *cell = Cell::filler(cell.pen);
657 }
658 }
659 self.touch(row);
660 }
661
662 /// Blanks the other half of any wide character the write at `col` would cut in two.
663 fn clear_overlapped(&mut self, col: usize, n: usize) {
664 let row = self.cursor.row;
665 let pen = self.erase_pen();
666 // The cell to the left, if this write lands on the right hand half of a wide character.
667 if col > 0 {
668 let left = row * self.cols + col - 1;
669 if self.grid.get(left).map(|c| c.wide) == Some(Wide::Lead) {
670 if let Some(cell) = self.grid.get_mut(left) {
671 *cell = Cell::blank(pen);
672 }
673 }
674 }
675 // The cell to the right, if this write covers the left hand half of a wide character.
676 let last = col + n;
677 if last < self.cols {
678 let right = row * self.cols + last;
679 if self.grid.get(right).map(|c| c.wide) == Some(Wide::Trail) {
680 if let Some(cell) = self.grid.get_mut(right) {
681 *cell = Cell::blank(pen);
682 }
683 }
684 }
685 }
686
687 /// Moves to the start of the next line, scrolling if the cursor is at the foot of the region.
688 ///
689 /// This is the only place a row is marked as running into the row below, and that mark is what
690 /// lets a later resize put the text back together.
691 fn wrap(&mut self) {
692 self.cursor.wrap_pending = false;
693 self.cursor.col = 0;
694 if let Some(w) = self.wrapped.get_mut(self.cursor.row) {
695 *w = true;
696 }
697 self.line_feed_inner();
698 }
699
700 // ┌─────────────────────────────┐
701 // │ CONTROLS │
702 // └─────────────────────────────┘
703
704 /// Moves the cursor one cell left, without wrapping to the previous line.
705 pub fn backspace(&mut self) {
706 if self.cursor.wrap_pending {
707 self.cursor.wrap_pending = false;
708 } else if self.cursor.col > 0 {
709 self.cursor.col -= 1;
710 }
711 self.touch_cursor();
712 }
713
714 /// Moves the cursor to the first column.
715 pub fn carriage_return(&mut self) {
716 self.cursor.col = 0;
717 self.cursor.wrap_pending = false;
718 self.touch_cursor();
719 }
720
721 /// Moves the cursor down one row, scrolling if it is at the foot of the region.
722 pub fn line_feed(&mut self) {
723 self.cursor.wrap_pending = false;
724 self.line_feed_inner();
725 }
726
727 /// The row advance of a line feed, without touching the pending wrap.
728 fn line_feed_inner(&mut self) {
729 if self.cursor.row == self.region.1 {
730 self.scroll_up(1);
731 } else if self.cursor.row + 1 < self.rows {
732 self.cursor.row += 1;
733 }
734 self.touch_cursor();
735 }
736
737 /// Moves the cursor to the first column of the next row, scrolling if need be.
738 pub fn next_line(&mut self) {
739 self.carriage_return();
740 self.line_feed();
741 }
742
743 /// Moves the cursor up one row, scrolling the region down if it is at the head of it.
744 pub fn reverse_index(&mut self) {
745 self.cursor.wrap_pending = false;
746 if self.cursor.row == self.region.0 {
747 self.scroll_down(1);
748 } else if self.cursor.row > 0 {
749 self.cursor.row -= 1;
750 }
751 self.touch_cursor();
752 }
753
754 /// Moves the cursor to the next tab stop, or to the final column if there is none.
755 pub fn tab(&mut self) {
756 let mut col = self.cursor.col + 1;
757 while col < self.cols && !self.tabs[col] {
758 col += 1;
759 }
760 self.cursor.col = col.min(self.cols - 1);
761 self.cursor.wrap_pending = false;
762 self.touch_cursor();
763 }
764
765 /// Moves the cursor forward `n` tab stops.
766 pub fn tab_forward(&mut self, n: usize) {
767 for _ in 0..n.max(1) {
768 self.tab();
769 }
770 }
771
772 /// Moves the cursor back `n` tab stops.
773 pub fn tab_back(&mut self, n: usize) {
774 for _ in 0..n.max(1) {
775 let mut col = self.cursor.col;
776 loop {
777 if col == 0 {
778 break;
779 }
780 col -= 1;
781 if self.tabs[col] {
782 break;
783 }
784 }
785 self.cursor.col = col;
786 }
787 self.cursor.wrap_pending = false;
788 self.touch_cursor();
789 }
790
791 /// Sets a tab stop at the cursor.
792 pub fn set_tab(&mut self) {
793 if let Some(t) = self.tabs.get_mut(self.cursor.col) {
794 *t = true;
795 }
796 }
797
798 /// Clears the tab stop at the cursor.
799 pub fn clear_tab(&mut self) {
800 if let Some(t) = self.tabs.get_mut(self.cursor.col) {
801 *t = false;
802 }
803 }
804
805 /// Clears every tab stop.
806 pub fn clear_all_tabs(&mut self) {
807 for t in self.tabs.iter_mut() {
808 *t = false;
809 }
810 }
811
812 // ┌─────────────────────────────┐
813 // │ CURSOR MOVEMENT │
814 // └─────────────────────────────┘
815
816 /// Moves the cursor to an absolute position, clamped to the grid.
817 ///
818 /// With origin mode set, the row is counted from the head of the scrolling region and cannot
819 /// leave it.
820 pub fn move_to(&mut self, col: usize, row: usize) {
821 let (lo, hi) = if self.origin {
822 self.region
823 } else {
824 (0, self.rows - 1)
825 };
826 self.cursor.row = (lo + row).min(hi);
827 self.cursor.col = col.min(self.cols - 1);
828 self.cursor.wrap_pending = false;
829 self.touch_cursor();
830 }
831
832 /// Moves the cursor to a column, leaving the row alone.
833 pub fn move_to_col(&mut self, col: usize) {
834 self.cursor.col = col.min(self.cols - 1);
835 self.cursor.wrap_pending = false;
836 self.touch_cursor();
837 }
838
839 /// Moves the cursor to a row, leaving the column alone.
840 pub fn move_to_row(&mut self, row: usize) {
841 let (lo, hi) = if self.origin {
842 self.region
843 } else {
844 (0, self.rows - 1)
845 };
846 self.cursor.row = (lo + row).min(hi);
847 self.cursor.wrap_pending = false;
848 self.touch_cursor();
849 }
850
851 /// Moves the cursor up, stopping at the head of the region or of the grid.
852 pub fn move_up(&mut self, n: usize) {
853 let lo = if self.cursor.row >= self.region.0 { self.region.0 } else { 0 };
854 self.cursor.row = self.cursor.row.saturating_sub(n.max(1)).max(lo);
855 self.cursor.wrap_pending = false;
856 self.touch_cursor();
857 }
858
859 /// Moves the cursor down, stopping at the foot of the region or of the grid.
860 pub fn move_down(&mut self, n: usize) {
861 let hi = if self.cursor.row <= self.region.1 { self.region.1 } else { self.rows - 1 };
862 self.cursor.row = (self.cursor.row + n.max(1)).min(hi);
863 self.cursor.wrap_pending = false;
864 self.touch_cursor();
865 }
866
867 /// Moves the cursor left, stopping at the first column.
868 pub fn move_left(&mut self, n: usize) {
869 self.cursor.col = self.cursor.col.saturating_sub(n.max(1));
870 self.cursor.wrap_pending = false;
871 self.touch_cursor();
872 }
873
874 /// Moves the cursor right, stopping at the final column.
875 pub fn move_right(&mut self, n: usize) {
876 self.cursor.col = (self.cursor.col + n.max(1)).min(self.cols - 1);
877 self.cursor.wrap_pending = false;
878 self.touch_cursor();
879 }
880
881 /// Shows or hides the cursor.
882 pub fn set_cursor_visible(&mut self, visible: bool) {
883 self.cursor.visible = visible;
884 self.touch_cursor();
885 }
886
887 /// Saves the cursor, the pen and origin mode.
888 pub fn save_cursor(&mut self) {
889 self.saved = Some(SavedCursor {
890 cursor: self.cursor,
891 pen: self.pen,
892 origin: self.origin,
893 });
894 }
895
896 /// Restores what [`Screen::save_cursor`] saved, or homes the cursor if nothing was saved.
897 pub fn restore_cursor(&mut self) {
898 match self.saved {
899 Some(s) => {
900 self.cursor = s.cursor;
901 self.cursor.row = self.cursor.row.min(self.rows - 1);
902 self.cursor.col = self.cursor.col.min(self.cols - 1);
903 self.pen = s.pen;
904 self.origin = s.origin;
905 }
906 None => {
907 self.cursor.row = 0;
908 self.cursor.col = 0;
909 self.cursor.wrap_pending = false;
910 }
911 }
912 self.touch_cursor();
913 }
914
915 // ┌─────────────────────────────┐
916 // │ MODES │
917 // └─────────────────────────────┘
918
919 /// Sets whether printing past the final column wraps.
920 pub fn set_autowrap(&mut self, on: bool) {
921 self.autowrap = on;
922 if !on {
923 self.cursor.wrap_pending = false;
924 }
925 }
926
927 /// Sets whether printing pushes the rest of the line to the right.
928 pub fn set_insert(&mut self, on: bool) {
929 self.insert = on;
930 }
931
932 /// Sets whether row addressing is relative to the scrolling region, homing the cursor.
933 pub fn set_origin(&mut self, on: bool) {
934 self.origin = on;
935 self.move_to(0, 0);
936 }
937
938 /// Sets the scrolling region and homes the cursor, as `DECSTBM` requires.
939 ///
940 /// A region of fewer than two rows, or one that does not fit, is refused and the region is set
941 /// to the whole screen instead, which is what the standard asks for.
942 pub fn set_region(&mut self, top: usize, bottom: usize) {
943 if top < bottom && bottom < self.rows {
944 self.region = (top, bottom);
945 } else {
946 self.region = (0, self.rows - 1);
947 }
948 self.move_to(0, 0);
949 }
950
951 // ┌─────────────────────────────┐
952 // │ ERASING │
953 // └─────────────────────────────┘
954
955 /// Erases part or all of the cursor's row.
956 pub fn erase_line(&mut self, what: Erase) {
957 let row = self.cursor.row;
958 let (a, b) = match what {
959 Erase::ToEnd => (self.cursor.col, self.cols),
960 Erase::ToStart => (0, self.cursor.col + 1),
961 Erase::All => (0, self.cols),
962 };
963 self.blank_span(row, a, b.min(self.cols));
964 self.cursor.wrap_pending = false;
965 self.touch(row);
966 }
967
968 /// Erases part or all of the grid.
969 pub fn erase_display(&mut self, what: Erase) {
970 match what {
971 Erase::ToEnd => {
972 let row = self.cursor.row;
973 let col = self.cursor.col;
974 self.blank_span(row, col, self.cols);
975 for r in row + 1..self.rows {
976 self.blank_span(r, 0, self.cols);
977 }
978 }
979 Erase::ToStart => {
980 let row = self.cursor.row;
981 let col = self.cursor.col;
982 for r in 0..row {
983 self.blank_span(r, 0, self.cols);
984 }
985 self.blank_span(row, 0, (col + 1).min(self.cols));
986 }
987 Erase::All => {
988 for r in 0..self.rows {
989 self.blank_span(r, 0, self.cols);
990 }
991 }
992 }
993 self.cursor.wrap_pending = false;
994 self.touch_all();
995 }
996
997 /// Erases `n` cells from the cursor without moving it.
998 pub fn erase_chars(&mut self, n: usize) {
999 let row = self.cursor.row;
1000 let a = self.cursor.col;
1001 let b = (a + n.max(1)).min(self.cols);
1002 self.blank_span(row, a, b);
1003 self.touch(row);
1004 }
1005
1006 /// Discards the scrollback.
1007 pub fn erase_scrollback(&mut self) {
1008 self.scrollback.clear();
1009 }
1010
1011 /// Blanks the cells of `row` in `[a, b)` with the erase pen.
1012 ///
1013 /// An erase that reaches the end of the row also ends it: whatever ran on from it is no longer
1014 /// the same text, so the row stops being a continuation. tmux does the same, which is why
1015 /// erasing a line and then widening the window does not glue the remains back together.
1016 fn blank_span(&mut self, row: usize, a: usize, b: usize) {
1017 let pen = self.erase_pen();
1018 let base = row * self.cols;
1019 for col in a..b {
1020 if let Some(cell) = self.grid.get_mut(base + col) {
1021 *cell = Cell::blank(pen);
1022 }
1023 }
1024 if b >= self.cols {
1025 if let Some(w) = self.wrapped.get_mut(row) {
1026 *w = false;
1027 }
1028 }
1029 self.touch(row);
1030 }
1031
1032 // ┌─────────────────────────────┐
1033 // │ INSERTION AND DELETION │
1034 // └─────────────────────────────┘
1035
1036 /// Inserts `n` blank cells at the cursor, pushing the rest of the row right.
1037 pub fn insert_chars(&mut self, n: usize) {
1038 self.insert_cells(n.max(1));
1039 let row = self.cursor.row;
1040 self.touch(row);
1041 }
1042
1043 /// The cell shuffle behind an insertion.
1044 fn insert_cells(&mut self, n: usize) {
1045 let row = self.cursor.row;
1046 let col = self.cursor.col;
1047 let n = n.min(self.cols - col);
1048 let base = row * self.cols;
1049 let mut i = self.cols;
1050 while i > col + n {
1051 i -= 1;
1052 self.grid[base + i] = self.grid[base + i - n];
1053 }
1054 let pen = self.erase_pen();
1055 for c in col..col + n {
1056 self.grid[base + c] = Cell::blank(pen);
1057 }
1058 self.touch(row);
1059 }
1060
1061 /// Deletes `n` cells at the cursor, pulling the rest of the row left.
1062 pub fn delete_chars(&mut self, n: usize) {
1063 let row = self.cursor.row;
1064 let col = self.cursor.col;
1065 let n = n.max(1).min(self.cols - col);
1066 let base = row * self.cols;
1067 for i in col..self.cols - n {
1068 self.grid[base + i] = self.grid[base + i + n];
1069 }
1070 let pen = self.erase_pen();
1071 for i in self.cols - n..self.cols {
1072 self.grid[base + i] = Cell::blank(pen);
1073 }
1074 self.touch(row);
1075 }
1076
1077 /// Inserts `n` blank rows at the cursor, pushing the rest of the region down.
1078 ///
1079 /// Nothing happens if the cursor is outside the scrolling region, which is what the standard
1080 /// requires and what stops a full screen application corrupting a status line.
1081 pub fn insert_lines(&mut self, n: usize) {
1082 let row = self.cursor.row;
1083 if row < self.region.0 || row > self.region.1 {
1084 return;
1085 }
1086 let n = n.max(1).min(self.region.1 - row + 1);
1087 self.shift_rows_down(row, self.region.1, n);
1088 self.cursor.col = 0;
1089 self.cursor.wrap_pending = false;
1090 self.touch_all();
1091 }
1092
1093 /// Deletes `n` rows at the cursor, pulling the rest of the region up.
1094 pub fn delete_lines(&mut self, n: usize) {
1095 let row = self.cursor.row;
1096 if row < self.region.0 || row > self.region.1 {
1097 return;
1098 }
1099 let n = n.max(1).min(self.region.1 - row + 1);
1100 self.shift_rows_up(row, self.region.1, n, false);
1101 self.cursor.col = 0;
1102 self.cursor.wrap_pending = false;
1103 self.touch_all();
1104 }
1105
1106 // ┌─────────────────────────────┐
1107 // │ SCROLLING │
1108 // └─────────────────────────────┘
1109
1110 /// Scrolls the region up `n` rows, keeping what leaves the top if it is also the top of the
1111 /// screen.
1112 pub fn scroll_up(&mut self, n: usize) {
1113 let n = n.max(1);
1114 let keep = self.surface == Surface::Primary && self.region.0 == 0;
1115 self.shift_rows_up(self.region.0, self.region.1, n, keep);
1116 self.touch_all();
1117 }
1118
1119 /// Scrolls the region down `n` rows, discarding what leaves the foot.
1120 pub fn scroll_down(&mut self, n: usize) {
1121 let n = n.max(1);
1122 self.shift_rows_down(self.region.0, self.region.1, n);
1123 self.touch_all();
1124 }
1125
1126 /// Moves rows `[top, bottom]` up by `n`, blanking the rows uncovered at the foot.
1127 ///
1128 /// When `keep` is set, the rows that leave the top are appended to the scrollback.
1129 fn shift_rows_up(&mut self, top: usize, bottom: usize, n: usize, keep: bool) {
1130 let span = bottom - top + 1;
1131 let n = n.min(span);
1132 if keep {
1133 for r in top..top + n {
1134 let a = r * self.cols;
1135 let line = Line {
1136 cells: self.grid[a..a + self.cols].to_vec(),
1137 wrapped: self.row_wrapped(r),
1138 };
1139 self.push_scrollback(line);
1140 }
1141 }
1142 for r in top..=bottom - n {
1143 let dst = r * self.cols;
1144 let src = (r + n) * self.cols;
1145 for c in 0..self.cols {
1146 self.grid[dst + c] = self.grid[src + c];
1147 }
1148 self.wrapped[r] = self.wrapped[r + n];
1149 }
1150 let pen = self.erase_pen();
1151 for r in bottom + 1 - n..=bottom {
1152 let a = r * self.cols;
1153 for c in 0..self.cols {
1154 self.grid[a + c] = Cell::blank(pen);
1155 }
1156 self.wrapped[r] = false;
1157 }
1158 }
1159
1160 /// Moves rows `[top, bottom]` down by `n`, blanking the rows uncovered at the head.
1161 fn shift_rows_down(&mut self, top: usize, bottom: usize, n: usize) {
1162 let span = bottom - top + 1;
1163 let n = n.min(span);
1164 let mut r = bottom + 1;
1165 while r > top + n {
1166 r -= 1;
1167 let dst = r * self.cols;
1168 let src = (r - n) * self.cols;
1169 for c in 0..self.cols {
1170 self.grid[dst + c] = self.grid[src + c];
1171 }
1172 self.wrapped[r] = self.wrapped[r - n];
1173 }
1174 let pen = self.erase_pen();
1175 for r in top..top + n {
1176 let a = r * self.cols;
1177 for c in 0..self.cols {
1178 self.grid[a + c] = Cell::blank(pen);
1179 }
1180 self.wrapped[r] = false;
1181 }
1182 }
1183
1184 /// Appends a line to the scrollback, trimming its trailing blanks and evicting the oldest line
1185 /// once the bound is reached.
1186 ///
1187 /// A wrapped line keeps its trailing blanks. Its length is the width the screen had when it was
1188 /// stored, and a rewrap needs that width in order to put the text back together: a wrapped line
1189 /// whose last cells happen to be spaces would otherwise lose those columns when it is joined to
1190 /// the line below.
1191 fn push_scrollback(&mut self, mut line: Line) {
1192 if self.scrollback_max == 0 {
1193 self.damage.scrolled += 1;
1194 return;
1195 }
1196 if !line.wrapped {
1197 while line.cells.len() > 0 {
1198 let last = line.cells.len() - 1;
1199 if line.cells[last].is_blank() {
1200 line.cells.truncate(last);
1201 } else {
1202 break;
1203 }
1204 }
1205 }
1206 line.cells.shrink_to_fit();
1207 while self.scrollback.len() >= self.scrollback_max {
1208 self.scrollback.pop_front();
1209 self.view = self.view.saturating_sub(1);
1210 }
1211 self.scrollback.push_back(line);
1212 if self.view > 0 {
1213 // The viewport stays on the text it was showing rather than sliding with the screen.
1214 self.view += 1;
1215 self.view = self.view.min(self.scrollback.len());
1216 }
1217 self.damage.scrolled += 1;
1218 }
1219
1220 // ┌─────────────────────────────┐
1221 // │ SURFACES │
1222 // └─────────────────────────────┘
1223
1224 /// Switches between the ordinary and the alternate grid.
1225 ///
1226 /// When `save` is set, as it is for the `?1049` form, the cursor and pen are saved on the way in
1227 /// and restored on the way out, and the alternate grid is cleared as it comes forward. The
1228 /// alternate grid never contributes to the scrollback, which is the whole point of it: an
1229 /// editor that takes over the screen should leave the log of the session it interrupted intact.
1230 pub fn set_surface(&mut self, want: Surface, save: bool) {
1231 if want == self.surface {
1232 return;
1233 }
1234 if self.spare.len() != self.grid.len() {
1235 self.spare = vec![Cell::default(); self.grid.len()];
1236 }
1237 if self.spare_wrapped.len() != self.wrapped.len() {
1238 self.spare_wrapped = vec![false; self.wrapped.len()];
1239 }
1240 std::mem::swap(&mut self.grid, &mut self.spare);
1241 std::mem::swap(&mut self.wrapped, &mut self.spare_wrapped);
1242 let outgoing = self.cursor;
1243 self.cursor = self.spare_cursor;
1244 self.spare_cursor = outgoing;
1245 self.surface = want;
1246 match want {
1247 Surface::Alternate => {
1248 if save {
1249 // The saved cursor of the ordinary screen is what a leave restores.
1250 self.saved = Some(SavedCursor {
1251 cursor: outgoing,
1252 pen: self.pen,
1253 origin: self.origin,
1254 });
1255 }
1256 let pen = self.erase_pen();
1257 for cell in self.grid.iter_mut() {
1258 *cell = Cell::blank(pen);
1259 }
1260 for w in self.wrapped.iter_mut() {
1261 *w = false;
1262 }
1263 self.cursor = Cursor {
1264 row: 0,
1265 col: 0,
1266 visible: outgoing.visible,
1267 wrap_pending: false,
1268 };
1269 }
1270 Surface::Primary => {
1271 if save {
1272 if let Some(s) = self.saved {
1273 self.cursor = s.cursor;
1274 self.pen = s.pen;
1275 self.origin = s.origin;
1276 }
1277 }
1278 }
1279 }
1280 self.cursor.row = self.cursor.row.min(self.rows - 1);
1281 self.cursor.col = self.cursor.col.min(self.cols - 1);
1282 self.region = (0, self.rows - 1);
1283 self.touch_all();
1284 self.touch_cursor();
1285 }
1286
1287 // ┌─────────────────────────────┐
1288 // │ RESIZE AND RESET │
1289 // └─────────────────────────────┘
1290
1291 /// Changes the size of the screen, putting the text back together at the new width.
1292 ///
1293 /// Height is settled first, then width, because the rewrap has to know how many rows the screen
1294 /// is going to keep.
1295 ///
1296 /// Rows keep the cursor on the screen. Shrinking removes rows from the top, so that the most
1297 /// recent output survives, and those rows go to the scrollback; if the cursor is near the top
1298 /// and rows can be spared below it, they are taken from the bottom instead and discarded.
1299 /// Growing pulls lines back out of the scrollback where any are held, and pads at the foot for
1300 /// the rest.
1301 ///
1302 /// Columns rewrap. The scrollback and the ordinary screen are joined back into the lines the
1303 /// application actually printed, using the record [`Line::wrapped`] keeps of which rows ran on
1304 /// into the row below, and are then split again at the new width. Narrowing a window therefore
1305 /// keeps every character; only a terminal that truncates loses text a reader could still have
1306 /// read. A double width character that will not fit in the columns left at the end of a row is
1307 /// moved whole to the next row, leaving the odd column blank, which is what the same character
1308 /// does when it is printed there in the first place.
1309 ///
1310 /// The alternate screen is not rewrapped. A full screen application draws at absolute positions
1311 /// and its rows are not continuations of anything, so joining them would scramble the display;
1312 /// it is truncated and padded, and the application redraws.
1313 ///
1314 /// The cursor follows the character it was on rather than the coordinates it had, and the
1315 /// viewport follows the line it was showing, so a resize while the view is scrolled back does
1316 /// not jump.
1317 ///
1318 /// The scrolling region is reset to the whole screen, which is what a real terminal does, since
1319 /// a region set for the old height rarely means anything at the new one.
1320 ///
1321 /// # Errors
1322 /// Fails if either dimension is zero.
1323 pub fn resize(&mut self, cols: usize, rows: usize) -> Outcome<()> {
1324 if cols == 0 || rows == 0 {
1325 return Err(err!(
1326 "A screen must have at least one row and one column, was given {} by {}.",
1327 cols, rows;
1328 Invalid, Input));
1329 }
1330 if cols == self.cols && rows == self.rows {
1331 return Ok(());
1332 }
1333 let alt = self.surface == Surface::Alternate;
1334 // `main` is the ordinary screen wherever it happens to be, since that is the one holding
1335 // the scrollback and the one worth rewrapping; `other` is the alternate one.
1336 let (mut main, mut other) = if alt {
1337 (
1338 Self::split_grid(&self.spare, &self.spare_wrapped, self.cols, self.rows),
1339 Self::split_grid(&self.grid, &self.wrapped, self.cols, self.rows),
1340 )
1341 } else {
1342 (
1343 Self::split_grid(&self.grid, &self.wrapped, self.cols, self.rows),
1344 Self::split_grid(&self.spare, &self.spare_wrapped, self.cols, self.rows),
1345 )
1346 };
1347 let mut main_cur = if alt { self.spare_cursor } else { self.cursor };
1348 let mut other_cur = if alt { self.cursor } else { self.spare_cursor };
1349
1350 // Height.
1351 if main.is_empty() {
1352 main = vec![Line::blank(self.cols); rows];
1353 } else {
1354 self.fit_height(&mut main, &mut main_cur, rows);
1355 }
1356 while other.len() > rows {
1357 other.pop();
1358 }
1359 while !other.is_empty() && other.len() < rows {
1360 other.push(Line::blank(self.cols));
1361 }
1362
1363 // Width.
1364 if cols == self.cols {
1365 for line in main.iter_mut() {
1366 Self::resize_line(&mut line.cells, cols);
1367 }
1368 } else {
1369 self.rewrap(&mut main, &mut main_cur, cols, rows);
1370 }
1371 for line in other.iter_mut() {
1372 Self::resize_line(&mut line.cells, cols);
1373 line.wrapped = false;
1374 }
1375 other_cur.row = other_cur.row.min(rows - 1);
1376 other_cur.col = other_cur.col.min(cols - 1);
1377
1378 // Reassemble.
1379 let (front, back) = if alt { (other, main) } else { (main, other) };
1380 self.grid = Vec::with_capacity(cols * rows);
1381 self.wrapped = Vec::with_capacity(rows);
1382 for line in front {
1383 self.grid.extend_from_slice(&line.cells);
1384 self.wrapped.push(line.wrapped);
1385 }
1386 self.spare = Vec::with_capacity(cols * rows);
1387 self.spare_wrapped = Vec::with_capacity(rows);
1388 for line in back {
1389 self.spare.extend_from_slice(&line.cells);
1390 self.spare_wrapped.push(line.wrapped);
1391 }
1392 if alt {
1393 self.cursor = other_cur;
1394 self.spare_cursor = main_cur;
1395 } else {
1396 self.cursor = main_cur;
1397 self.spare_cursor = other_cur;
1398 }
1399 // Tab stops keep whatever the application set within the old width.
1400 let mut tabs = Self::default_tabs(cols);
1401 for c in 0..cols.min(self.cols) {
1402 tabs[c] = self.tabs[c];
1403 }
1404 self.tabs = tabs;
1405 self.cols = cols;
1406 self.rows = rows;
1407 self.region = (0, rows - 1);
1408 self.cursor.row = self.cursor.row.min(rows - 1);
1409 self.cursor.col = self.cursor.col.min(cols - 1);
1410 self.spare_cursor.row = self.spare_cursor.row.min(rows - 1);
1411 self.spare_cursor.col = self.spare_cursor.col.min(cols - 1);
1412 self.view = self.view.min(self.scrollback.len());
1413 let scrolled = self.damage.scrolled;
1414 self.damage = Damage::new(rows);
1415 self.damage.scrolled = scrolled;
1416 Ok(())
1417 }
1418
1419 /// Takes a grid apart into lines, or gives nothing back if the grid is not the size claimed.
1420 fn split_grid(grid: &[Cell], wrapped: &[bool], cols: usize, rows: usize) -> Vec<Line> {
1421 if grid.len() < cols * rows || cols == 0 {
1422 return Vec::new();
1423 }
1424 let mut out = Vec::with_capacity(rows);
1425 for r in 0..rows {
1426 let a = r * cols;
1427 out.push(Line {
1428 cells: grid[a..a + cols].to_vec(),
1429 wrapped: wrapped.get(r).copied().unwrap_or(false),
1430 });
1431 }
1432 out
1433 }
1434
1435 /// Brings the ordinary screen to `rows` rows, keeping the cursor on it.
1436 fn fit_height(&mut self, lines: &mut Vec<Line>, cur: &mut Cursor, rows: usize) {
1437 while lines.len() > rows {
1438 // Prefer to lose a row below the cursor; otherwise lose the topmost.
1439 if cur.row + 1 < lines.len() {
1440 lines.pop();
1441 } else {
1442 let gone = lines.remove(0);
1443 self.push_scrollback(gone);
1444 cur.row = cur.row.saturating_sub(1);
1445 }
1446 }
1447 while lines.len() < rows {
1448 match self.scrollback.pop_back() {
1449 Some(mut line) => {
1450 line.cells.resize(self.cols, Cell::default());
1451 lines.insert(0, line);
1452 cur.row += 1;
1453 self.view = self.view.saturating_sub(1);
1454 }
1455 None => lines.push(Line::blank(self.cols)),
1456 }
1457 }
1458 }
1459
1460 /// Joins the scrollback and the ordinary screen back into the lines that were printed, splits
1461 /// them again at `cols`, and hands the screen back its last `rows` of them.
1462 ///
1463 /// Whatever the rewrap produces above those rows becomes the new scrollback, which is why
1464 /// narrowing a window pushes text upwards into the history rather than losing it.
1465 fn rewrap(&mut self, lines: &mut Vec<Line>, cur: &mut Cursor, cols: usize, rows: usize) {
1466 let hist = self.scrollback.len();
1467 let cur_line = hist + cur.row;
1468 let cur_col = cur.reported_col();
1469 let top_line = hist.saturating_sub(self.view);
1470 let mut all: Vec<Line> = Vec::with_capacity(hist + lines.len());
1471 all.extend(self.scrollback.drain(..));
1472 all.append(lines);
1473
1474 // Join. A line that ran on is glued to the one below it, and where each line started in
1475 // the joined text is remembered so that the cursor and the viewport can be found again.
1476 let mut logical: Vec<Vec<Cell>> = Vec::new();
1477 let mut place: Vec<(usize, usize)> = Vec::with_capacity(all.len());
1478 let mut open = false;
1479 for line in all {
1480 if !open {
1481 logical.push(Vec::new());
1482 }
1483 let i = logical.len() - 1;
1484 place.push((i, logical[i].len()));
1485 // A column left over at the end of a row belongs to the width the row had, not to
1486 // the text, so it goes no further.
1487 let mut end = line.cells.len();
1488 while end > 0 && line.cells[end - 1].wide == Wide::Filler {
1489 end -= 1;
1490 }
1491 logical[i].extend_from_slice(&line.cells[..end]);
1492 open = line.wrapped;
1493 }
1494 // Trailing blanks are padding rather than text, so they are not carried into the rewrap;
1495 // a blank cell painted with a background colour is not blank and stays.
1496 for cells in logical.iter_mut() {
1497 while let Some(last) = cells.last() {
1498 if last.is_blank() {
1499 cells.pop();
1500 } else {
1501 break;
1502 }
1503 }
1504 }
1505
1506 // Split again.
1507 let (cur_log, cur_base) = place.get(cur_line).copied().unwrap_or((0, 0));
1508 let cur_off = cur_base + cur_col;
1509 let (top_log, top_off) = place.get(top_line).copied().unwrap_or((0, 0));
1510 let mut out: Vec<Line> = Vec::new();
1511 let mut cur_at = (0usize, 0usize);
1512 let mut top_at = 0usize;
1513 for (li, cells) in logical.iter().enumerate() {
1514 let (made, starts) = Self::split_line(cells, cols);
1515 let base = out.len();
1516 if li == cur_log {
1517 let (k, c) = Self::locate(&starts, cur_off);
1518 cur_at = (base + k, c);
1519 }
1520 if li == top_log {
1521 let (k, _) = Self::locate(&starts, top_off);
1522 top_at = base + k;
1523 }
1524 out.extend(made);
1525 }
1526 while out.len() < rows {
1527 out.push(Line::blank(cols));
1528 }
1529
1530 // The screen keeps the last `rows` of what the rewrap made; the rest is history.
1531 let hist_new = out.len() - rows;
1532 let skip = hist_new.saturating_sub(self.scrollback_max);
1533 self.scrollback.clear();
1534 let mut i = 0;
1535 for line in out.drain(..hist_new) {
1536 if i >= skip {
1537 self.scrollback.push_back(line);
1538 }
1539 i += 1;
1540 }
1541 *lines = out;
1542 self.view = if top_at >= hist_new {
1543 0
1544 } else if top_at >= skip {
1545 self.scrollback.len() - (top_at - skip)
1546 } else {
1547 self.scrollback.len()
1548 };
1549 cur.row = cur_at.0.saturating_sub(hist_new).min(rows - 1);
1550 if cur_at.1 >= cols {
1551 cur.col = cols - 1;
1552 cur.wrap_pending = true;
1553 } else {
1554 cur.col = cur_at.1;
1555 cur.wrap_pending = false;
1556 }
1557 }
1558
1559 /// Splits a joined line into rows of `cols` cells, and says where in the joined line each row
1560 /// begins.
1561 ///
1562 /// A double width character that will not fit in the columns left at the end of a row is moved
1563 /// whole to the next row and the odd column is left blank, which is what the same character
1564 /// does when it is printed at the edge of a screen.
1565 fn split_line(cells: &[Cell], cols: usize) -> (Vec<Line>, Vec<usize>) {
1566 let mut made: Vec<Line> = Vec::new();
1567 let mut starts: Vec<usize> = Vec::new();
1568 let mut i = 0;
1569 loop {
1570 starts.push(i);
1571 let mut row: Vec<Cell> = Vec::with_capacity(cols);
1572 while i < cells.len() && row.len() < cols {
1573 let cell = cells[i];
1574 match cell.wide {
1575 Wide::Lead => {
1576 if row.len() + 2 > cols {
1577 // It will not fit in what is left of the row.
1578 row.push(Cell::filler(Pen::default()));
1579 break;
1580 }
1581 row.push(cell);
1582 i += 1;
1583 if i < cells.len() && cells[i].wide == Wide::Trail {
1584 row.push(cells[i]);
1585 i += 1;
1586 } else {
1587 row.push(Cell {
1588 chr: ' ',
1589 pen: cell.pen,
1590 wide: Wide::Trail,
1591 });
1592 }
1593 }
1594 Wide::Trail => {
1595 // A trailing half with no lead before it, which only an earlier cut can
1596 // leave behind. It stands as a blank of its own.
1597 row.push(Cell::blank(cell.pen));
1598 i += 1;
1599 }
1600 Wide::Filler => {
1601 // Padding from a width the text no longer has.
1602 i += 1;
1603 }
1604 Wide::No => {
1605 row.push(cell);
1606 i += 1;
1607 }
1608 }
1609 }
1610 let wrapped = i < cells.len();
1611 while row.len() < cols {
1612 row.push(Cell::default());
1613 }
1614 made.push(Line { cells: row, wrapped });
1615 if i >= cells.len() {
1616 break;
1617 }
1618 }
1619 (made, starts)
1620 }
1621
1622 /// Finds the row and column a position in a joined line ended up at, given where each row of
1623 /// the split began.
1624 ///
1625 /// A position one past the end of a full row comes back as that row and a column equal to the
1626 /// width, which is the pending wrap the caller turns back into a cursor.
1627 fn locate(starts: &[usize], off: usize) -> (usize, usize) {
1628 let mut k = 0;
1629 for (j, s) in starts.iter().enumerate() {
1630 if *s <= off {
1631 k = j;
1632 } else {
1633 break;
1634 }
1635 }
1636 (k, off - starts.get(k).copied().unwrap_or(0))
1637 }
1638
1639 /// Truncates or pads one line to `cols`, blanking a wide character the truncation would cut.
1640 fn resize_line(line: &mut Vec<Cell>, cols: usize) {
1641 if line.len() > cols {
1642 line.truncate(cols);
1643 if cols > 0 {
1644 let last = cols - 1;
1645 if line[last].wide == Wide::Lead {
1646 line[last] = Cell::default();
1647 }
1648 }
1649 } else {
1650 line.resize(cols, Cell::default());
1651 }
1652 }
1653
1654 /// Returns the screen to the state it had when it was made, keeping only its size.
1655 pub fn reset(&mut self) {
1656 let pen = Pen::default();
1657 for cell in self.grid.iter_mut() {
1658 *cell = Cell::blank(pen);
1659 }
1660 for cell in self.spare.iter_mut() {
1661 *cell = Cell::blank(pen);
1662 }
1663 for w in self.wrapped.iter_mut() {
1664 *w = false;
1665 }
1666 for w in self.spare_wrapped.iter_mut() {
1667 *w = false;
1668 }
1669 self.surface = Surface::Primary;
1670 self.cursor = Cursor::default();
1671 self.spare_cursor = Cursor::default();
1672 self.pen = pen;
1673 self.saved = None;
1674 self.region = (0, self.rows - 1);
1675 self.tabs = Self::default_tabs(self.cols);
1676 self.autowrap = true;
1677 self.insert = false;
1678 self.origin = false;
1679 self.scrollback.clear();
1680 self.view = 0;
1681 self.damage = Damage::new(self.rows);
1682 }
1683
1684 /// Fills the grid with `E`, which is the alignment pattern `DECALN` draws.
1685 pub fn fill_alignment(&mut self) {
1686 let pen = Pen::default();
1687 for cell in self.grid.iter_mut() {
1688 *cell = Cell {
1689 chr: 'E',
1690 pen,
1691 wide: Wide::No,
1692 };
1693 }
1694 for w in self.wrapped.iter_mut() {
1695 *w = false;
1696 }
1697 self.cursor = Cursor {
1698 row: 0,
1699 col: 0,
1700 visible: self.cursor.visible,
1701 wrap_pending: false,
1702 };
1703 self.region = (0, self.rows - 1);
1704 self.touch_all();
1705 self.touch_cursor();
1706 }
1707}
1708
1709/// Composes a base character with a combining mark, where a single composed character exists.
1710fn compose(base: char, mark: char) -> Option<char> {
1711 let mut s = String::with_capacity(8);
1712 s.push(base);
1713 s.push(mark);
1714 let composed = norm::nfc(&s);
1715 let mut it = composed.chars();
1716 match (it.next(), it.next()) {
1717 (Some(c), None) => Some(c),
1718 _ => None,
1719 }
1720}