| 279 |
279 |
|
|
| 280 |
280 |
|
/// One row that has scrolled off the top of the main screen.
|
| 281 |
281 |
|
///
|
| 282 |
|
- |
/// Held at the width it was authored at, which `resize` then normalizes to the
|
| 283 |
|
- |
/// grid's width — see [`Grid::history`].
|
|
282 |
+ |
/// Always exactly the grid's current width: `resize` rewraps the whole buffer
|
|
283 |
+ |
/// to the new one — see [`Grid::history`].
|
| 284 |
284 |
|
#[derive(Clone, Debug)]
|
| 285 |
285 |
|
struct HistoryRow {
|
| 286 |
286 |
|
cells: Vec<Cell>,
|
| 287 |
287 |
|
/// Whether it ran off the right edge and continued on the row below, so a
|
| 288 |
288 |
|
/// copy spanning the two joins them without a newline.
|
|
289 |
+ |
///
|
|
290 |
+ |
/// Also the wrap-point record [`Grid::rewrap_history`] reads: a maximal run
|
|
291 |
+ |
/// of these plus the row that ends it is one logical line.
|
| 289 |
292 |
|
wrapped: bool,
|
| 290 |
293 |
|
}
|
| 291 |
294 |
|
|
| 322 |
325 |
|
/// a row leaving its top is overdraw rather than history, and every
|
| 323 |
326 |
|
/// terminal that keeps scrollback keeps none for it.
|
| 324 |
327 |
|
///
|
| 325 |
|
- |
/// Every row here is exactly `cols` wide. `resize` clips or pads the whole
|
| 326 |
|
- |
/// buffer to the new width rather than reflowing it, so the invariant every
|
| 327 |
|
- |
/// reader depends on — `row()` yields `cols` cells — holds for history rows
|
| 328 |
|
- |
/// as much as for live ones.
|
|
328 |
+ |
/// Every row here is exactly `cols` wide. `resize` rewraps the whole buffer
|
|
329 |
+ |
/// to the new width and re-materializes it at that width, so the invariant
|
|
330 |
+ |
/// every reader depends on — `row()` yields `cols` cells — holds for history
|
|
331 |
+ |
/// rows as much as for live ones.
|
| 329 |
332 |
|
history: VecDeque<HistoryRow>,
|
| 330 |
333 |
|
/// How many rows `history` keeps before dropping its oldest.
|
| 331 |
334 |
|
history_limit: usize,
|
| 897 |
900 |
|
self.set_row_wrapped(r, false);
|
| 898 |
901 |
|
}
|
| 899 |
902 |
|
|
| 900 |
|
- |
/// Resize the grid, preserving as much of the top-left as fits. Truncates
|
| 901 |
|
- |
/// content outside the new bounds; does not reflow.
|
|
903 |
+ |
/// Resize the grid, preserving as much of the top-left of the live screen
|
|
904 |
+ |
/// as fits and rewrapping scrollback to the new width.
|
|
905 |
+ |
///
|
|
906 |
+ |
/// The live screen is truncated, not reflowed: it is whatever an
|
|
907 |
+ |
/// application last painted, and it is about to be told the new size and
|
|
908 |
+ |
/// repaint. History has no one to repaint it, so it is rewrapped — see
|
|
909 |
+ |
/// [`Grid::rewrap_history`].
|
| 902 |
910 |
|
pub fn resize(&mut self, cols: u16, rows: u16) {
|
| 903 |
911 |
|
let cols = cols.max(1);
|
| 904 |
912 |
|
let rows = rows.max(1);
|
| 905 |
913 |
|
if cols == self.cols && rows == self.rows {
|
| 906 |
914 |
|
return;
|
| 907 |
915 |
|
}
|
|
916 |
+ |
let old_cols = self.cols;
|
| 908 |
917 |
|
self.main = resize_buf(
|
| 909 |
918 |
|
&self.main,
|
| 910 |
919 |
|
self.main_origin,
|
| 914 |
923 |
|
rows,
|
| 915 |
924 |
|
);
|
| 916 |
925 |
|
self.alt = resize_buf(&self.alt, self.alt_origin, self.cols, self.rows, cols, rows);
|
| 917 |
|
- |
// Resize does not reflow, so every recorded wrap point is now a lie
|
| 918 |
|
- |
// about where the text actually runs off the edge. Drop them all
|
| 919 |
|
- |
// rather than carry wrong ones into a copy.
|
|
926 |
+ |
// The live screen is clipped rather than reflowed, so every recorded
|
|
927 |
+ |
// wrap point on it is now a lie about where the text runs off the edge.
|
|
928 |
+ |
// Drop them all rather than carry wrong ones into a copy. History keeps
|
|
929 |
+ |
// its flags: the rewrap is what makes them true again.
|
| 920 |
930 |
|
self.main_wrapped = vec![false; rows as usize];
|
| 921 |
931 |
|
self.alt_wrapped = vec![false; rows as usize];
|
| 922 |
|
- |
// Scrollback is clipped or padded to the new width, never reflowed.
|
| 923 |
|
- |
// Reflow is the right answer for a history someone is reading and it is
|
| 924 |
|
- |
// also the classic source of terminal bugs, so it is a separate,
|
| 925 |
|
- |
// deliberate piece of work rather than a side effect of this one.
|
| 926 |
|
- |
//
|
| 927 |
|
- |
// Normalizing here rather than at render keeps the invariant every
|
| 928 |
|
- |
// reader leans on — a row is `cols` cells — in one place. The cost is
|
| 929 |
|
- |
// that narrowing the window truncates old wide lines for good.
|
| 930 |
|
- |
if cols != self.cols {
|
| 931 |
|
- |
for row in &mut self.history {
|
| 932 |
|
- |
row.cells.resize(cols as usize, Cell::default());
|
| 933 |
|
- |
// A wrap point recorded at the old width no longer says where
|
| 934 |
|
- |
// the text runs off the edge, same argument as the live rows.
|
| 935 |
|
- |
row.wrapped = false;
|
| 936 |
|
- |
}
|
| 937 |
|
- |
}
|
| 938 |
932 |
|
// The viewport survives a height change, but it cannot point further
|
| 939 |
933 |
|
// back than history goes.
|
| 940 |
934 |
|
self.view_offset = self.view_offset.min(self.history_len_u16());
|
| 943 |
937 |
|
self.region_origin = 0;
|
| 944 |
938 |
|
self.cols = cols;
|
| 945 |
939 |
|
self.rows = rows;
|
|
940 |
+ |
if cols != old_cols {
|
|
941 |
+ |
self.rewrap_history(old_cols);
|
|
942 |
+ |
}
|
| 946 |
943 |
|
self.scroll_top = 0;
|
| 947 |
944 |
|
self.scroll_bottom = rows - 1;
|
| 948 |
945 |
|
self.cursor.row = self.cursor.row.min(rows - 1);
|
| 955 |
952 |
|
self.invalidate_cur_row();
|
| 956 |
953 |
|
}
|
| 957 |
954 |
|
|
|
955 |
+ |
/// Rewrap scrollback from `old_cols` to the width already stored in
|
|
956 |
+ |
/// `self.cols`.
|
|
957 |
+ |
///
|
|
958 |
+ |
/// The logical lines are recoverable from the materialized rows, so this
|
|
959 |
+ |
/// needs no second representation: a maximal run of `wrapped` rows plus the
|
|
960 |
+ |
/// row that ends it is one line a program printed, and `wrapped` is correct
|
|
961 |
+ |
/// at the moment a row is pushed. Join those runs, re-split at the new
|
|
962 |
+ |
/// width, and history is still a deque of exactly-`cols` rows — `row()` and
|
|
963 |
+ |
/// every reader above it (selection, word boundaries, copy) is untouched.
|
|
964 |
+ |
///
|
|
965 |
+ |
/// The alternative, storing history as logical lines and materializing rows
|
|
966 |
+ |
/// on read, moves the cost to every frame and puts variable-width rows in
|
|
967 |
+ |
/// front of every reader to buy nothing this does not.
|
|
968 |
+ |
///
|
|
969 |
+ |
/// Costs one pass and a transient second copy of the buffer, at resize
|
|
970 |
+ |
/// only. That is ~24 MB at the default limit and 200 columns, held for the
|
|
971 |
+ |
/// length of a window drag.
|
|
972 |
+ |
fn rewrap_history(&mut self, old_cols: u16) {
|
|
973 |
+ |
if self.history.is_empty() || old_cols == 0 {
|
|
974 |
+ |
return;
|
|
975 |
+ |
}
|
|
976 |
+ |
let cols = self.cols as usize;
|
|
977 |
+ |
// Absolute index of the row the viewport's top sits on, if it is back
|
|
978 |
+ |
// in history at all. Carried through as (logical line, cells into it)
|
|
979 |
+ |
// so the text under the user's eye stays under it.
|
|
980 |
+ |
let anchor_row = self.history.len().saturating_sub(self.view_offset as usize);
|
|
981 |
+ |
// The newest line may run onto the live screen. Recorded before the
|
|
982 |
+ |
// walk, because re-splitting otherwise decides the final row's flag
|
|
983 |
+ |
// from the line's length and would break that join.
|
|
984 |
+ |
let tail_continues = self.history.back().is_some_and(|r| r.wrapped);
|
|
985 |
+ |
|
|
986 |
+ |
let mut lines: Vec<Vec<Cell>> = Vec::new();
|
|
987 |
+ |
let mut anchor: Option<(usize, usize)> = None;
|
|
988 |
+ |
// Whether the row being visited continues the line already open.
|
|
989 |
+ |
let mut open = false;
|
|
990 |
+ |
for (i, row) in self.history.iter().enumerate() {
|
|
991 |
+ |
if !open {
|
|
992 |
+ |
lines.push(Vec::new());
|
|
993 |
+ |
}
|
|
994 |
+ |
let li = lines.len() - 1;
|
|
995 |
+ |
let line = lines.last_mut().expect("a line is open by here");
|
|
996 |
+ |
if i == anchor_row {
|
|
997 |
+ |
anchor = Some((li, line.len()));
|
|
998 |
+ |
}
|
|
999 |
+ |
if row.wrapped {
|
|
1000 |
+ |
// An interior row ran off the right edge, so it is full of
|
|
1001 |
+ |
// content by construction — nothing on it is padding.
|
|
1002 |
+ |
line.extend_from_slice(&row.cells);
|
|
1003 |
+ |
} else {
|
|
1004 |
+ |
// The last row of a line: its tail is padding, not content.
|
|
1005 |
+ |
// Only never-written cells count as padding. A space someone
|
|
1006 |
+ |
// typed is a cell like any other and keeps its background.
|
|
1007 |
+ |
let end = row
|
|
1008 |
+ |
.cells
|
|
1009 |
+ |
.iter()
|
|
1010 |
+ |
.rposition(|c| *c != Cell::default())
|
|
1011 |
+ |
.map_or(0, |i| i + 1);
|
|
1012 |
+ |
line.extend_from_slice(&row.cells[..end]);
|
|
1013 |
+ |
}
|
|
1014 |
+ |
open = row.wrapped;
|
|
1015 |
+ |
}
|
|
1016 |
+ |
|
|
1017 |
+ |
let last_line = lines.len() - 1;
|
|
1018 |
+ |
let mut out: VecDeque<HistoryRow> = VecDeque::with_capacity(self.history.len());
|
|
1019 |
+ |
let mut new_anchor: Option<usize> = None;
|
|
1020 |
+ |
for (li, line) in lines.into_iter().enumerate() {
|
|
1021 |
+ |
let first = out.len();
|
|
1022 |
+ |
// Only the newest line can be unterminated, and only if it was
|
|
1023 |
+ |
// running onto the live screen before the resize.
|
|
1024 |
+ |
let unterminated = li == last_line && tail_continues;
|
|
1025 |
+ |
if line.is_empty() {
|
|
1026 |
+ |
// A blank line is content: someone's output had a gap in it.
|
|
1027 |
+ |
// Never wrapped — an empty row holds nothing that could have
|
|
1028 |
+ |
// run off the edge.
|
|
1029 |
+ |
out.push_back(HistoryRow {
|
|
1030 |
+ |
cells: vec![Cell::default(); cols],
|
|
1031 |
+ |
wrapped: false,
|
|
1032 |
+ |
});
|
|
1033 |
+ |
} else {
|
|
1034 |
+ |
// A line exactly `cols` long is one row, not a row plus an
|
|
1035 |
+ |
// empty continuation — `div_ceil` of a multiple is the multiple.
|
|
1036 |
+ |
let n = line.len().div_ceil(cols);
|
|
1037 |
+ |
for (ci, chunk) in line.chunks(cols).enumerate() {
|
|
1038 |
+ |
// A row is wrapped when it is full AND something follows.
|
|
1039 |
+ |
// Interior rows are full by construction. The final row of
|
|
1040 |
+ |
// an unterminated line only keeps the flag if it fills the
|
|
1041 |
+ |
// new width too: the live screen it ran onto is clipped
|
|
1042 |
+ |
// rather than reflowed, so a flag on a half-full row would
|
|
1043 |
+ |
// be the same lie about where the text leaves the edge that
|
|
1044 |
+ |
// the live rows drop theirs for, and would emit its padding
|
|
1045 |
+ |
// as content on a copy.
|
|
1046 |
+ |
let full = chunk.len() == cols;
|
|
1047 |
+ |
let mut cells = chunk.to_vec();
|
|
1048 |
+ |
cells.resize(cols, Cell::default());
|
|
1049 |
+ |
out.push_back(HistoryRow {
|
|
1050 |
+ |
cells,
|
|
1051 |
+ |
wrapped: if ci + 1 < n {
|
|
1052 |
+ |
true
|
|
1053 |
+ |
} else {
|
|
1054 |
+ |
unterminated && full
|
|
1055 |
+ |
},
|
|
1056 |
+ |
});
|
|
1057 |
+ |
}
|
|
1058 |
+ |
}
|
|
1059 |
+ |
if let Some((al, off)) = anchor
|
|
1060 |
+ |
&& al == li
|
|
1061 |
+ |
{
|
|
1062 |
+ |
// Widening can put the anchor past the line's new end, in which
|
|
1063 |
+ |
// case that line's last row is the closest thing to it.
|
|
1064 |
+ |
new_anchor = Some((first + off / cols).min(out.len() - 1));
|
|
1065 |
+ |
}
|
|
1066 |
+ |
}
|
|
1067 |
+ |
self.history = out;
|
|
1068 |
+ |
|
|
1069 |
+ |
// Narrowing turns n rows into more than n, which can cross the limit.
|
|
1070 |
+ |
// Trim after the rewrap and not before, so the trim never cuts a
|
|
1071 |
+ |
// logical line in half and leaves its tail to be rewrapped alone.
|
|
1072 |
+ |
let over = self.history.len().saturating_sub(self.history_limit);
|
|
1073 |
+ |
self.history.drain(..over);
|
|
1074 |
+ |
|
|
1075 |
+ |
let before = self.view_offset;
|
|
1076 |
+ |
self.view_offset = match new_anchor {
|
|
1077 |
+ |
// The anchored row itself can fall to the trim, and then the oldest
|
|
1078 |
+ |
// surviving row is the closest the viewport can get to it.
|
|
1079 |
+ |
Some(a) => {
|
|
1080 |
+ |
let a = a.saturating_sub(over);
|
|
1081 |
+ |
(self.history.len() - a).min(u16::MAX as usize) as u16
|
|
1082 |
+ |
}
|
|
1083 |
+ |
None => 0,
|
|
1084 |
+ |
};
|
|
1085 |
+ |
if self.view_offset != before {
|
|
1086 |
+ |
self.view_dirty = true;
|
|
1087 |
+ |
}
|
|
1088 |
+ |
}
|
|
1089 |
+ |
|
| 958 |
1090 |
|
#[inline]
|
| 959 |
1091 |
|
fn invalidate_cur_row(&mut self) {
|
| 960 |
1092 |
|
self.cur_row_start = CUR_ROW_INVALID;
|
| 2491 |
2623 |
|
assert_eq!(g.history_len(), 0);
|
| 2492 |
2624 |
|
}
|
| 2493 |
2625 |
|
|
|
2626 |
+ |
/// Everything in scrollback, as text. The round-trip property is stated
|
|
2627 |
+ |
/// over this rather than over rows, because the rows are exactly what a
|
|
2628 |
+ |
/// rewrap is allowed to change.
|
|
2629 |
+ |
fn history_text(g: &Grid) -> String {
|
|
2630 |
+ |
g.text_range(0, g.history_len())
|
|
2631 |
+ |
}
|
|
2632 |
+ |
|
| 2494 |
2633 |
|
#[test]
|
| 2495 |
|
- |
fn history_is_clipped_to_the_new_width_rather_than_reflowed() {
|
|
2634 |
+ |
fn narrowing_wraps_a_long_history_row_instead_of_clipping_it() {
|
| 2496 |
2635 |
|
let mut g = Grid::new(8, 2);
|
| 2497 |
2636 |
|
feed(&mut g, b"abcdefgh\r\nsecond\r\nthird");
|
| 2498 |
2637 |
|
g.scroll_view_up(1);
|
| 2499 |
2638 |
|
assert_eq!(row_str(&g, 0), "abcdefgh");
|
| 2500 |
2639 |
|
g.scroll_view_down(1);
|
| 2501 |
2640 |
|
g.resize(4, 2);
|
| 2502 |
|
- |
g.scroll_view_up(1);
|
| 2503 |
|
- |
// Narrowed, so the tail is gone for good. That is the cost of not
|
| 2504 |
|
- |
// reflowing, and it is the stated tradeoff rather than a bug.
|
| 2505 |
|
- |
assert_eq!(row_str(&g, 0), "abcd");
|
|
2641 |
+ |
// The tail moved to a continuation row rather than being destroyed.
|
| 2506 |
2642 |
|
assert_eq!(g.row(0).len(), 4, "history rows must be `cols` wide");
|
|
2643 |
+ |
assert!(history_text(&g).contains("abcdefgh"));
|
| 2507 |
2644 |
|
}
|
| 2508 |
2645 |
|
|
| 2509 |
2646 |
|
#[test]
|
| 2510 |
|
- |
fn widening_pads_history_so_every_row_is_cols_wide() {
|
| 2511 |
|
- |
let mut g = Grid::new(4, 2);
|
| 2512 |
|
- |
// Exactly one row off the top, so offset 1 is unambiguously "abcd".
|
| 2513 |
|
- |
feed(&mut g, b"abcd\r\nxy\r\nz");
|
|
2647 |
+ |
fn narrowing_then_widening_gives_the_logical_lines_back() {
|
|
2648 |
+ |
let mut g = Grid::new(8, 2);
|
|
2649 |
+ |
feed(&mut g, b"abcdefgh\r\nsecond\r\nthird");
|
|
2650 |
+ |
let before = history_text(&g);
|
|
2651 |
+ |
g.resize(4, 2);
|
| 2514 |
2652 |
|
g.resize(8, 2);
|
|
2653 |
+ |
// The property the clipping code could not satisfy at any width.
|
|
2654 |
+ |
assert_eq!(history_text(&g), before);
|
|
2655 |
+ |
}
|
|
2656 |
+ |
|
|
2657 |
+ |
#[test]
|
|
2658 |
+ |
fn a_line_exactly_cols_wide_gains_no_empty_continuation_row() {
|
|
2659 |
+ |
let mut g = Grid::new(4, 2);
|
|
2660 |
+ |
feed(&mut g, b"abcd\r\nxy\r\nz");
|
|
2661 |
+ |
let rows = g.history_len();
|
|
2662 |
+ |
g.resize(8, 2);
|
|
2663 |
+ |
g.resize(4, 2);
|
|
2664 |
+ |
assert_eq!(g.history_len(), rows, "a full row grew a continuation");
|
|
2665 |
+ |
}
|
|
2666 |
+ |
|
|
2667 |
+ |
#[test]
|
|
2668 |
+ |
fn widening_rejoins_what_narrowing_split() {
|
|
2669 |
+ |
let mut g = Grid::new(4, 2);
|
|
2670 |
+ |
// "abcdefgh" wraps into two history rows at width 4.
|
|
2671 |
+ |
feed(&mut g, b"abcdefgh\r\nxy\r\nz");
|
|
2672 |
+ |
assert_eq!(g.history_len(), 2);
|
|
2673 |
+ |
g.resize(8, 2);
|
|
2674 |
+ |
assert_eq!(g.history_len(), 1, "the two halves did not rejoin");
|
| 2515 |
2675 |
|
g.scroll_view_up(1);
|
| 2516 |
2676 |
|
assert_eq!(g.row(0).len(), 8);
|
| 2517 |
|
- |
assert_eq!(row_str(&g, 0), "abcd");
|
|
2677 |
+ |
assert_eq!(row_str(&g, 0), "abcdefgh");
|
|
2678 |
+ |
}
|
|
2679 |
+ |
|
|
2680 |
+ |
#[test]
|
|
2681 |
+ |
fn a_blank_history_line_survives_a_rewrap() {
|
|
2682 |
+ |
let mut g = Grid::new(8, 2);
|
|
2683 |
+ |
feed(&mut g, b"one\r\n\r\ntwo\r\nthree");
|
|
2684 |
+ |
let before = history_text(&g);
|
|
2685 |
+ |
g.resize(4, 2);
|
|
2686 |
+ |
g.resize(8, 2);
|
|
2687 |
+ |
assert_eq!(history_text(&g), before, "the gap in the output closed");
|
|
2688 |
+ |
}
|
|
2689 |
+ |
|
|
2690 |
+ |
#[test]
|
|
2691 |
+ |
fn narrowing_trims_to_the_limit_after_rewrapping_not_before() {
|
|
2692 |
+ |
let mut g = Grid::new(8, 2);
|
|
2693 |
+ |
g.set_history_limit(3);
|
|
2694 |
+ |
feed(&mut g, b"abcdefgh\r\nijklmnop\r\nqrst\r\nuvwx\r\nlast");
|
|
2695 |
+ |
g.resize(4, 2);
|
|
2696 |
+ |
assert_eq!(g.history_len(), 3, "the limit did not hold across a rewrap");
|
|
2697 |
+ |
// The newest rows survive and they are whole: the trim came after the
|
|
2698 |
+ |
// rewrap, so no line was cut in half and its tail rewrapped alone.
|
|
2699 |
+ |
assert!(
|
|
2700 |
+ |
history_text(&g).ends_with("mnop\nqrst\n"),
|
|
2701 |
+ |
"{:?}",
|
|
2702 |
+ |
history_text(&g)
|
|
2703 |
+ |
);
|
|
2704 |
+ |
}
|
|
2705 |
+ |
|
|
2706 |
+ |
#[test]
|
|
2707 |
+ |
fn the_viewport_keeps_the_row_it_was_reading_across_a_rewrap() {
|
|
2708 |
+ |
let mut g = Grid::new(8, 2);
|
|
2709 |
+ |
feed(&mut g, b"aaaaaaaa\r\nbbbb\r\ncccc\r\ndddd\r\nlive");
|
|
2710 |
+ |
g.scroll_view_up(2);
|
|
2711 |
+ |
let reading = row_str(&g, 0);
|
|
2712 |
+ |
assert_eq!(reading, "bbbb");
|
|
2713 |
+ |
g.resize(4, 2);
|
|
2714 |
+ |
assert_eq!(row_str(&g, 0), reading, "narrowing moved the text");
|
|
2715 |
+ |
g.resize(8, 2);
|
|
2716 |
+ |
assert_eq!(row_str(&g, 0), reading, "widening moved the text");
|
|
2717 |
+ |
}
|
|
2718 |
+ |
|
|
2719 |
+ |
#[test]
|
|
2720 |
+ |
fn a_history_row_running_onto_the_live_screen_still_joins_after_a_rewrap() {
|
|
2721 |
+ |
let mut g = Grid::new(8, 2);
|
|
2722 |
+ |
// The oldest line runs off the edge and continues onto the live
|
|
2723 |
+ |
// screen, so its wrap flag has to survive the rewrap.
|
|
2724 |
+ |
feed(&mut g, b"abcdefghijklmnopqrst");
|
|
2725 |
+ |
assert_eq!(g.history_len(), 1);
|
|
2726 |
+ |
g.resize(4, 2);
|
|
2727 |
+ |
let all = g.text_range(0, g.abs_rows());
|
|
2728 |
+ |
assert!(
|
|
2729 |
+ |
all.starts_with("abcdefghijkl"),
|
|
2730 |
+ |
"the join to the live screen broke: {all:?}"
|
|
2731 |
+ |
);
|
|
2732 |
+ |
}
|
|
2733 |
+ |
|
|
2734 |
+ |
#[test]
|
|
2735 |
+ |
fn a_half_full_tail_row_drops_its_wrap_flag_rather_than_emit_padding() {
|
|
2736 |
+ |
let mut g = Grid::new(4, 2);
|
|
2737 |
+ |
// "abcd" is a full history row continuing onto the live screen. At
|
|
2738 |
+ |
// width 8 it no longer reaches the edge, and the live screen it ran
|
|
2739 |
+ |
// onto is clipped rather than reflowed, so the flag would be a lie —
|
|
2740 |
+ |
// and would emit four cells of padding as content on a copy.
|
|
2741 |
+ |
feed(&mut g, b"abcdefghijkl");
|
|
2742 |
+ |
g.resize(8, 2);
|
|
2743 |
+ |
assert!(
|
|
2744 |
+ |
!history_text(&g).starts_with("abcd "),
|
|
2745 |
+ |
"padding emitted as content: {:?}",
|
|
2746 |
+ |
history_text(&g)
|
|
2747 |
+ |
);
|
|
2748 |
+ |
}
|
|
2749 |
+ |
|
|
2750 |
+ |
#[test]
|
|
2751 |
+ |
fn a_typed_space_at_the_end_of_a_line_is_content_not_padding() {
|
|
2752 |
+ |
let mut g = Grid::new(8, 2);
|
|
2753 |
+ |
// A trailing space inside a wrapped line is a real cell the text ran
|
|
2754 |
+ |
// through; only never-written cells are padding.
|
|
2755 |
+ |
feed(&mut g, b"ab cd ef gh\r\nxx\r\nyy");
|
|
2756 |
+ |
let before = history_text(&g);
|
|
2757 |
+ |
g.resize(4, 2);
|
|
2758 |
+ |
g.resize(8, 2);
|
|
2759 |
+ |
assert_eq!(history_text(&g), before);
|
| 2518 |
2760 |
|
}
|
| 2519 |
2761 |
|
|
| 2520 |
2762 |
|
#[test]
|