fix(ui): reserve pad height per message to prevent multi-line clip and scroll desync #136
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/multiline-pad-clip"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem. Scrolling up through chat history jumps by ~one page when passing a tall multi-line message. Long single-line (soft-wrapped) messages don't trigger it — only messages with embedded newlines.
Root cause. _win_print_internal grew the ncurses pad from the current cursor only (_win_ensure_pad_capacity(getcury)), not the height of the message about to be printed. A tall multi-line message printed near the pad bottom got clipped, so its captured height was wrong (e.g. 1 row for a 49-row message). win_redraw later re-rendered it in full, and the resulting buffer->lines / y_start_pos mismatch desynced the page-up scroll anchor → the jump. Regression from the upstream sync
72f4f186d, which replaced the fixed-size pad with the dynamic _win_ensure_pad_capacity model.Fix. Estimate the message's rendered height (hard newlines + soft-wrap over the usable width) and reserve that many rows before printing, so it's never clipped and its captured height matches the redraw. One spot in _win_print_internal → covers all print paths (incoming/outgoing/history).
Verification. Reproduced on a real flatfile history (terminal 212×47 and 74×219). Instrumented logs showed the clipped message captured as 1 row vs 49 on redraw (Δ+61 total → ~page jump); with the fix, per-message captured height == redraw height for all entries, buffer->lines stays consistent, and the jump is gone. Builds clean (-Werror, full features); clang-format clean.
de172f3533toadb078a3e2WIP: fix(ui): reserve pad height per message to prevent multi-line clip and scroll desyncto fix(ui): reserve pad height per message to prevent multi-line clip and scroll desyncLGTM
@@ -70,0 +73,4 @@_win_estimated_lines(WINDOW* win, const char* const message, int indent, int pad_indent){int usable = getmaxx(win) - (indent + pad_indent);if (usable < 1) {We might also use
MAX(1, ...)instead, but this variant is also fine.Agreed, used MAX instead
@@ -70,0 +77,4 @@usable = 1;}int newlines = 0;for (const char* p = message; *p; p++) {There are guarantees that messages are null-terminated, right?
Yes — message is a NUL-terminated C string. The buffer stores the body via strdup (STRDUP_OR_NULL, buffer.c), and _win_print_internal already relies on NUL-termination above this point: strncmp(message, "/me ", 4), wprintw("%s", message + offset), and _win_print_wrapped calls strlen(message). The estimate loop uses the same invariant — nothing new. (message + offset is also safe: offset is 4 only when strncmp(message, "/me ", 4) matched, i.e. the string is ≥ 4 chars.)
adb078a3e2to13fa1bf108LGTM
13fa1bf108tob0e486367eb0e486367eto2d3d1ced71