//! Terminal control and key decoding for the history view. //! //! The view owns the screen while it runs, so raw mode and the alternate screen //! have to be entered and left reliably, on every exit path. Rendering is a //! plain string of ANSI escapes written in one call per frame — no redraw //! diffing, because the whole frame is small and the terminal does that faster //! than we can. module view #flag -I@VMODROOT/vendor/shim #flag @VMODROOT/vendor/shim/koh_term.c #include "koh_term.h" fn C.koh_term_raw(enable int) int fn C.koh_term_size(rows &int, cols &int) int fn C.koh_read_key(buf &char, cap int) int pub enum KeyKind { rune up down left right enter escape tab backtab backspace delete home end page_up page_down interrupt eof unknown } pub struct Key { pub: kind KeyKind text string } pub fn terminal_size() (int, int) { mut rows := 0 mut cols := 0 C.koh_term_size(&rows, &cols) return rows, cols } pub fn raw_mode(enable bool) { if !enable { C.koh_term_raw(0) return } C.koh_term_raw(1) } // read_key blocks for one keystroke. `eof` means input ended (stdin closed or // Ctrl-D), which the view treats as quit. pub fn read_key() Key { mut buf := []u8{len: 16} n := C.koh_read_key(unsafe { &char(buf.data) }, buf.len) if n <= 0 { return Key{ kind: .eof } } return decode(buf[..n]) } // decode is a key table: one case per byte or escape sequence, nothing carried between reads. // It is long because there are many keys. fn decode(bytes []u8) Key { if bytes.len == 1 { b := bytes[0] match b { 0x03 { return Key{ kind: .interrupt } } 0x04 { return Key{ kind: .eof } } 0x09 { return Key{ kind: .tab } } 0x0d, 0x0a { return Key{ kind: .enter } } 0x7f, 0x08 { return Key{ kind: .backspace } } 0x1b { return Key{ kind: .escape } } else { if b >= 0x20 { return Key{ kind: .rune text: bytes.bytestr() } } return Key{ kind: .unknown } } } } if bytes[0] == 0x1b { sequence := bytes[1..].bytestr() return match sequence { '[A', 'OA' { Key{ kind: .up } } '[B', 'OB' { Key{ kind: .down } } '[C', 'OC' { Key{ kind: .right } } '[D', 'OD' { Key{ kind: .left } } '[H', 'OH', '[1~' { Key{ kind: .home } } '[F', 'OF', '[4~' { Key{ kind: .end } } '[3~' { Key{ kind: .delete } } '[5~' { Key{ kind: .page_up } } '[6~' { Key{ kind: .page_down } } '[Z' { Key{ kind: .backtab } } // Function keys, focus reports: not Esc, which would // leave the view. else { Key{ kind: .unknown } } } } // Anything else is typed text: a pasted run, or a multi-byte character the // shim gathered in one read. return Key{ kind: .rune text: bytes.bytestr() } }