term.v

2.8 KB · raw

1//! Terminal control and key decoding for the history view.2//!3//! The view owns the screen while it runs, so raw mode and the alternate screen4//! have to be entered and left reliably, on every exit path. Rendering is a5//! plain string of ANSI escapes written in one call per frame — no redraw6//! diffing, because the whole frame is small and the terminal does that faster7//! than we can.89module view1011#flag -I@VMODROOT/vendor/shim12#flag @VMODROOT/vendor/shim/koh_term.c13#include "koh_term.h"1415fn C.koh_term_raw(enable int) int1617fn C.koh_term_size(rows &int, cols &int) int1819fn C.koh_read_key(buf &char, cap int) int2021pub enum KeyKind {22	rune23	up24	down25	left26	right27	enter28	escape29	tab30	backtab31	backspace32	delete33	home34	end35	page_up36	page_down37	interrupt38	eof39	unknown40}4142pub struct Key {43pub:44	kind KeyKind45	text string46}4748pub fn terminal_size() (int, int) {49	mut rows := 050	mut cols := 051	C.koh_term_size(&rows, &cols)52	return rows, cols53}5455pub fn raw_mode(enable bool) {56	if !enable {57		C.koh_term_raw(0)58		return59	}60	C.koh_term_raw(1)61}6263// read_key blocks for one keystroke. `eof` means input ended (stdin closed or64// Ctrl-D), which the view treats as quit.65pub fn read_key() Key {66	mut buf := []u8{len: 16}67	n := C.koh_read_key(unsafe { &char(buf.data) }, buf.len)68	if n <= 0 {69		return Key{70			kind: .eof71		}72	}73	return decode(buf[..n])74}7576fn decode(bytes []u8) Key {77	if bytes.len == 1 {78		b := bytes[0]79		match b {80			0x03 {81				return Key{82					kind: .interrupt83				}84			}85			0x04 {86				return Key{87					kind: .eof88				}89			}90			0x09 {91				return Key{92					kind: .tab93				}94			}95			0x0d, 0x0a {96				return Key{97					kind: .enter98				}99			}100			0x7f, 0x08 {101				return Key{102					kind: .backspace103				}104			}105			0x1b {106				return Key{107					kind: .escape108				}109			}110			else {111				if b >= 0x20 {112					return Key{113						kind: .rune114						text: bytes.bytestr()115					}116				}117				return Key{118					kind: .unknown119				}120			}121		}122	}123	if bytes[0] == 0x1b {124		sequence := bytes[1..].bytestr()125		return match sequence {126			'[A', 'OA' {127				Key{128					kind: .up129				}130			}131			'[B', 'OB' {132				Key{133					kind: .down134				}135			}136			'[C', 'OC' {137				Key{138					kind: .right139				}140			}141			'[D', 'OD' {142				Key{143					kind: .left144				}145			}146			'[H', 'OH', '[1~' {147				Key{148					kind: .home149				}150			}151			'[F', 'OF', '[4~' {152				Key{153					kind: .end154				}155			}156			'[3~' {157				Key{158					kind: .delete159				}160			}161			'[5~' {162				Key{163					kind: .page_up164				}165			}166			'[6~' {167				Key{168					kind: .page_down169				}170			}171			'[Z' {172				Key{173					kind: .backtab174				}175			}176			// Function keys, focus reports: not Esc, which would177			// leave the view.178			else {179				Key{180					kind: .unknown181				}182			}183		}184	}185	// Anything else is typed text: a pasted run, or a multi-byte character the186	// shim gathered in one read.187	return Key{188		kind: .rune189		text: bytes.bytestr()190	}191}