Skip to content

fix redrawing & vanishing last column - #145

Merged
meszmate merged 4 commits into
meszmate:mainfrom
gcgbarbosa:main
Aug 28, 2026
Merged

meszmate merged 4 commits into
meszmate:mainfrom
gcgbarbosa:main

Conversation

@gcgbarbosa

Copy link
Copy Markdown
Contributor

closes #144 #143

writer.writeAll(ansi.cursor_home)
prints the byte \x1b[H (go to row 1, column 1 but ommit params)

I changed it to:
ansi.cursorTo0(writer, @intcast(line_count), 0)

which prints the byte \x1b[1;1H (go to row 1, column 1, exclicit params)

(it actually prints  \x1b[{row+1};1H, but you get the idea)
@gcgbarbosa

Copy link
Copy Markdown
Contributor Author

This is my first Zig PR ever.
Please check it thoroughly because I just started learning.

Addressing rows absolutely removes the scroll that meszmate#143 reported, and
skipping `EL` on a line that fills the row removes the erased border that
meszmate#144 reported. Both need bounds the original change did not apply.

A `CUP` row is a `u16`, and the terminal clamps it to the screen:

  * A view of 65536 lines or more overflowed the row counter and
    panicked. `writeFull` is the path that absorbs oversized frames, so
    the crash sat on exactly the input it exists to handle, and `.full`
    mode never bounds the line count at all.

  * Rows past the bottom clamped onto the last row, so each overflow
    line overwrote the one before it: six lines on a three-row screen
    left `a`, `b`, `f` rather than the top three. Count those lines, so
    `last_line_count` still describes the frame, but do not draw them.

`writeDiff` bounds its trailing clear the same way. It cannot reach a
`last_line_count` past the screen today — an oversized frame marks the
renderer dirty, which routes the next frame through `writeFull` — but
two paths clearing to different limits is a trap, not an invariant.

The erase predicate needs two corrections of its own, both in `fillsRow`:

  * `width < size.width` is never true when the width is zero, which
    suppressed every erase and left stale text on screen. `TIOCGWINSZ`
    reports 0x0 for a terminal whose window size was never set, and the
    80x24 fallback in `getSize` only covers the non-TTY case. An unknown
    width says nothing about which lines fill a row, so erase.

  * A line that wraps returns to the edge at every multiple of the row
    width, not only when it fills one row exactly. Ten columns of text
    on a five-column screen parks the cursor on the edge a second time,
    where `EL` eats the last character just the same. Test the width
    modulo the row rather than against it.
Two gaps let the renderer's own regressions through.

`VirtualScreen` counted bytes, not columns, and knew nothing of the
screen it was modelling. It could not represent the bug behind meszmate#144 at
all: a character written to the last column parks the cursor there with
its wrap deferred, and `EL` erases from the column the cursor occupies —
taking that character with it. A frame drawn to the full width of the
screen modelled perfectly and still lost its right-hand border on a real
terminal. Model columns, autowrap, wrap-pending and `CUP` clamping, so
the harness can show what a terminal would.

`\x1b[H` used to be proof of a full repaint, because only `writeFull`
emitted it. Absolute addressing made it `\x1b[1;1H`, which `writeDiff`
also emits whenever row 0 is the row that changed. Every rewritten
assertion still discriminated by luck — each left row 0 untouched — but
they no longer tested what their names claimed. Assert instead that a
row which did *not* change was rewritten, which is the actual difference
between the two paths.

Covers what neither path had: a frame that fills the width, a wrapped
line that ends on the edge, truncation past the bottom row, a view too
tall for a `u16` row, and an unknown terminal width.
@meszmate

meszmate commented Aug 28, 2026 •

Copy link
Copy Markdown
Owner

Thanks, and for the two issues too. Both diagnoses were spot on, which is the hard part.

Pushed two commits on top:

  • CUP rows are u16 and get clamped to the screen, so a 65536+ line view panicked and rows past the bottom piled onto the last one. Both bounded now.
  • width < size.width is never true at width 0, so nothing got erased and stale text stuck around. TIOCGWINSZ reports 0x0 when a terminal's size was never set.
  • A wrapped line hits the edge more than once. 10 columns on a 5 column screen fills the second row exactly and parks the cursor there again, so it's width modulo the row.
  • VirtualScreen counted bytes and had no idea how big the screen was, so it couldn't actually represent your bug. Models columns and wrap-pending now.

589/589 pass, green in all optimize modes. Closes #143, closes #144.

@meszmate
meszmate merged commit aae2d50 into meszmate:main Aug 28, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants