manual.v

4.1 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}6970// page_text is the same small pager without manual sections, for diffs.71pub fn page_text(lines []string, title string) {72	enter_screen()73	defer { leave_screen() }74	mut page := 075	for {76		rows, cols := terminal_size()77		size := if rows > 2 { rows - 1 } else { 1 }78		last := last_page(lines.len, size)79		if page > last {80			page = last81		}82		start := page * size83		end := if start + size < lines.len { start + size } else { lines.len }84		mut frame := lines[start..end].map(fmt.fit(it, cols))85		for frame.len < size {86			frame << ''87		}88		frame << fmt.fit('${fmt.blue(title)}  ·  page ${page + 1} of ${last + 1}  ·  ↑↓ j/k  ·  q quit', cols)89		paint(render(frame))90		key := read_key()91		match key.kind {92			.interrupt, .eof, .escape, .enter { return }93			.down, .page_down { page = if page < last { page + 1 } else { page } }94			.up, .page_up { page = if page > 0 { page - 1 } else { 0 } }95			.rune {96				if key.text == 'q' {97					return98				}99				if key.text == 'j' || key.text == ' ' {100					page = if page < last { page + 1 } else { page }101				}102				if key.text == 'k' {103					page = if page > 0 { page - 1 } else { 0 }104				}105			}106			else {}107		}108	}109}110111fn last_page(count int, size int) int {112	return if count == 0 { 0 } else { (count - 1) / size }113}114115// manual_frame is one page padded to the screen's height, then the status bar.116pub fn manual_frame(lines []ManualLine, page int, size int, cols int) []string {117	start := page * size118	end := if start + size < lines.len { start + size } else { lines.len }119	mut frame := []string{}120	for line in lines[start..end] {121		frame << fmt.fit(line.text, cols)122	}123	for frame.len < size {124		frame << ''125	}126	bar := '${fmt.blue(section_at(lines, start))}  ·  ' +127		fmt.muted('page ${page + 1} of ${last_page(lines.len, size) + 1}  ·  tab: next section  ·  ↑↓ j/k  ·  q quit')128	frame << fmt.fit(bar, cols)129	return frame130}131132// section_at names the section a line belongs to: the nearest heading at or133// above it, as in Koh 2, which counts a heading on the top line as current.134fn section_at(lines []ManualLine, index int) string {135	for i := index; i >= 0; i-- {136		if i < lines.len && lines[i].section != '' {137			return lines[i].section138		}139	}140	return 'KOH'141}142143// section_page is the page holding the next heading after this page, or the144// nearest one above its top line; the page stays put when there is none.145pub fn section_page(lines []ManualLine, page int, size int, forward bool) int {146	if forward {147		for i := (page + 1) * size; i < lines.len; i++ {148			if lines[i].section != '' {149				return i / size150			}151		}152		return page153	}154	for i := page * size - 1; i >= 0; i-- {155		if lines[i].section != '' {156			return i / size157		}158	}159	return page160}