forked from Mirrors/bubbletea
368 lines
10 KiB
Go
368 lines
10 KiB
Go
package tea
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// 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
|
|
keySP KeyType = 32 // space
|
|
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
|
|
KeySpace KeyType = keySP
|
|
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
|
|
KeyDelete
|
|
)
|
|
|
|
// Mapping for control keys to friendly consts.
|
|
var keyNames = map[KeyType]string{
|
|
keyNUL: "ctrl+@", // also ctrl+`
|
|
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+_",
|
|
keySP: "space",
|
|
keyDEL: "backspace",
|
|
|
|
KeyRunes: "runes",
|
|
KeyUp: "up",
|
|
KeyDown: "down",
|
|
KeyRight: "right",
|
|
KeyLeft: "left",
|
|
KeyShiftTab: "shift+tab",
|
|
KeyHome: "home",
|
|
KeyEnd: "end",
|
|
KeyPgUp: "pgup",
|
|
KeyPgDown: "pgdown",
|
|
}
|
|
|
|
// Mapping for sequences to consts.
|
|
var sequences = map[string]KeyType{
|
|
"\x1b[A": KeyUp,
|
|
"\x1b[B": KeyDown,
|
|
"\x1b[C": KeyRight,
|
|
"\x1b[D": KeyLeft,
|
|
}
|
|
|
|
// Mapping for hex codes to consts. Unclear why these won't register as
|
|
// sequences.
|
|
var hexes = map[string]Key{
|
|
"1b5b5a": {Type: KeyShiftTab},
|
|
"1b5b337e": {Type: KeyDelete},
|
|
"1b0d": {Type: KeyEnter, Alt: true},
|
|
"1b7f": {Type: KeyBackspace, Alt: true},
|
|
"1b5b48": {Type: KeyHome},
|
|
"1b5b377e": {Type: KeyHome}, // urxvt
|
|
"1b5b313b3348": {Type: KeyHome, Alt: true},
|
|
"1b1b5b377e": {Type: KeyHome, Alt: true}, // urxvt
|
|
"1b5b46": {Type: KeyEnd},
|
|
"1b5b387e": {Type: KeyEnd}, // urxvt
|
|
"1b5b313b3346": {Type: KeyEnd, Alt: true},
|
|
"1b1b5b387e": {Type: KeyEnd, Alt: true}, // urxvt
|
|
"1b5b357e": {Type: KeyPgUp},
|
|
"1b5b353b337e": {Type: KeyPgUp, Alt: true},
|
|
"1b1b5b357e": {Type: KeyPgUp, Alt: true}, // urxvt
|
|
"1b5b367e": {Type: KeyPgDown},
|
|
"1b5b363b337e": {Type: KeyPgDown, Alt: true},
|
|
"1b1b5b367e": {Type: KeyPgDown, Alt: true}, // urxvt
|
|
"1b5b313b3341": {Type: KeyUp, Alt: true},
|
|
"1b5b313b3342": {Type: KeyDown, Alt: true},
|
|
"1b5b313b3343": {Type: KeyRight, Alt: true},
|
|
"1b5b313b3344": {Type: KeyLeft, Alt: true},
|
|
|
|
// Powershell
|
|
"1b4f41": {Type: KeyUp, Alt: false},
|
|
"1b4f42": {Type: KeyDown, Alt: false},
|
|
"1b4f43": {Type: KeyRight, Alt: false},
|
|
"1b4f44": {Type: KeyLeft, Alt: false},
|
|
}
|
|
|
|
// readInput reads keypress and mouse input from a TTY and returns a message
|
|
// containing information about the key or mouse event accordingly.
|
|
func readInput(input io.Reader) (Msg, error) {
|
|
var buf [256]byte
|
|
|
|
// Read and block
|
|
numBytes, err := input.Read(buf[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// See if it's a mouse event. For now we're parsing X10-type mouse events
|
|
// only.
|
|
mouseEvent, err := parseX10MouseEvent(buf[:numBytes])
|
|
if err == nil {
|
|
return MouseMsg(mouseEvent), nil
|
|
}
|
|
|
|
// Is it a special sequence, like an arrow key?
|
|
if k, ok := sequences[string(buf[:numBytes])]; ok {
|
|
return KeyMsg(Key{Type: k}), nil
|
|
}
|
|
|
|
// Some of these need special handling
|
|
hex := fmt.Sprintf("%x", buf[:numBytes])
|
|
if k, ok := hexes[hex]; ok {
|
|
return KeyMsg(k), nil
|
|
}
|
|
|
|
// Is the alt key pressed? The buffer will be prefixed with an escape
|
|
// sequence if so.
|
|
if numBytes > 1 && buf[0] == 0x1b {
|
|
// Now remove the initial escape sequence and re-process to get the
|
|
// character being pressed in combination with alt.
|
|
c, _ := utf8.DecodeRune(buf[1:])
|
|
if c == utf8.RuneError {
|
|
return nil, errors.New("could not decode rune after removing initial escape")
|
|
}
|
|
return KeyMsg(Key{Alt: true, Type: KeyRunes, Runes: []rune{c}}), nil
|
|
}
|
|
|
|
var runes []rune
|
|
b := buf[:numBytes]
|
|
|
|
// 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")
|
|
}
|
|
runes = append(runes, r)
|
|
w = width
|
|
}
|
|
|
|
if len(runes) == 0 {
|
|
return nil, errors.New("received 0 runes from input")
|
|
} else if len(runes) > 1 {
|
|
// We received multiple runes, so we know this isn't a control
|
|
// character, sequence, and so on.
|
|
return KeyMsg(Key{Type: KeyRunes, Runes: runes}), nil
|
|
}
|
|
|
|
// Is the first rune a control character?
|
|
r := KeyType(runes[0])
|
|
if numBytes == 1 && r <= keyUS || r == keyDEL {
|
|
return KeyMsg(Key{Type: r}), nil
|
|
}
|
|
|
|
// Welp, it's just a regular, ol' single rune
|
|
return KeyMsg(Key{Type: KeyRunes, Runes: runes}), nil
|
|
}
|