term.v

2.9 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}7576// decode is a key table: one case per byte or escape sequence, nothing carried between reads.77// It is long because there are many keys.78fn decode(bytes []u8) Key {79	if bytes.len == 1 {80		b := bytes[0]81		match b {82			0x03 {83				return Key{84					kind: .interrupt85				}86			}87			0x04 {88				return Key{89					kind: .eof90				}91			}92			0x09 {93				return Key{94					kind: .tab95				}96			}97			0x0d, 0x0a {98				return Key{99					kind: .enter100				}101			}102			0x7f, 0x08 {103				return Key{104					kind: .backspace105				}106			}107			0x1b {108				return Key{109					kind: .escape110				}111			}112			else {113				if b >= 0x20 {114					return Key{115						kind: .rune116						text: bytes.bytestr()117					}118				}119				return Key{120					kind: .unknown121				}122			}123		}124	}125	if bytes[0] == 0x1b {126		sequence := bytes[1..].bytestr()127		return match sequence {128			'[A', 'OA' {129				Key{130					kind: .up131				}132			}133			'[B', 'OB' {134				Key{135					kind: .down136				}137			}138			'[C', 'OC' {139				Key{140					kind: .right141				}142			}143			'[D', 'OD' {144				Key{145					kind: .left146				}147			}148			'[H', 'OH', '[1~' {149				Key{150					kind: .home151				}152			}153			'[F', 'OF', '[4~' {154				Key{155					kind: .end156				}157			}158			'[3~' {159				Key{160					kind: .delete161				}162			}163			'[5~' {164				Key{165					kind: .page_up166				}167			}168			'[6~' {169				Key{170					kind: .page_down171				}172			}173			'[Z' {174				Key{175					kind: .backtab176				}177			}178			// Function keys, focus reports: not Esc, which would179			// leave the view.180			else {181				Key{182					kind: .unknown183				}184			}185		}186	}187	// Anything else is typed text: a pasted run, or a multi-byte character the188	// shim gathered in one read.189	return Key{190		kind: .rune191		text: bytes.bytestr()192	}193}