bubbletea/key.go

353 lines
9.4 KiB
Go
Raw Normal View History

package tea
2020-01-10 16:02:04 -05:00
import (
"errors"
2020-02-18 14:52:57 -05:00
"fmt"
2020-01-10 16:02:04 -05:00
"io"
"unicode/utf8"
)
2020-07-29 20:49:20 -04:00
// 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:
//
2020-10-30 23:49:01 -04:00
// // Switch on the string representation of the key (shorter)
2020-07-29 20:49:20 -04:00
// switch msg := msg.(type) {
// case KeyMsg:
2020-10-30 23:49:01 -04:00
// switch msg.String() {
// case "enter":
2020-07-30 11:29:20 -04:00
// fmt.Println("you pressed enter!")
2020-10-30 23:49:01 -04:00
// case "a":
// fmt.Println("you pressed a!")
2020-07-29 20:49:20 -04:00
// }
// }
//
2020-10-30 23:49:01 -04:00
// // Switch on the key type (more foolproof)
2020-07-29 20:49:20 -04:00
// switch msg := msg.(type) {
// case KeyMsg:
2020-10-30 23:49:01 -04:00
// switch msg.Type {
// case KeyEnter:
2020-07-30 11:29:20 -04:00
// fmt.Println("you pressed enter!")
2020-10-30 23:49:01 -04:00
// case KeyRunes:
// switch string(msg.Runes) {
// case "a":
// fmt.Println("you pressed a!")
// }
2020-07-29 20:49:20 -04:00
// }
// }
//
// 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
2020-01-10 16:02:04 -05:00
// String returns a friendly name for a key.
2020-07-29 20:49:20 -04:00
//
// k := KeyType{Type: KeyEnter}
// fmt.Println(k)
// // Output: enter
2020-02-19 18:35:34 -05:00
func (k *KeyMsg) String() (str string) {
if k.Alt {
str += "alt+"
}
if k.Type == KeyRunes {
str += string(k.Runes)
2020-02-19 18:35:34 -05:00
return str
} else if s, ok := keyNames[int(k.Type)]; ok {
2020-02-19 18:35:34 -05:00
str += s
return str
}
return ""
}
// Key contains information about a keypress.
type Key struct {
Type KeyType
Runes []rune
Alt bool
}
2020-07-29 20:49:20 -04:00
// 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
2020-07-29 20:49:20 -04:00
// 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 {
2020-07-29 20:49:20 -04:00
//
// fmt.Println(k.Runes)
2020-07-29 20:49:20 -04:00
// // Output: a
//
// fmt.Println(k.String())
// // Output: alt+a
//
// }
type KeyType int
// Control keys. I know we could do this with an iota, but the values are very
2020-01-26 15:39:54 -05:00
// specific, so we set the values explicitly to avoid any confusion.
//
// See also:
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
const (
2020-01-26 15:39:54 -05:00
keyNUL = 0 // null, \0
keySOH = 1 // start of heading
keySTX = 2 // start of text
keyETX = 3 // break, ctrl+c
2020-01-26 15:39:54 -05:00
keyEOT = 4 // end of transmission
keyENQ = 5 // enquiry
keyACK = 6 // acknowledge
keyBEL = 7 // bell, \a
keyBS = 8 // backspace
keyHT = 9 // horizontal tabulation, \t
keyLF = 10 // line feed, \n
2020-01-26 15:39:54 -05:00
keyVT = 11 // vertical tabulation \v
keyFF = 12 // form feed \f
keyCR = 13 // carriage return, \r
2020-01-26 15:39:54 -05:00
keySO = 14 // shift out
keySI = 15 // shift in
keyDLE = 16 // data link escape
keyDC1 = 17 // device control one
keyDC2 = 18 // device control two
keyDC3 = 19 // device control three
keyDC4 = 20 // device control four
keyNAK = 21 // negative acknowledge
keySYN = 22 // synchronous idle
keyETB = 23 // end of transmission block
keyCAN = 24 // cancel
keyEM = 25 // end of medium
keySUB = 26 // substitution
keyESC = 27 // escape, \e
keyFS = 28 // file separator
keyGS = 29 // group separator
keyRS = 30 // record separator
keyUS = 31 // unit separator
2020-01-26 15:39:54 -05:00
keySP = 32 // space
keyDEL = 127 // delete. on most systems this is mapped to backspace, I hear
)
// Control key aliases.
const (
KeyNull = keyNUL
KeyBreak = keyETX
KeyEnter = keyCR
KeyBackspace = keyDEL
2020-02-18 11:06:37 -05:00
KeyTab = keyHT
KeySpace = keySP
KeyEsc = keyESC
KeyEscape = keyESC
KeyCtrlAt = keyNUL // ctrl+@
KeyCtrlA = keySOH
KeyCtrlB = keySTX
KeyCtrlC = keyETX
KeyCtrlD = keyEOT
KeyCtrlE = keyENQ
KeyCtrlF = keyACK
KeyCtrlG = keyBEL
KeyCtrlH = keyBS
KeyCtrlI = keyHT
KeyCtrlJ = keyLF
KeyCtrlK = keyVT
KeyCtrlL = keyFF
KeyCtrlM = keyCR
KeyCtrlN = keySO
KeyCtrlO = keySI
KeyCtrlP = keyDLE
KeyCtrlQ = keyDC1
KeyCtrlR = keyDC2
KeyCtrlS = keyDC3
KeyCtrlT = keyDC4
KeyCtrlU = keyNAK
2020-07-21 15:50:07 -04:00
KeyCtrlV = keySYN
KeyCtrlW = keyETB
KeyCtrlX = keyCAN
KeyCtrlY = keyEM
KeyCtrlZ = keySUB
KeyCtrlOpenBracket = keyESC // ctrl+[
KeyCtrlBackslash = keyFS // ctrl+\
KeyCtrlCloseBracket = keyGS // ctrl+]
KeyCtrlCaret = keyRS // ctrl+^
KeyCtrlUnderscore = keyUS // ctrl+_
KeyCtrlQuestionMark = keyDEL // ctrl+?
)
// Other keys.
const (
KeyRunes = -(iota + 1)
KeyUp
KeyDown
KeyRight
KeyLeft
2020-02-18 14:52:57 -05:00
KeyShiftTab
KeyHome
KeyEnd
KeyPgUp
KeyPgDown
KeyDelete
)
// Mapping for control keys to friendly consts.
var keyNames = map[int]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",
2020-02-18 14:52:57 -05:00
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{
2020-02-19 18:35:34 -05:00
"\x1b[A": KeyUp,
"\x1b[B": KeyDown,
"\x1b[C": KeyRight,
"\x1b[D": KeyLeft,
2020-01-10 23:12:25 -05:00
}
// Mapping for hex codes to consts. Unclear why these won't register as
// sequences.
var hexes = map[string]Key{
2020-05-25 08:03:59 -04:00
"1b5b5a": {Type: KeyShiftTab},
"1b5b337e": {Type: KeyDelete},
2020-05-25 08:03:59 -04:00
"1b0d": {Type: KeyEnter, Alt: true},
"1b7f": {Type: KeyBackspace, Alt: true},
2020-05-25 08:03:59 -04:00
"1b5b48": {Type: KeyHome},
"1b5b377e": {Type: KeyHome}, // urxvt
"1b5b313b3348": {Type: KeyHome, Alt: true},
"1b1b5b377e": {Type: KeyHome, Alt: true}, // urxvt
2020-05-25 08:03:59 -04:00
"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},
}
2020-07-29 20:51:55 -04:00
// readInput reads keypress and mouse input from a TTY and returns a message
2020-08-22 06:34:03 -04:00
// containing information about the key or mouse event accordingly.
func readInput(input io.Reader) (Msg, error) {
2020-01-10 16:02:04 -05:00
var buf [256]byte
// Read and block
numBytes, err := input.Read(buf[:])
2020-01-10 16:02:04 -05:00
if err != nil {
2020-06-22 20:30:16 -04:00
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
2020-01-10 16:02:04 -05:00
}
// 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
2020-02-18 14:52:57 -05:00
}
// 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("receied 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
2020-01-10 16:02:04 -05:00
}
// Is the first rune a control character?
r := runes[0]
if numBytes == 1 && r <= keyUS || r == keyDEL {
return KeyMsg(Key{Type: KeyType(r)}), nil
}
// Welp, it's just a regular, ol' single rune
return KeyMsg(Key{Type: KeyRunes, Runes: runes}), nil
2020-01-10 16:02:04 -05:00
}