309cf693ce0f
fix man pager ghosting on scroll and section moves (redraw climbs exactly the body+footer rows; every body row emits its newline so frames no longer drift and leave ghost rows; exit and help clear the full frame) and lift body text from website --text-mid to a light warm tone so paragraphs read on a dark terminal; bump v3.4.5
- time
- 2026-07-01 16:33 UTC · 3 months ago
- machine
- koh2
- agent
- glm-5.2
- session
- man pager ghosting and contrast
- parent
- a36b421dce6b
↓ nina-309cf693.tar.gzatlas at this save
files changed2 files
VERSION+1 −1src/man.zig+33 −20VERSION+1 −1
@@ -1 +1 @@1-3.4.4\ No newline at end of file1+3.4.5\ No newline at end of filesrc/man.zig+33 −20
@@ -21,7 +21,11 @@2121 const GOLD = "\x1b[38;2;173;138;80m"; // --gold (#AD8A50)2222 const MINT = "\x1b[38;2;112;136;129m"; // --mint (#708881)2323 const LAV = "\x1b[38;2;122;129;98m"; // --lavender (#7A8162)24-const TEXT = "\x1b[38;2;102;93;85m"; // --text-mid (#665D55)24+// Body text. The website's --text-mid (#665D55) is calibrated for a cream25+// background; on a dark terminal it sinks in and becomes unreadable. Lifted to26+// a warm light tone so paragraphs, callout bodies, and flag descriptions read27+// against the terminal's default (usually dark) background.28+const TEXT = "\x1b[38;2;217;207;195m"; // body text, lightened for dark terminals2529 const TEXT_DIM = "\x1b[38;2;136;124;113m"; // --text-dim (#887C71)2630 const BG_HI = "\x1b[48;2;238;230;219m"; // --bg-surface (#EEE6DB)2731 const BG_BAR = "\x1b[48;2;205;191;174m"; // --bg-deep (#CDBFAE)@@ -484,8 +488,14 @@484488 485489 // --- Pager state ---486490 487-const PAGE_ROWS: u16 = 22; // visible content rows; header + footer reserve 4491+const PAGE_ROWS: u16 = 22; // visible content rows488492 const COL_WIDTH: u16 = 96; // wrap width for paragraphs493+// Frame geometry. The header paints exactly HEADER_ROWS lines and the footer494+// FOOTER_ROWS; redraw arithmetic depends on these being right, so name them495+// instead of magic +2 guesses that drift.496+const HEADER_ROWS: u16 = 3;497+const FOOTER_ROWS: u16 = 1;498+const FRAME_ROWS: u16 = HEADER_ROWS + PAGE_ROWS + FOOTER_ROWS;489499 490500 pub fn run(io: std.Io, topic: []const u8) !void {491501 // Plain fallback for non-tty (piped, redirected). Print every section in@@ -605,20 +615,25 @@605615 606616 if (redraw) {607617 cur_section = sectionOf(section_starts.items, top);608- output.cursorUp(PAGE_ROWS + 2);618+ // Header is static; repaint only the body + footer below it. Move up619+ // exactly the rows we repaint. The old `+ 2` overshot by one, so each620+ // redraw crept the frame up a row and left the previous footer621+ // behind as a ghost line.622+ output.cursorUp(PAGE_ROWS + FOOTER_ROWS);609623 drawBody(rendered.items, top, total, section_titles.items, cur_section);610624 drawFooter(cur_section, MAN_PAGE.len, top, total);611625 output.flush();612626 }613627 }614628 615- // Repaint the last screen without the highlight so the user lands on clean616- // text when the pager exits.617- output.cursorUp(PAGE_ROWS + 2);618- for (0..PAGE_ROWS + 2) |_| {629+ // Clear the whole inline frame on exit — header included — so the shell630+ // prompt lands on a clean line. The old PAGE_ROWS + 2 count missed the top631+ // two header rows, leaving them stranded above the prompt.632+ output.cursorUp(FRAME_ROWS);633+ for (0..FRAME_ROWS) |_| {619634 output.raw("\x1b[K\n");620635 }621- output.cursorUp(PAGE_ROWS + 2);636+ output.cursorUp(FRAME_ROWS);622637 output.flush();623638 }624639 @@ -819,23 +834,21 @@819834 fn drawBody(lines: []const []const u8, top: usize, total: usize, titles: []const []const u8, cur_section: usize) void {820835 _ = total;821836 _ = titles;837+ _ = cur_section;838+ // Each iteration must advance the cursor exactly one row: \x1b[K wipes the839+ // previous frame's leftovers on this row, then the line's own trailing840+ // newline moves down. The old chapter-divider special case wrote nothing —841+ // no newline — so a frame with a divider mid-viewport drew fewer rows than842+ // the cursor had climbed, and stale rows bled through as ghosting.822843 var i: usize = 0;823844 while (i < PAGE_ROWS) : (i += 1) {824845 output.raw("\x1b[K");825846 const idx = top + i;826847 if (idx < lines.len) {827- const line = lines[idx];828- const is_chapter = line.len > 0 and std.mem.indexOf(u8, line, "── ") != null and std.mem.indexOf(u8, line, "◆") == null;829- if (is_chapter and i > 0) {830- // Don't re-print chapter dividers that scroll past the top — feels jarring.831- output.raw("");832- } else {833- output.raw(line);834- }848+ output.raw(lines[idx]);835849 } else {836850 output.raw("\n");837851 }838- _ = cur_section;839852 }840853 }841854 @@ -857,9 +870,9 @@857870 }858871 859872 fn showHelp() void {860- output.cursorUp(PAGE_ROWS + 2);861- for (0..PAGE_ROWS + 2) |_| output.raw("\x1b[K\n");862- output.cursorUp(PAGE_ROWS + 2);873+ output.cursorUp(FRAME_ROWS);874+ for (0..FRAME_ROWS) |_| output.raw("\x1b[K\n");875+ output.cursorUp(FRAME_ROWS);863876 drawHeader();864877 drawHelpBody();865878 drawFooter(0, MAN_PAGE.len, 0, 1);