// +build !windows,!plan9package termimport ()// reader reads terminal escape sequences and decodes them into events.typereaderstruct {frfileReader}func ( *os.File) *reader { , := newFileReader()if != nil {// TODO(xiaq): Do not panic.panic() }return &reader{}}func ( *reader) () (Event, error) {returnreadEvent(.fr)}func ( *reader) () (Event, error) { , := readRune(.fr, -1)returnK(), }func ( *reader) () { .fr.Stop() .fr.Close()}// Used by readRune in readOne to signal end of current sequence.construneEndOfSeqrune = -1// Timeout for bytes in escape sequences. Modern terminal emulators send escape// sequences very fast, so 10ms is more than sufficient. SSH connections on a// slow link might be problematic though.varkeySeqTimeout = 10 * time.Millisecondfunc ( byteReaderWithTimeout) ( Event, error) {varrune , = readRune(, -1)if != nil {return } := string()// Attempts to read a rune within a timeout of keySeqTimeout. It returns // runeEndOfSeq if there is any error; the caller should terminate the // current sequence when it sees that value. :=func() rune { , := readRune(, keySeqTimeout)if != nil {returnruneEndOfSeq } += string()return } := func( string) { = seqError{, } }switch {case0x1b: // ^[ Escape := ()// According to https://unix.stackexchange.com/a/73697, rxvt and derivatives // prepend another ESC to a CSI-style or G3-style sequence to signal Alt. // If that happens, remember this now; it will be later picked up when parsing // those two kinds of sequences. // // issue #181 := falseif == 0x1b { = true = () }if == runeEndOfSeq {// TODO(xiaq): Error is swallowed. // Nothing follows. Taken as a lone Escape. = KeyEvent{'[', ui.Ctrl}break }switch {case'[':// A '[' follows. CSI style function key sequence. = ()if == runeEndOfSeq { = KeyEvent{'[', ui.Alt}return } := make([]int, 0, 2)varrune// Read an optional starter.switch {case'<': = = ()case'M':// Mouse event. := ()if == runeEndOfSeq { ("Incomplete mouse event")return } := ()if == runeEndOfSeq { ("Incomplete mouse event")return } := ()if == runeEndOfSeq { ("Incomplete mouse event")return } := true := int( & 3)if == 3 { = false = -1 } := mouseModify(int()) = MouseEvent{Pos{int() - 32, int() - 32}, , , }return } :for {switch {case == ';': = append(, 0)case'0' <= && <= '9':iflen() == 0 { = append(, 0) } := len() - 1 [] = []*10 + int(-'0')case == runeEndOfSeq:// Incomplete CSI. ("Incomplete CSI")returndefault: // Treat as a terminator.break } = () }if == 0 && == 'R' {// Cursor position report.iflen() != 2 { ("bad CPR")return } = CursorPosition{[0], [1]} } elseif == '<' && ( == 'm' || == 'M') {// SGR-style mouse event.iflen() != 3 { ("bad SGR mouse event")return } := == 'M' := [0] & 3 := mouseModify([0]) = MouseEvent{Pos{[2], [1]}, , , } } elseif == '~' && len() == 1 && ([0] == 200 || [0] == 201) { := [0] == 200 = PasteSetting() } else { := parseCSI(, , )if == (ui.Key{}) { ("bad CSI") } else {if { .Mod |= ui.Alt } = KeyEvent() } }case'O':// An 'O' follows. G3 style function key sequence: read one rune. = ()if == runeEndOfSeq {// Nothing follows after 'O'. Taken as Alt-O. = KeyEvent{'O', ui.Alt}return } , := g3Seq[]if {if { .Mod |= ui.Alt } = KeyEvent() } else { ("bad G3") }default:// Something other than '[' or 'O' follows. Taken as an // Alt-modified key, possibly also modified by Ctrl. := ctrlModify() .Mod |= ui.Alt = KeyEvent() }default: = KeyEvent(ctrlModify()) }return}// Determines whether a rune corresponds to a Ctrl-modified key and returns the// ui.Key the rune represents.func ( rune) ui.Key {switch {// TODO(xiaq): Are the following special cases universal?case0x0:returnui.K('`', ui.Ctrl) // ^@case0x1e:returnui.K('6', ui.Ctrl) // ^^case0x1f:returnui.K('/', ui.Ctrl) // ^_caseui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^?// Ambiguous Ctrl keys; prefer the non-Ctrl form as they are more likely.returnui.K()default:// Regular ui.Ctrl sequences.if0x1 <= && <= 0x1d {returnui.K(+0x40, ui.Ctrl) } }returnui.K()}// Tables for key sequences. Comments document which terminal emulators are// known to generate which sequences. The terminal emulators tested are// categorized into xterm (including actual xterm, libvte-based terminals,// Konsole and Terminal.app unless otherwise noted), urxvt, tmux.// G3-style key sequences: \eO followed by exactly one character. For instance,// \eOP is F1. These are pretty limited in that they cannot be extended to// support modifier keys, other than a leading \e for Alt (e.g. \e\eOP is// Alt-F1). Terminals that send G3-style key sequences typically switch to// sending a CSI-style key sequence when a non-Alt modifier key is pressed.varg3Seq = map[rune]ui.Key{// xterm, tmux -- only in Vim, depends on termios setting? // NOTE(xiaq): According to urxvt's manpage, \eO[ABCD] sequences are used for // Ctrl-Shift-modified arrow keys; however, this doesn't seem to be true for // urxvt 9.22 packaged by Debian; those keys simply send the same sequence // as Ctrl-modified keys (\eO[abcd]).'A': ui.K(ui.Up), 'B': ui.K(ui.Down), 'C': ui.K(ui.Right), 'D': ui.K(ui.Left),'H': ui.K(ui.Home), 'F': ui.K(ui.End), 'M': ui.K(ui.Insert),// urxvt'a': ui.K(ui.Up, ui.Ctrl), 'b': ui.K(ui.Down, ui.Ctrl),'c': ui.K(ui.Right, ui.Ctrl), 'd': ui.K(ui.Left, ui.Ctrl),// xterm, urxvt, tmux'P': ui.K(ui.F1), 'Q': ui.K(ui.F2), 'R': ui.K(ui.F3), 'S': ui.K(ui.F4),}// Tables for CSI-style key sequences. A CSI sequence is \e[ followed by zero or// more numerical arguments (separated by semicolons), ending in a non-numeric,// non-semicolon rune. They are used for many purposes, and CSI-style key// sequences are a subset of them.//// There are several variants of CSI-style key sequences; see comments above the// respective tables. In all variants, modifier keys are encoded in numerical// arguments; see xtermModify. Note that although the set of possible sequences// make it possible to express a very complete set of key combinations, they are// not always sent by terminals. For instance, many (if not most) terminals will// send the same sequence for Up when Shift-Up is pressed, even if Shift-Up is// expressible using the escape sequences described below.// CSI-style key sequences identified by the last rune. For instance, \e[A is// Up. When modified, two numerical arguments are added, the first always beging// 1 and the second identifying the modifier. For instance, \e[1;5A is Ctrl-Up.varcsiSeqByLast = map[rune]ui.Key{// xterm, urxvt, tmux'A': ui.K(ui.Up), 'B': ui.K(ui.Down), 'C': ui.K(ui.Right), 'D': ui.K(ui.Left),// urxvt'a': ui.K(ui.Up, ui.Shift), 'b': ui.K(ui.Down, ui.Shift),'c': ui.K(ui.Right, ui.Shift), 'd': ui.K(ui.Left, ui.Shift),// xterm (Terminal.app only sends those in alternate screen)'H': ui.K(ui.Home), 'F': ui.K(ui.End),// xterm, urxvt, tmux'Z': ui.K(ui.Tab, ui.Shift),}// CSI-style key sequences ending with '~' with by one or two numerical// arguments. The first argument identifies the key, and the optional second// argument identifies the modifier. For instance, \e[3~ is Delete, and \e[3;5~// is Ctrl-Delete.//// An alternative encoding of the modifier key, only known to be used by urxvt// (or for that matter, likely also rxvt) is to change the last rune: '$' for// Shift, '^' for Ctrl, and '@' for Ctrl+Shift. The numeric argument is kept// unchanged. For instance, \e[3^ is Ctrl-Delete.varcsiSeqTilde = map[int]rune{// tmux (NOTE: urxvt uses the pair for Find/Select)1: ui.Home, 4: ui.End,// xterm (Terminal.app sends ^M for Fn+Enter), urxvt, tmux2: ui.Insert,// xterm, urxvt, tmux3: ui.Delete,// xterm (Terminal.app only sends those in alternate screen), urxvt, tmux // NOTE: called Prior/Next in urxvt manpage5: ui.PageUp, 6: ui.PageDown,// urxvt7: ui.Home, 8: ui.End,// urxvt11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4,// xterm, urxvt, tmux // NOTE: 16 and 22 are unused15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8,20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12,}// CSI-style key sequences ending with '~', with the first argument always 27,// the second argument identifying the modifier, and the third argument// identifying the key. For instance, \e[27;5;9~ is Ctrl-Tab.//// NOTE(xiaq): The list is taken blindly from xterm-keys.c in the tmux source// tree. I do not have a keyboard-terminal combination that generate such// sequences, but assumably they are generated by some terminals for numpad// inputs.varcsiSeqTilde27 = map[int]rune{9: '\t', 13: '\r',33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',46: '.',48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',56: '8', 57: '9',58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',}// parseCSI parses a CSI-style key sequence. See comments above for all the 3// variants this function handles.func ( []int, rune, string) ui.Key {if , := csiSeqByLast[]; {iflen() == 0 {// Unmodified: \e[A (Up)return } elseiflen() == 2 && [0] == 1 {// Modified: \e[1;5A (Ctrl-Up)returnxtermModify(, [1], ) } else {returnui.Key{} } }switch {case'~':iflen() == 1 || len() == 2 {if , := csiSeqTilde[[0]]; { := ui.K()iflen() == 1 {// Unmodified: \e[5~ (e.g. PageUp)return }// Modified: \e[5;5~ (e.g. Ctrl-PageUp)returnxtermModify(, [1], ) } } elseiflen() == 3 && [0] == 27 {if , := csiSeqTilde27[[2]]; { := ui.K()returnxtermModify(, [1], ) } }case'$', '^', '@':// Modified by urxvt; see comment above csiSeqTilde.iflen() == 1 {if , := csiSeqTilde[[0]]; {varui.Modswitch {case'$': = ui.Shiftcase'^': = ui.Ctrlcase'@': = ui.Shift | ui.Ctrl }returnui.K(, ) } } }returnui.Key{}}func ( ui.Key, int, string) ui.Key {if < 0 || > 16 {// Out of rangereturnui.Key{} }if == 0 {return } := - 1if &0x1 != 0 { .Mod |= ui.Shift }if &0x2 != 0 { .Mod |= ui.Alt }if &0x4 != 0 { .Mod |= ui.Ctrl }if &0x8 != 0 {// This should be Meta, but we currently conflate Meta and Alt. .Mod |= ui.Alt }return}func ( int) ui.Mod {varui.Modif &4 != 0 { |= ui.Shift }if &8 != 0 { |= ui.Alt }if &16 != 0 { |= ui.Ctrl }return}
The pages are generated with Goldsv0.2.8-preview. (GOOS=darwin GOARCH=arm64)