manual.v

3.0 KB · raw

1//! The manual's pager, Koh 2's `runMan` (`src/pager.zig`): a page at a time,2//! Tab and Shift-Tab jump between sections, and the status bar names the3//! section at the top of the page.45module view67import fmt89// ManualLine is one already-coloured line of the manual. `section` is the10// heading's name on a section heading, and empty everywhere else.11pub struct ManualLine {12pub:13	text    string14	section string15}1617// page_manual shows the lines until the user quits.18pub fn page_manual(lines []ManualLine) {19	enter_screen()20	defer {21		leave_screen()22	}23	mut page := 024	for {25		rows, cols := terminal_size()26		size := if rows > 2 { rows - 1 } else { 1 }27		last := last_page(lines.len, size)28		if page > last {29			page = last30		}31		paint(render(manual_frame(lines, page, size, cols)))32		key := read_key()33		match key.kind {34			.interrupt, .eof, .escape, .enter {35				return36			}37			.down, .page_down {38				page = if page < last { page + 1 } else { page }39			}40			.up, .page_up {41				page = if page > 0 { page - 1 } else { 0 }42			}43			.home {44				page = 045			}46			.end {47				page = last48			}49			.tab {50				page = section_page(lines, page, size, true)51			}52			.backtab {53				page = section_page(lines, page, size, false)54			}55			.rune {56				match key.text {57					'q' { return }58					'j', ' ' { page = if page < last { page + 1 } else { page } }59					'k' { page = if page > 0 { page - 1 } else { 0 } }60					'g' { page = 0 }61					'G' { page = last }62					else {}63				}64			}65			else {}66		}67	}68}6970fn last_page(count int, size int) int {71	return if count == 0 { 0 } else { (count - 1) / size }72}7374// manual_frame is one page padded to the screen's height, then the status bar.75pub fn manual_frame(lines []ManualLine, page int, size int, cols int) []string {76	start := page * size77	end := if start + size < lines.len { start + size } else { lines.len }78	mut frame := []string{}79	for line in lines[start..end] {80		frame << fmt.fit(line.text, cols)81	}82	for frame.len < size {83		frame << ''84	}85	bar := '${fmt.blue(section_at(lines, start))}  ·  ' +86		fmt.muted('page ${page + 1} of ${last_page(lines.len, size) + 1}  ·  tab: next section  ·  ↑↓ j/k  ·  q quit')87	frame << fmt.fit(bar, cols)88	return frame89}9091// section_at names the section a line belongs to: the nearest heading at or92// above it, as in Koh 2, which counts a heading on the top line as current.93fn section_at(lines []ManualLine, index int) string {94	for i := index; i >= 0; i-- {95		if i < lines.len && lines[i].section != '' {96			return lines[i].section97		}98	}99	return 'KOH'100}101102// section_page is the page holding the next heading after this page, or the103// nearest one above its top line; the page stays put when there is none.104pub fn section_page(lines []ManualLine, page int, size int, forward bool) int {105	if forward {106		for i := (page + 1) * size; i < lines.len; i++ {107			if lines[i].section != '' {108				return i / size109			}110		}111		return page112	}113	for i := page * size - 1; i >= 0; i-- {114		if lines[i].section != '' {115			return i / size116		}117	}118	return page119}