Refactor the keybinding stuff + add backspace

This commit is contained in:
Christian Rocha 2020-01-17 20:46:34 -05:00
parent 051a370769
commit 208f421470
No known key found for this signature in database
GPG Key ID: D6CC7A16E5878018
4 changed files with 108 additions and 41 deletions

View File

@ -22,14 +22,14 @@ func main() {
} }
} }
func update(msg tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) { func update(message tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) {
m, _ := mdl.(model) m, _ := mdl.(model)
switch message := msg.(type) { switch msg := message.(type) {
case tea.KeyPressMsg: case tea.KeyMsg:
switch message { switch msg.String() {
case "ctrl+c": case "break":
fallthrough fallthrough
case "esc": case "esc":
fallthrough fallthrough

View File

@ -1,5 +1,7 @@
package main package main
// TODO: This code feels messy. Clean it up.
import ( import (
"fmt" "fmt"
"math" "math"
@ -62,8 +64,8 @@ func update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) {
func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) { func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyPressMsg: case tea.KeyMsg:
switch msg { switch msg.String() {
case "j": case "j":
fallthrough fallthrough
case "down": case "down":
@ -85,7 +87,7 @@ func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
fallthrough fallthrough
case "esc": case "esc":
fallthrough fallthrough
case "ctrl+c": case "break":
return m, tea.Quit return m, tea.Quit
} }
@ -102,13 +104,13 @@ func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
func updateChosen(msg tea.Msg, m Model) (tea.Model, tea.Cmd) { func updateChosen(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyPressMsg: case tea.KeyMsg:
switch msg { switch msg.String() {
case "q": case "q":
fallthrough fallthrough
case "esc": case "esc":
fallthrough fallthrough
case "ctrl+c": case "break":
return m, tea.Quit return m, tea.Quit
} }

123
key.go
View File

@ -7,61 +7,126 @@ import (
) )
// KeyPressMsg contains information about a keypress // KeyPressMsg contains information about a keypress
type KeyPressMsg string type KeyMsg Key
// Control keys // String returns a friendly name for a key
const ( func (k *KeyMsg) String() string {
keyETX = 3 // break, ctrl+c if k.Type == KeyRune {
keyLF = 9 // line-feed, \n return string(k.Rune)
keyCR = 13 // carriage return, \r } else if s, ok := keyNames[k.Type]; ok {
keyESC = 27 return s
keyUS = 31 // unit separator }
) return ""
var controlNames = map[int]string{
keyETX: "ctrl+c",
keyLF: "enter",
keyCR: "enter",
keyESC: "esc",
keyUS: "us",
} }
var sequenceNames = map[string]string{ // IsRune returns weather or not the key is a rune
"\x1b[A": "up", func (k *KeyMsg) IsRune() bool {
"\x1b[B": "down", if k.Type == KeyRune {
"\x1b[C": "right", return true
"\x1b[D": "left", }
return false
}
type Key struct {
Type KeyType
Rune rune
}
// KeyType indicates the key pressed
type KeyType int
// Possible keys
const (
KeyBreak KeyType = iota
KeyEnter
KeyEscape
KeyUp
KeyDown
KeyRight
KeyLeft
KeyUnitSeparator
KeyBackspace
KeyRune
)
// Friendly key names
var keyNames = map[KeyType]string{
KeyBreak: "break",
KeyEnter: "enter",
KeyEscape: "esc",
KeyUp: "up",
KeyDown: "down",
KeyRight: "right",
KeyLeft: "left",
KeyUnitSeparator: "us",
KeyBackspace: "backspace",
KeyRune: "rune",
}
// Control keys. I know we could do this with an iota, but the values are very
// specific, so we set the values explicitly to avoid any confusion
const (
keyETX = 3 // break, ctrl+c
keyLF = 9 // line-feed, \n
keyCR = 13 // carriage return, \r
keyESC = 27 // escape
keyUS = 31 // unit separator
keyDEL = 127 // delete. on most systems this is mapped to backspace, I hear
)
// Mapping for control keys to friendly consts
var controlKeys = map[int]KeyType{
keyETX: KeyBreak,
keyLF: KeyEnter,
keyCR: KeyEnter,
keyESC: KeyEscape,
keyUS: KeyUnitSeparator,
keyDEL: KeyBackspace,
}
// Mapping for sequences to consts
var sequences = map[string]KeyType{
"\x1b[A": KeyUp,
"\x1b[B": KeyDown,
"\x1b[C": KeyRight,
"\x1b[D": KeyLeft,
} }
// ReadKey reads keypress input from a TTY and returns a string representation // ReadKey reads keypress input from a TTY and returns a string representation
// of a key // of a key
func ReadKey(r io.Reader) (string, error) { func ReadKey(r io.Reader) (Key, error) {
var buf [256]byte var buf [256]byte
// Read and block // Read and block
n, err := r.Read(buf[:]) n, err := r.Read(buf[:])
if err != nil { if err != nil {
return "", err return Key{}, err
} }
// Get rune // Get rune
c, _ := utf8.DecodeRune(buf[:]) c, _ := utf8.DecodeRune(buf[:])
if c == utf8.RuneError { if c == utf8.RuneError {
return "", errors.New("no such rune") return Key{}, errors.New("no such rune")
} }
// Is it a control character? // Is it a control character?
if n == 1 && c <= keyUS || c == keyDEL {
if k, ok := controlKeys[n]; ok {
return Key{Type: k}, nil
}
}
if n == 1 && c <= keyUS { if n == 1 && c <= keyUS {
if s, ok := controlNames[int(c)]; ok { if k, ok := controlKeys[int(c)]; ok {
return s, nil return Key{Type: k}, nil
} }
} }
// Is it a special sequence, like an arrow key? // Is it a special sequence, like an arrow key?
if s, ok := sequenceNames[string(buf[:n])]; ok { if k, ok := sequences[string(buf[:n])]; ok {
return s, nil return Key{Type: k}, nil
} }
// Nope, just a regular, ol' rune // Nope, just a regular, ol' rune
return string(c), nil return Key{Type: KeyRune, Rune: c}, nil
} }

2
tea.go
View File

@ -95,7 +95,7 @@ func (p *Program) Start() error {
go func() { go func() {
for { for {
msg, _ := ReadKey(p.rw) msg, _ := ReadKey(p.rw)
msgs <- KeyPressMsg(msg) msgs <- KeyMsg(msg)
} }
}() }()