a54a8105
fix: a blank row paints nothing, and a trailing default space is not a divergence
a73x 2026-09-04 18:04
Commit message
src/tui/paint.zig
| Old | New | ||
|---|---|---|---|
| @@ -49,15 +49,22 @@ pub fn rowToVtFrom( | |||
| 49 | if (s.from > last) break :blk null; | 49 | if (s.from > last) break :blk null; |
| 50 | break :blk grid.snapWideOf(r, cols, s.from, @min(s.to, last)); | 50 | break :blk grid.snapWideOf(r, cols, s.from, @min(s.to, last)); |
| 51 | } else null; | 51 | } else null; |
| 52 | var end: u16 = last; | 52 | // The last cell worth writing: trailing default blanks are the caller's |
| 53 | // Trailing default blanks are the caller's clear, not ours — but a | 53 | // clear, not ours — but a selected blank is a cell the user can see they |
| 54 | // selected blank is a cell the user can see they selected, so the span | 54 | // selected, so the span holds the stop open past it. A row with nothing |
| 55 | // holds the stop open past it. | 55 | // on it writes nothing at all, or an empty row would print a space and a |
| 56 | while (end > 0 and r.cells[end].isBlank() and (snapped == null or end > snapped.?.to)) end -= 1; | 56 | // screen would never look empty again. |
| 57 | var end: ?u16 = null; | ||
| 58 | var i: u16 = 0; | ||
| 59 | while (i <= last) : (i += 1) { | ||
| 60 | const selected = if (snapped) |s| i >= s.from and i <= s.to else false; | ||
| 61 | if (!r.cells[i].isBlank() or selected) end = i; | ||
| 62 | } | ||
| 63 | const stop = end orelse return out.toOwnedSlice(alloc); | ||
| 57 | var cur: proto.CellStyle = .{}; | 64 | var cur: proto.CellStyle = .{}; |
| 58 | var x: u16 = 0; | 65 | var x: u16 = 0; |
| 59 | var inverted = false; | 66 | var inverted = false; |
| 60 | while (x <= end) : (x += 1) { | 67 | while (x <= stop) : (x += 1) { |
| 61 | const c = r.cells[x]; | 68 | const c = r.cells[x]; |
| 62 | if (c.wide == .spacer_tail or c.wide == .spacer_head) continue; | 69 | if (c.wide == .spacer_tail or c.wide == .spacer_head) continue; |
| 63 | const in_span = if (snapped) |s| x >= s.from and x <= s.to else false; | 70 | const in_span = if (snapped) |s| x >= s.from and x <= s.to else false; |
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -98,7 +98,8 @@ if converged_quiet "$OUT.doctored" "$SOCK"; then | |||
| 98 | fi | 98 | fi |
| 99 | rm_swept "$OUT.doctored" "$OUT.doctored.render" "$OUT.doctored.dump" \ | 99 | rm_swept "$OUT.doctored" "$OUT.doctored.render" "$OUT.doctored.dump" \ |
| 100 | "$OUT.doctored.render.n" "$OUT.doctored.dump.n" "$OUT.doctored.diff" \ | 100 | "$OUT.doctored.render.n" "$OUT.doctored.dump.n" "$OUT.doctored.diff" \ |
| 101 | "$OUT.doctored.rvt" "$OUT.doctored.dvt" | 101 | "$OUT.doctored.rvt" "$OUT.doctored.dvt" \ |
| 102 | "$OUT.doctored.rvt.n" "$OUT.doctored.dvt.n" | ||
| 102 | ok "convergence control fires on a doctored stream" | 103 | ok "convergence control fires on a doctored stream" |
| 103 | 104 | ||
| 104 | # --- M11: a styled specimen, so the byte-exact leg has something to compare. | 105 | # --- M11: a styled specimen, so the byte-exact leg has something to compare. |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -1041,9 +1041,20 @@ converged_quiet() { | |||
| 1041 | # shellcheck disable=SC2086 | 1041 | # shellcheck disable=SC2086 |
| 1042 | "$RENDER" --vt $_sz $_drop < "$_co" > "$_co.rvt" || return 1 | 1042 | "$RENDER" --vt $_sz $_drop < "$_co" > "$_co.rvt" || return 1 |
| 1043 | "$MUX" d dump --vt --sock "$_cs" > "$_co.dvt" || return 1 | 1043 | "$MUX" d dump --vt --sock "$_cs" > "$_co.dvt" || return 1 |
| 1044 | cmp -s "$_co.dvt" "$_co.rvt" || return 1 | 1044 | # Trailing DEFAULT spaces are normalized away on both sides for the same |
| 1045 | # reason the plain leg strips them: the row encoder stops a row at its | ||
| 1046 | # last cell that is not a default blank, so a space the shell wrote at | ||
| 1047 | # the end of a row never leaves the daemon and the client's grid holds an | ||
| 1048 | # erased cell where the daemon's holds a space. The two paint identically. | ||
| 1049 | # A trailing space carrying a colour is NOT a default blank — it is sent, | ||
| 1050 | # and it arrives wrapped in SGR bytes, so the line does not end in | ||
| 1051 | # whitespace and this leaves it alone. | ||
| 1052 | _cr=$(printf '\r') | ||
| 1053 | sed "s/ *\\(${_cr}\\{0,1\\}\\)\$/\\1/" "$_co.dvt" > "$_co.dvt.n" | ||
| 1054 | sed "s/ *\\(${_cr}\\{0,1\\}\\)\$/\\1/" "$_co.rvt" > "$_co.rvt.n" | ||
| 1055 | cmp -s "$_co.dvt.n" "$_co.rvt.n" || return 1 | ||
| 1045 | rm -f "$_co.render" "$_co.dump" "$_co.render.n" "$_co.dump.n" \ | 1056 | rm -f "$_co.render" "$_co.dump" "$_co.render.n" "$_co.dump.n" \ |
| 1046 | "$_co.diff" "$_co.rvt" "$_co.dvt" | 1057 | "$_co.diff" "$_co.rvt" "$_co.dvt" "$_co.rvt.n" "$_co.dvt.n" |
| 1047 | return 0 | 1058 | return 0 |
| 1048 | } | 1059 | } |
| 1049 | 1060 | ||