|
@@ -0,0 +1,664 @@
|
|
|
|
|
+package tea
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "errors"
|
|
|
|
|
+ "io"
|
|
|
|
|
+ "unicode/utf8"
|
|
|
|
|
+
|
|
|
|
|
+ "github.com/mattn/go-localereader"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// KeyMsg contains information about a keypress. KeyMsgs are always sent to
|
|
|
|
|
+// the program's update function. There are a couple general patterns you could
|
|
|
|
|
+// use to check for keypresses:
|
|
|
|
|
+//
|
|
|
|
|
+// // Switch on the string representation of the key (shorter)
|
|
|
|
|
+// switch msg := msg.(type) {
|
|
|
|
|
+// case KeyMsg:
|
|
|
|
|
+// switch msg.String() {
|
|
|
|
|
+// case "enter":
|
|
|
|
|
+// fmt.Println("you pressed enter!")
|
|
|
|
|
+// case "a":
|
|
|
|
|
+// fmt.Println("you pressed a!")
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+//
|
|
|
|
|
+// // Switch on the key type (more foolproof)
|
|
|
|
|
+// switch msg := msg.(type) {
|
|
|
|
|
+// case KeyMsg:
|
|
|
|
|
+// switch msg.Type {
|
|
|
|
|
+// case KeyEnter:
|
|
|
|
|
+// fmt.Println("you pressed enter!")
|
|
|
|
|
+// case KeyRunes:
|
|
|
|
|
+// switch string(msg.Runes) {
|
|
|
|
|
+// case "a":
|
|
|
|
|
+// fmt.Println("you pressed a!")
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+//
|
|
|
|
|
+// Note that Key.Runes will always contain at least one character, so you can
|
|
|
|
|
+// always safely call Key.Runes[0]. In most cases Key.Runes will only contain
|
|
|
|
|
+// one character, though certain input method editors (most notably Chinese
|
|
|
|
|
+// IMEs) can input multiple runes at once.
|
|
|
|
|
+type KeyMsg Key
|
|
|
|
|
+
|
|
|
|
|
+// String returns a string representation for a key message. It's safe (and
|
|
|
|
|
+// encouraged) for use in key comparison.
|
|
|
|
|
+func (k KeyMsg) String() (str string) {
|
|
|
|
|
+ return Key(k).String()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Key contains information about a keypress.
|
|
|
|
|
+type Key struct {
|
|
|
|
|
+ Type KeyType
|
|
|
|
|
+ Runes []rune
|
|
|
|
|
+ Alt bool
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// String returns a friendly string representation for a key. It's safe (and
|
|
|
|
|
+// encouraged) for use in key comparison.
|
|
|
|
|
+//
|
|
|
|
|
+// k := Key{Type: KeyEnter}
|
|
|
|
|
+// fmt.Println(k)
|
|
|
|
|
+// // Output: enter
|
|
|
|
|
+func (k Key) String() (str string) {
|
|
|
|
|
+ if k.Alt {
|
|
|
|
|
+ str += "alt+"
|
|
|
|
|
+ }
|
|
|
|
|
+ if k.Type == KeyRunes {
|
|
|
|
|
+ str += string(k.Runes)
|
|
|
|
|
+ return str
|
|
|
|
|
+ } else if s, ok := keyNames[k.Type]; ok {
|
|
|
|
|
+ str += s
|
|
|
|
|
+ return str
|
|
|
|
|
+ }
|
|
|
|
|
+ return ""
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// KeyType indicates the key pressed, such as KeyEnter or KeyBreak or KeyCtrlC.
|
|
|
|
|
+// All other keys will be type KeyRunes. To get the rune value, check the Rune
|
|
|
|
|
+// method on a Key struct, or use the Key.String() method:
|
|
|
|
|
+//
|
|
|
|
|
+// k := Key{Type: KeyRunes, Runes: []rune{'a'}, Alt: true}
|
|
|
|
|
+// if k.Type == KeyRunes {
|
|
|
|
|
+//
|
|
|
|
|
+// fmt.Println(k.Runes)
|
|
|
|
|
+// // Output: a
|
|
|
|
|
+//
|
|
|
|
|
+// fmt.Println(k.String())
|
|
|
|
|
+// // Output: alt+a
|
|
|
|
|
+//
|
|
|
|
|
+// }
|
|
|
|
|
+type KeyType int
|
|
|
|
|
+
|
|
|
|
|
+func (k KeyType) String() (str string) {
|
|
|
|
|
+ if s, ok := keyNames[k]; ok {
|
|
|
|
|
+ return s
|
|
|
|
|
+ }
|
|
|
|
|
+ return ""
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Control keys. We could do this with an iota, but the values are very
|
|
|
|
|
+// specific, so we set the values explicitly to avoid any confusion.
|
|
|
|
|
+//
|
|
|
|
|
+// See also:
|
|
|
|
|
+// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
|
|
|
|
|
+const (
|
|
|
|
|
+ keyNUL KeyType = 0 // null, \0
|
|
|
|
|
+ keySOH KeyType = 1 // start of heading
|
|
|
|
|
+ keySTX KeyType = 2 // start of text
|
|
|
|
|
+ keyETX KeyType = 3 // break, ctrl+c
|
|
|
|
|
+ keyEOT KeyType = 4 // end of transmission
|
|
|
|
|
+ keyENQ KeyType = 5 // enquiry
|
|
|
|
|
+ keyACK KeyType = 6 // acknowledge
|
|
|
|
|
+ keyBEL KeyType = 7 // bell, \a
|
|
|
|
|
+ keyBS KeyType = 8 // backspace
|
|
|
|
|
+ keyHT KeyType = 9 // horizontal tabulation, \t
|
|
|
|
|
+ keyLF KeyType = 10 // line feed, \n
|
|
|
|
|
+ keyVT KeyType = 11 // vertical tabulation \v
|
|
|
|
|
+ keyFF KeyType = 12 // form feed \f
|
|
|
|
|
+ keyCR KeyType = 13 // carriage return, \r
|
|
|
|
|
+ keySO KeyType = 14 // shift out
|
|
|
|
|
+ keySI KeyType = 15 // shift in
|
|
|
|
|
+ keyDLE KeyType = 16 // data link escape
|
|
|
|
|
+ keyDC1 KeyType = 17 // device control one
|
|
|
|
|
+ keyDC2 KeyType = 18 // device control two
|
|
|
|
|
+ keyDC3 KeyType = 19 // device control three
|
|
|
|
|
+ keyDC4 KeyType = 20 // device control four
|
|
|
|
|
+ keyNAK KeyType = 21 // negative acknowledge
|
|
|
|
|
+ keySYN KeyType = 22 // synchronous idle
|
|
|
|
|
+ keyETB KeyType = 23 // end of transmission block
|
|
|
|
|
+ keyCAN KeyType = 24 // cancel
|
|
|
|
|
+ keyEM KeyType = 25 // end of medium
|
|
|
|
|
+ keySUB KeyType = 26 // substitution
|
|
|
|
|
+ keyESC KeyType = 27 // escape, \e
|
|
|
|
|
+ keyFS KeyType = 28 // file separator
|
|
|
|
|
+ keyGS KeyType = 29 // group separator
|
|
|
|
|
+ keyRS KeyType = 30 // record separator
|
|
|
|
|
+ keyUS KeyType = 31 // unit separator
|
|
|
|
|
+ keyDEL KeyType = 127 // delete. on most systems this is mapped to backspace, I hear
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// Control key aliases.
|
|
|
|
|
+const (
|
|
|
|
|
+ KeyNull KeyType = keyNUL
|
|
|
|
|
+ KeyBreak KeyType = keyETX
|
|
|
|
|
+ KeyEnter KeyType = keyCR
|
|
|
|
|
+ KeyBackspace KeyType = keyDEL
|
|
|
|
|
+ KeyTab KeyType = keyHT
|
|
|
|
|
+ KeyEsc KeyType = keyESC
|
|
|
|
|
+ KeyEscape KeyType = keyESC
|
|
|
|
|
+
|
|
|
|
|
+ KeyCtrlAt KeyType = keyNUL // ctrl+@
|
|
|
|
|
+ KeyCtrlA KeyType = keySOH
|
|
|
|
|
+ KeyCtrlB KeyType = keySTX
|
|
|
|
|
+ KeyCtrlC KeyType = keyETX
|
|
|
|
|
+ KeyCtrlD KeyType = keyEOT
|
|
|
|
|
+ KeyCtrlE KeyType = keyENQ
|
|
|
|
|
+ KeyCtrlF KeyType = keyACK
|
|
|
|
|
+ KeyCtrlG KeyType = keyBEL
|
|
|
|
|
+ KeyCtrlH KeyType = keyBS
|
|
|
|
|
+ KeyCtrlI KeyType = keyHT
|
|
|
|
|
+ KeyCtrlJ KeyType = keyLF
|
|
|
|
|
+ KeyCtrlK KeyType = keyVT
|
|
|
|
|
+ KeyCtrlL KeyType = keyFF
|
|
|
|
|
+ KeyCtrlM KeyType = keyCR
|
|
|
|
|
+ KeyCtrlN KeyType = keySO
|
|
|
|
|
+ KeyCtrlO KeyType = keySI
|
|
|
|
|
+ KeyCtrlP KeyType = keyDLE
|
|
|
|
|
+ KeyCtrlQ KeyType = keyDC1
|
|
|
|
|
+ KeyCtrlR KeyType = keyDC2
|
|
|
|
|
+ KeyCtrlS KeyType = keyDC3
|
|
|
|
|
+ KeyCtrlT KeyType = keyDC4
|
|
|
|
|
+ KeyCtrlU KeyType = keyNAK
|
|
|
|
|
+ KeyCtrlV KeyType = keySYN
|
|
|
|
|
+ KeyCtrlW KeyType = keyETB
|
|
|
|
|
+ KeyCtrlX KeyType = keyCAN
|
|
|
|
|
+ KeyCtrlY KeyType = keyEM
|
|
|
|
|
+ KeyCtrlZ KeyType = keySUB
|
|
|
|
|
+ KeyCtrlOpenBracket KeyType = keyESC // ctrl+[
|
|
|
|
|
+ KeyCtrlBackslash KeyType = keyFS // ctrl+\
|
|
|
|
|
+ KeyCtrlCloseBracket KeyType = keyGS // ctrl+]
|
|
|
|
|
+ KeyCtrlCaret KeyType = keyRS // ctrl+^
|
|
|
|
|
+ KeyCtrlUnderscore KeyType = keyUS // ctrl+_
|
|
|
|
|
+ KeyCtrlQuestionMark KeyType = keyDEL // ctrl+?
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// Other keys.
|
|
|
|
|
+const (
|
|
|
|
|
+ KeyRunes KeyType = -(iota + 1)
|
|
|
|
|
+ KeyUp
|
|
|
|
|
+ KeyDown
|
|
|
|
|
+ KeyRight
|
|
|
|
|
+ KeyLeft
|
|
|
|
|
+ KeyShiftTab
|
|
|
|
|
+ KeyHome
|
|
|
|
|
+ KeyEnd
|
|
|
|
|
+ KeyPgUp
|
|
|
|
|
+ KeyPgDown
|
|
|
|
|
+ KeyCtrlPgUp
|
|
|
|
|
+ KeyCtrlPgDown
|
|
|
|
|
+ KeyDelete
|
|
|
|
|
+ KeyInsert
|
|
|
|
|
+ KeySpace
|
|
|
|
|
+ KeyCtrlUp
|
|
|
|
|
+ KeyCtrlDown
|
|
|
|
|
+ KeyCtrlRight
|
|
|
|
|
+ KeyCtrlLeft
|
|
|
|
|
+ KeyCtrlHome
|
|
|
|
|
+ KeyCtrlEnd
|
|
|
|
|
+ KeyShiftUp
|
|
|
|
|
+ KeyShiftDown
|
|
|
|
|
+ KeyShiftRight
|
|
|
|
|
+ KeyShiftLeft
|
|
|
|
|
+ KeyShiftHome
|
|
|
|
|
+ KeyShiftEnd
|
|
|
|
|
+ KeyCtrlShiftUp
|
|
|
|
|
+ KeyCtrlShiftDown
|
|
|
|
|
+ KeyCtrlShiftLeft
|
|
|
|
|
+ KeyCtrlShiftRight
|
|
|
|
|
+ KeyCtrlShiftHome
|
|
|
|
|
+ KeyCtrlShiftEnd
|
|
|
|
|
+ KeyF1
|
|
|
|
|
+ KeyF2
|
|
|
|
|
+ KeyF3
|
|
|
|
|
+ KeyF4
|
|
|
|
|
+ KeyF5
|
|
|
|
|
+ KeyF6
|
|
|
|
|
+ KeyF7
|
|
|
|
|
+ KeyF8
|
|
|
|
|
+ KeyF9
|
|
|
|
|
+ KeyF10
|
|
|
|
|
+ KeyF11
|
|
|
|
|
+ KeyF12
|
|
|
|
|
+ KeyF13
|
|
|
|
|
+ KeyF14
|
|
|
|
|
+ KeyF15
|
|
|
|
|
+ KeyF16
|
|
|
|
|
+ KeyF17
|
|
|
|
|
+ KeyF18
|
|
|
|
|
+ KeyF19
|
|
|
|
|
+ KeyF20
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// Mappings for control keys and other special keys to friendly consts.
|
|
|
|
|
+var keyNames = map[KeyType]string{
|
|
|
|
|
+ // Control keys.
|
|
|
|
|
+ keyNUL: "ctrl+@", // also ctrl+` (that's ctrl+backtick)
|
|
|
|
|
+ keySOH: "ctrl+a",
|
|
|
|
|
+ keySTX: "ctrl+b",
|
|
|
|
|
+ keyETX: "ctrl+c",
|
|
|
|
|
+ keyEOT: "ctrl+d",
|
|
|
|
|
+ keyENQ: "ctrl+e",
|
|
|
|
|
+ keyACK: "ctrl+f",
|
|
|
|
|
+ keyBEL: "ctrl+g",
|
|
|
|
|
+ keyBS: "ctrl+h",
|
|
|
|
|
+ keyHT: "tab", // also ctrl+i
|
|
|
|
|
+ keyLF: "ctrl+j",
|
|
|
|
|
+ keyVT: "ctrl+k",
|
|
|
|
|
+ keyFF: "ctrl+l",
|
|
|
|
|
+ keyCR: "enter",
|
|
|
|
|
+ keySO: "ctrl+n",
|
|
|
|
|
+ keySI: "ctrl+o",
|
|
|
|
|
+ keyDLE: "ctrl+p",
|
|
|
|
|
+ keyDC1: "ctrl+q",
|
|
|
|
|
+ keyDC2: "ctrl+r",
|
|
|
|
|
+ keyDC3: "ctrl+s",
|
|
|
|
|
+ keyDC4: "ctrl+t",
|
|
|
|
|
+ keyNAK: "ctrl+u",
|
|
|
|
|
+ keySYN: "ctrl+v",
|
|
|
|
|
+ keyETB: "ctrl+w",
|
|
|
|
|
+ keyCAN: "ctrl+x",
|
|
|
|
|
+ keyEM: "ctrl+y",
|
|
|
|
|
+ keySUB: "ctrl+z",
|
|
|
|
|
+ keyESC: "esc",
|
|
|
|
|
+ keyFS: "ctrl+\\",
|
|
|
|
|
+ keyGS: "ctrl+]",
|
|
|
|
|
+ keyRS: "ctrl+^",
|
|
|
|
|
+ keyUS: "ctrl+_",
|
|
|
|
|
+ keyDEL: "backspace",
|
|
|
|
|
+
|
|
|
|
|
+ // Other keys.
|
|
|
|
|
+ KeyRunes: "runes",
|
|
|
|
|
+ KeyUp: "up",
|
|
|
|
|
+ KeyDown: "down",
|
|
|
|
|
+ KeyRight: "right",
|
|
|
|
|
+ KeySpace: " ", // for backwards compatibility
|
|
|
|
|
+ KeyLeft: "left",
|
|
|
|
|
+ KeyShiftTab: "shift+tab",
|
|
|
|
|
+ KeyHome: "home",
|
|
|
|
|
+ KeyEnd: "end",
|
|
|
|
|
+ KeyCtrlHome: "ctrl+home",
|
|
|
|
|
+ KeyCtrlEnd: "ctrl+end",
|
|
|
|
|
+ KeyShiftHome: "shift+home",
|
|
|
|
|
+ KeyShiftEnd: "shift+end",
|
|
|
|
|
+ KeyCtrlShiftHome: "ctrl+shift+home",
|
|
|
|
|
+ KeyCtrlShiftEnd: "ctrl+shift+end",
|
|
|
|
|
+ KeyPgUp: "pgup",
|
|
|
|
|
+ KeyPgDown: "pgdown",
|
|
|
|
|
+ KeyCtrlPgUp: "ctrl+pgup",
|
|
|
|
|
+ KeyCtrlPgDown: "ctrl+pgdown",
|
|
|
|
|
+ KeyDelete: "delete",
|
|
|
|
|
+ KeyInsert: "insert",
|
|
|
|
|
+ KeyCtrlUp: "ctrl+up",
|
|
|
|
|
+ KeyCtrlDown: "ctrl+down",
|
|
|
|
|
+ KeyCtrlRight: "ctrl+right",
|
|
|
|
|
+ KeyCtrlLeft: "ctrl+left",
|
|
|
|
|
+ KeyShiftUp: "shift+up",
|
|
|
|
|
+ KeyShiftDown: "shift+down",
|
|
|
|
|
+ KeyShiftRight: "shift+right",
|
|
|
|
|
+ KeyShiftLeft: "shift+left",
|
|
|
|
|
+ KeyCtrlShiftUp: "ctrl+shift+up",
|
|
|
|
|
+ KeyCtrlShiftDown: "ctrl+shift+down",
|
|
|
|
|
+ KeyCtrlShiftLeft: "ctrl+shift+left",
|
|
|
|
|
+ KeyCtrlShiftRight: "ctrl+shift+right",
|
|
|
|
|
+ KeyF1: "f1",
|
|
|
|
|
+ KeyF2: "f2",
|
|
|
|
|
+ KeyF3: "f3",
|
|
|
|
|
+ KeyF4: "f4",
|
|
|
|
|
+ KeyF5: "f5",
|
|
|
|
|
+ KeyF6: "f6",
|
|
|
|
|
+ KeyF7: "f7",
|
|
|
|
|
+ KeyF8: "f8",
|
|
|
|
|
+ KeyF9: "f9",
|
|
|
|
|
+ KeyF10: "f10",
|
|
|
|
|
+ KeyF11: "f11",
|
|
|
|
|
+ KeyF12: "f12",
|
|
|
|
|
+ KeyF13: "f13",
|
|
|
|
|
+ KeyF14: "f14",
|
|
|
|
|
+ KeyF15: "f15",
|
|
|
|
|
+ KeyF16: "f16",
|
|
|
|
|
+ KeyF17: "f17",
|
|
|
|
|
+ KeyF18: "f18",
|
|
|
|
|
+ KeyF19: "f19",
|
|
|
|
|
+ KeyF20: "f20",
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Sequence mappings.
|
|
|
|
|
+var sequences = map[string]Key{
|
|
|
|
|
+ // Arrow keys
|
|
|
|
|
+ "\x1b[A": {Type: KeyUp},
|
|
|
|
|
+ "\x1b[B": {Type: KeyDown},
|
|
|
|
|
+ "\x1b[C": {Type: KeyRight},
|
|
|
|
|
+ "\x1b[D": {Type: KeyLeft},
|
|
|
|
|
+ "\x1b[1;2A": {Type: KeyShiftUp},
|
|
|
|
|
+ "\x1b[1;2B": {Type: KeyShiftDown},
|
|
|
|
|
+ "\x1b[1;2C": {Type: KeyShiftRight},
|
|
|
|
|
+ "\x1b[1;2D": {Type: KeyShiftLeft},
|
|
|
|
|
+ "\x1b[OA": {Type: KeyShiftUp}, // DECCKM
|
|
|
|
|
+ "\x1b[OB": {Type: KeyShiftDown}, // DECCKM
|
|
|
|
|
+ "\x1b[OC": {Type: KeyShiftRight}, // DECCKM
|
|
|
|
|
+ "\x1b[OD": {Type: KeyShiftLeft}, // DECCKM
|
|
|
|
|
+ "\x1b[a": {Type: KeyShiftUp}, // urxvt
|
|
|
|
|
+ "\x1b[b": {Type: KeyShiftDown}, // urxvt
|
|
|
|
|
+ "\x1b[c": {Type: KeyShiftRight}, // urxvt
|
|
|
|
|
+ "\x1b[d": {Type: KeyShiftLeft}, // urxvt
|
|
|
|
|
+ "\x1b[1;3A": {Type: KeyUp, Alt: true},
|
|
|
|
|
+ "\x1b[1;3B": {Type: KeyDown, Alt: true},
|
|
|
|
|
+ "\x1b[1;3C": {Type: KeyRight, Alt: true},
|
|
|
|
|
+ "\x1b[1;3D": {Type: KeyLeft, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[A": {Type: KeyUp, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[B": {Type: KeyDown, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[C": {Type: KeyRight, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[D": {Type: KeyLeft, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[1;4A": {Type: KeyShiftUp, Alt: true},
|
|
|
|
|
+ "\x1b[1;4B": {Type: KeyShiftDown, Alt: true},
|
|
|
|
|
+ "\x1b[1;4C": {Type: KeyShiftRight, Alt: true},
|
|
|
|
|
+ "\x1b[1;4D": {Type: KeyShiftLeft, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[a": {Type: KeyShiftUp, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[b": {Type: KeyShiftDown, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[c": {Type: KeyShiftRight, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[d": {Type: KeyShiftLeft, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[1;5A": {Type: KeyCtrlUp},
|
|
|
|
|
+ "\x1b[1;5B": {Type: KeyCtrlDown},
|
|
|
|
|
+ "\x1b[1;5C": {Type: KeyCtrlRight},
|
|
|
|
|
+ "\x1b[1;5D": {Type: KeyCtrlLeft},
|
|
|
|
|
+ "\x1b[Oa": {Type: KeyCtrlUp, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[Ob": {Type: KeyCtrlDown, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[Oc": {Type: KeyCtrlRight, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[Od": {Type: KeyCtrlLeft, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[1;6A": {Type: KeyCtrlShiftUp},
|
|
|
|
|
+ "\x1b[1;6B": {Type: KeyCtrlShiftDown},
|
|
|
|
|
+ "\x1b[1;6C": {Type: KeyCtrlShiftRight},
|
|
|
|
|
+ "\x1b[1;6D": {Type: KeyCtrlShiftLeft},
|
|
|
|
|
+ "\x1b[1;7A": {Type: KeyCtrlUp, Alt: true},
|
|
|
|
|
+ "\x1b[1;7B": {Type: KeyCtrlDown, Alt: true},
|
|
|
|
|
+ "\x1b[1;7C": {Type: KeyCtrlRight, Alt: true},
|
|
|
|
|
+ "\x1b[1;7D": {Type: KeyCtrlLeft, Alt: true},
|
|
|
|
|
+ "\x1b[1;8A": {Type: KeyCtrlShiftUp, Alt: true},
|
|
|
|
|
+ "\x1b[1;8B": {Type: KeyCtrlShiftDown, Alt: true},
|
|
|
|
|
+ "\x1b[1;8C": {Type: KeyCtrlShiftRight, Alt: true},
|
|
|
|
|
+ "\x1b[1;8D": {Type: KeyCtrlShiftLeft, Alt: true},
|
|
|
|
|
+
|
|
|
|
|
+ // Miscellaneous keys
|
|
|
|
|
+ "\x1b[Z": {Type: KeyShiftTab},
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[2~": {Type: KeyInsert},
|
|
|
|
|
+ "\x1b[3;2~": {Type: KeyInsert, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[2~": {Type: KeyInsert, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[3~": {Type: KeyDelete},
|
|
|
|
|
+ "\x1b[3;3~": {Type: KeyDelete, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[3~": {Type: KeyDelete, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[5~": {Type: KeyPgUp},
|
|
|
|
|
+ "\x1b[5;3~": {Type: KeyPgUp, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[5~": {Type: KeyPgUp, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[5;5~": {Type: KeyCtrlPgUp},
|
|
|
|
|
+ "\x1b[5^": {Type: KeyCtrlPgUp}, // urxvt
|
|
|
|
|
+ "\x1b[5;7~": {Type: KeyCtrlPgUp, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[5^": {Type: KeyCtrlPgUp, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[6~": {Type: KeyPgDown},
|
|
|
|
|
+ "\x1b[6;3~": {Type: KeyPgDown, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[6~": {Type: KeyPgDown, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[6;5~": {Type: KeyCtrlPgDown},
|
|
|
|
|
+ "\x1b[6^": {Type: KeyCtrlPgDown}, // urxvt
|
|
|
|
|
+ "\x1b[6;7~": {Type: KeyCtrlPgDown, Alt: true},
|
|
|
|
|
+ "\x1b\x1b[6^": {Type: KeyCtrlPgDown, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[1~": {Type: KeyHome},
|
|
|
|
|
+ "\x1b[H": {Type: KeyHome}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;3H": {Type: KeyHome, Alt: true}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;5H": {Type: KeyCtrlHome}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;7H": {Type: KeyCtrlHome, Alt: true}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;2H": {Type: KeyShiftHome}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;4H": {Type: KeyShiftHome, Alt: true}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;6H": {Type: KeyCtrlShiftHome}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;8H": {Type: KeyCtrlShiftHome, Alt: true}, // xterm, lxterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[4~": {Type: KeyEnd},
|
|
|
|
|
+ "\x1b[F": {Type: KeyEnd}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;3F": {Type: KeyEnd, Alt: true}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;5F": {Type: KeyCtrlEnd}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;7F": {Type: KeyCtrlEnd, Alt: true}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;2F": {Type: KeyShiftEnd}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;4F": {Type: KeyShiftEnd, Alt: true}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;6F": {Type: KeyCtrlShiftEnd}, // xterm, lxterm
|
|
|
|
|
+ "\x1b[1;8F": {Type: KeyCtrlShiftEnd, Alt: true}, // xterm, lxterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[7~": {Type: KeyHome}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[7~": {Type: KeyHome, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[7^": {Type: KeyCtrlHome}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[7^": {Type: KeyCtrlHome, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[7$": {Type: KeyShiftHome}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[7$": {Type: KeyShiftHome, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[7@": {Type: KeyCtrlShiftHome}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[7@": {Type: KeyCtrlShiftHome, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[8~": {Type: KeyEnd}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[8~": {Type: KeyEnd, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[8^": {Type: KeyCtrlEnd}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[8^": {Type: KeyCtrlEnd, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[8$": {Type: KeyShiftEnd}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[8$": {Type: KeyShiftEnd, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b[8@": {Type: KeyCtrlShiftEnd}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[8@": {Type: KeyCtrlShiftEnd, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ // Function keys, Linux console
|
|
|
|
|
+ "\x1b[[A": {Type: KeyF1}, // linux console
|
|
|
|
|
+ "\x1b[[B": {Type: KeyF2}, // linux console
|
|
|
|
|
+ "\x1b[[C": {Type: KeyF3}, // linux console
|
|
|
|
|
+ "\x1b[[D": {Type: KeyF4}, // linux console
|
|
|
|
|
+ "\x1b[[E": {Type: KeyF5}, // linux console
|
|
|
|
|
+
|
|
|
|
|
+ // Function keys, X11
|
|
|
|
|
+ "\x1bOP": {Type: KeyF1}, // vt100, xterm
|
|
|
|
|
+ "\x1bOQ": {Type: KeyF2}, // vt100, xterm
|
|
|
|
|
+ "\x1bOR": {Type: KeyF3}, // vt100, xterm
|
|
|
|
|
+ "\x1bOS": {Type: KeyF4}, // vt100, xterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[1;3P": {Type: KeyF1, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[1;3Q": {Type: KeyF2, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[1;3R": {Type: KeyF3, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[1;3S": {Type: KeyF4, Alt: true}, // vt100, xterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[11~": {Type: KeyF1}, // urxvt
|
|
|
|
|
+ "\x1b[12~": {Type: KeyF2}, // urxvt
|
|
|
|
|
+ "\x1b[13~": {Type: KeyF3}, // urxvt
|
|
|
|
|
+ "\x1b[14~": {Type: KeyF4}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[11~": {Type: KeyF1, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[12~": {Type: KeyF2, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[13~": {Type: KeyF3, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[14~": {Type: KeyF4, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[15~": {Type: KeyF5}, // vt100, xterm, also urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[15;3~": {Type: KeyF5, Alt: true}, // vt100, xterm, also urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[15~": {Type: KeyF5, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[17~": {Type: KeyF6}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[18~": {Type: KeyF7}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[19~": {Type: KeyF8}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[20~": {Type: KeyF9}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[21~": {Type: KeyF10}, // vt100, xterm, also urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[17~": {Type: KeyF6, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[18~": {Type: KeyF7, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[19~": {Type: KeyF8, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[20~": {Type: KeyF9, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[21~": {Type: KeyF10, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[17;3~": {Type: KeyF6, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[18;3~": {Type: KeyF7, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[19;3~": {Type: KeyF8, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[20;3~": {Type: KeyF9, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[21;3~": {Type: KeyF10, Alt: true}, // vt100, xterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[23~": {Type: KeyF11}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[24~": {Type: KeyF12}, // vt100, xterm, also urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[23;3~": {Type: KeyF11, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[24;3~": {Type: KeyF12, Alt: true}, // vt100, xterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[23~": {Type: KeyF11, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[24~": {Type: KeyF12, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[1;2P": {Type: KeyF13},
|
|
|
|
|
+ "\x1b[1;2Q": {Type: KeyF14},
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[25~": {Type: KeyF13}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[26~": {Type: KeyF14}, // vt100, xterm, also urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[25;3~": {Type: KeyF13, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[26;3~": {Type: KeyF14, Alt: true}, // vt100, xterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[25~": {Type: KeyF13, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[26~": {Type: KeyF14, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[1;2R": {Type: KeyF15},
|
|
|
|
|
+ "\x1b[1;2S": {Type: KeyF16},
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[28~": {Type: KeyF15}, // vt100, xterm, also urxvt
|
|
|
|
|
+ "\x1b[29~": {Type: KeyF16}, // vt100, xterm, also urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[28;3~": {Type: KeyF15, Alt: true}, // vt100, xterm
|
|
|
|
|
+ "\x1b[29;3~": {Type: KeyF16, Alt: true}, // vt100, xterm
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[28~": {Type: KeyF15, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[29~": {Type: KeyF16, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[15;2~": {Type: KeyF17},
|
|
|
|
|
+ "\x1b[17;2~": {Type: KeyF18},
|
|
|
|
|
+ "\x1b[18;2~": {Type: KeyF19},
|
|
|
|
|
+ "\x1b[19;2~": {Type: KeyF20},
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b[31~": {Type: KeyF17},
|
|
|
|
|
+ "\x1b[32~": {Type: KeyF18},
|
|
|
|
|
+ "\x1b[33~": {Type: KeyF19},
|
|
|
|
|
+ "\x1b[34~": {Type: KeyF20},
|
|
|
|
|
+
|
|
|
|
|
+ "\x1b\x1b[31~": {Type: KeyF17, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[32~": {Type: KeyF18, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[33~": {Type: KeyF19, Alt: true}, // urxvt
|
|
|
|
|
+ "\x1b\x1b[34~": {Type: KeyF20, Alt: true}, // urxvt
|
|
|
|
|
+
|
|
|
|
|
+ // Powershell sequences.
|
|
|
|
|
+ "\x1bOA": {Type: KeyUp, Alt: false},
|
|
|
|
|
+ "\x1bOB": {Type: KeyDown, Alt: false},
|
|
|
|
|
+ "\x1bOC": {Type: KeyRight, Alt: false},
|
|
|
|
|
+ "\x1bOD": {Type: KeyLeft, Alt: false},
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// readInputs reads keypress and mouse inputs from a TTY and returns messages
|
|
|
|
|
+// containing information about the key or mouse events accordingly.
|
|
|
|
|
+func readInputs(input io.Reader) ([]Msg, error) {
|
|
|
|
|
+ var buf [256]byte
|
|
|
|
|
+
|
|
|
|
|
+ // Read and block
|
|
|
|
|
+ numBytes, err := input.Read(buf[:])
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+ b := buf[:numBytes]
|
|
|
|
|
+ b, err = localereader.UTF8(b)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if it's a mouse event. For now we're parsing X10-type mouse events
|
|
|
|
|
+ // only.
|
|
|
|
|
+ mouseEvent, err := parseX10MouseEvents(b)
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ var m []Msg
|
|
|
|
|
+ for _, v := range mouseEvent {
|
|
|
|
|
+ m = append(m, MouseMsg(v))
|
|
|
|
|
+ }
|
|
|
|
|
+ return m, nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var runeSets [][]rune
|
|
|
|
|
+ var runes []rune
|
|
|
|
|
+
|
|
|
|
|
+ // Translate input into runes. In most cases we'll receive exactly one
|
|
|
|
|
+ // rune, but there are cases, particularly when an input method editor is
|
|
|
|
|
+ // used, where we can receive multiple runes at once.
|
|
|
|
|
+ for i, w := 0, 0; i < len(b); i += w {
|
|
|
|
|
+ r, width := utf8.DecodeRune(b[i:])
|
|
|
|
|
+ if r == utf8.RuneError {
|
|
|
|
|
+ return nil, errors.New("could not decode rune")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if r == '\x1b' && len(runes) > 1 {
|
|
|
|
|
+ // a new key sequence has started
|
|
|
|
|
+ runeSets = append(runeSets, runes)
|
|
|
|
|
+ runes = []rune{}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ runes = append(runes, r)
|
|
|
|
|
+ w = width
|
|
|
|
|
+ }
|
|
|
|
|
+ // add the final set of runes we decoded
|
|
|
|
|
+ runeSets = append(runeSets, runes)
|
|
|
|
|
+
|
|
|
|
|
+ if len(runeSets) == 0 {
|
|
|
|
|
+ return nil, errors.New("received 0 runes from input")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var msgs []Msg
|
|
|
|
|
+ for _, runes := range runeSets {
|
|
|
|
|
+ // Is it a sequence, like an arrow key?
|
|
|
|
|
+ if k, ok := sequences[string(runes)]; ok {
|
|
|
|
|
+ msgs = append(msgs, KeyMsg(k))
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Is this an unrecognized CSI sequence? If so, ignore it.
|
|
|
|
|
+ if len(runes) > 2 && runes[0] == 0x1b && (runes[1] == '[' ||
|
|
|
|
|
+ (len(runes) > 3 && runes[1] == 0x1b && runes[2] == '[')) {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Is the alt key pressed? If so, the buffer will be prefixed with an
|
|
|
|
|
+ // escape.
|
|
|
|
|
+ alt := false
|
|
|
|
|
+ if len(runes) > 1 && runes[0] == 0x1b {
|
|
|
|
|
+ alt = true
|
|
|
|
|
+ runes = runes[1:]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, v := range runes {
|
|
|
|
|
+ // Is the first rune a control character?
|
|
|
|
|
+ r := KeyType(v)
|
|
|
|
|
+ if r <= keyUS || r == keyDEL {
|
|
|
|
|
+ msgs = append(msgs, KeyMsg(Key{Type: r, Alt: alt}))
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // If it's a space, override the type with KeySpace (but still include
|
|
|
|
|
+ // the rune).
|
|
|
|
|
+ if r == ' ' {
|
|
|
|
|
+ msgs = append(msgs, KeyMsg(Key{Type: KeySpace, Runes: []rune{v}, Alt: alt}))
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Welp, just regular, ol' runes.
|
|
|
|
|
+ msgs = append(msgs, KeyMsg(Key{Type: KeyRunes, Runes: []rune{v}, Alt: alt}))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return msgs, nil
|
|
|
|
|
+}
|