//! The manual's pager, Koh 2's `runMan` (`src/pager.zig`): a page at a time, //! Tab and Shift-Tab jump between sections, and the status bar names the //! section at the top of the page. module view import fmt // ManualLine is one already-coloured line of the manual. `section` is the // heading's name on a section heading, and empty everywhere else. pub struct ManualLine { pub: text string section string } // page_manual shows the lines until the user quits. pub fn page_manual(lines []ManualLine) { enter_screen() defer { leave_screen() } mut page := 0 for { rows, cols := terminal_size() size := if rows > 2 { rows - 1 } else { 1 } last := last_page(lines.len, size) if page > last { page = last } paint(render(manual_frame(lines, page, size, cols))) key := read_key() match key.kind { .interrupt, .eof, .escape, .enter { return } .down, .page_down { page = if page < last { page + 1 } else { page } } .up, .page_up { page = if page > 0 { page - 1 } else { 0 } } .home { page = 0 } .end { page = last } .tab { page = section_page(lines, page, size, true) } .backtab { page = section_page(lines, page, size, false) } .rune { match key.text { 'q' { return } 'j', ' ' { page = if page < last { page + 1 } else { page } } 'k' { page = if page > 0 { page - 1 } else { 0 } } 'g' { page = 0 } 'G' { page = last } else {} } } else {} } } } // page_text is the same small pager without manual sections, for diffs. pub fn page_text(lines []string, title string) { enter_screen() defer { leave_screen() } mut page := 0 for { rows, cols := terminal_size() size := if rows > 2 { rows - 1 } else { 1 } last := last_page(lines.len, size) if page > last { page = last } start := page * size end := if start + size < lines.len { start + size } else { lines.len } mut frame := lines[start..end].map(fmt.fit(it, cols)) for frame.len < size { frame << '' } frame << fmt.fit('${fmt.blue(title)} · page ${page + 1} of ${last + 1} · ↑↓ j/k · q quit', cols) paint(render(frame)) key := read_key() match key.kind { .interrupt, .eof, .escape, .enter { return } .down, .page_down { page = if page < last { page + 1 } else { page } } .up, .page_up { page = if page > 0 { page - 1 } else { 0 } } .rune { if key.text == 'q' { return } if key.text == 'j' || key.text == ' ' { page = if page < last { page + 1 } else { page } } if key.text == 'k' { page = if page > 0 { page - 1 } else { 0 } } } else {} } } } fn last_page(count int, size int) int { return if count == 0 { 0 } else { (count - 1) / size } } // manual_frame is one page padded to the screen's height, then the status bar. pub fn manual_frame(lines []ManualLine, page int, size int, cols int) []string { start := page * size end := if start + size < lines.len { start + size } else { lines.len } mut frame := []string{} for line in lines[start..end] { frame << fmt.fit(line.text, cols) } for frame.len < size { frame << '' } bar := '${fmt.blue(section_at(lines, start))} · ' + fmt.muted('page ${page + 1} of ${last_page(lines.len, size) + 1} · tab: next section · ↑↓ j/k · q quit') frame << fmt.fit(bar, cols) return frame } // section_at names the section a line belongs to: the nearest heading at or // above it, as in Koh 2, which counts a heading on the top line as current. fn section_at(lines []ManualLine, index int) string { for i := index; i >= 0; i-- { if i < lines.len && lines[i].section != '' { return lines[i].section } } return 'KOH' } // section_page is the page holding the next heading after this page, or the // nearest one above its top line; the page stays put when there is none. pub fn section_page(lines []ManualLine, page int, size int, forward bool) int { if forward { for i := (page + 1) * size; i < lines.len; i++ { if lines[i].section != '' { return i / size } } return page } for i := page * size - 1; i >= 0; i-- { if lines[i].section != '' { return i / size } } return page }