diff --git a/examples/fullscreen/main.go b/examples/fullscreen/main.go index df04efa..aa4e392 100644 --- a/examples/fullscreen/main.go +++ b/examples/fullscreen/main.go @@ -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) - switch message := msg.(type) { + switch msg := message.(type) { - case tea.KeyPressMsg: - switch message { - case "ctrl+c": + case tea.KeyMsg: + switch msg.String() { + case "break": fallthrough case "esc": fallthrough diff --git a/examples/views/main.go b/examples/views/main.go index d42ba88..2df8eef 100644 --- a/examples/views/main.go +++ b/examples/views/main.go @@ -1,5 +1,7 @@ package main +// TODO: This code feels messy. Clean it up. + import ( "fmt" "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) { switch msg := msg.(type) { - case tea.KeyPressMsg: - switch msg { + case tea.KeyMsg: + switch msg.String() { case "j": fallthrough case "down": @@ -85,7 +87,7 @@ func updateChoices(msg tea.Msg, m Model) (tea.Model, tea.Cmd) { fallthrough case "esc": fallthrough - case "ctrl+c": + case "break": 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) { switch msg := msg.(type) { - case tea.KeyPressMsg: - switch msg { + case tea.KeyMsg: + switch msg.String() { case "q": fallthrough case "esc": fallthrough - case "ctrl+c": + case "break": return m, tea.Quit } diff --git a/key.go b/key.go index 98ce18f..fa231ac 100644 --- a/key.go +++ b/key.go @@ -7,61 +7,126 @@ import ( ) // KeyPressMsg contains information about a keypress -type KeyPressMsg string +type KeyMsg Key -// Control keys -const ( - keyETX = 3 // break, ctrl+c - keyLF = 9 // line-feed, \n - keyCR = 13 // carriage return, \r - keyESC = 27 - keyUS = 31 // unit separator -) - -var controlNames = map[int]string{ - keyETX: "ctrl+c", - keyLF: "enter", - keyCR: "enter", - keyESC: "esc", - keyUS: "us", +// String returns a friendly name for a key +func (k *KeyMsg) String() string { + if k.Type == KeyRune { + return string(k.Rune) + } else if s, ok := keyNames[k.Type]; ok { + return s + } + return "" } -var sequenceNames = map[string]string{ - "\x1b[A": "up", - "\x1b[B": "down", - "\x1b[C": "right", - "\x1b[D": "left", +// IsRune returns weather or not the key is a rune +func (k *KeyMsg) IsRune() bool { + if k.Type == KeyRune { + return true + } + 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 // of a key -func ReadKey(r io.Reader) (string, error) { +func ReadKey(r io.Reader) (Key, error) { var buf [256]byte // Read and block n, err := r.Read(buf[:]) if err != nil { - return "", err + return Key{}, err } // Get rune c, _ := utf8.DecodeRune(buf[:]) if c == utf8.RuneError { - return "", errors.New("no such rune") + return Key{}, errors.New("no such rune") } // 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 s, ok := controlNames[int(c)]; ok { - return s, nil + if k, ok := controlKeys[int(c)]; ok { + return Key{Type: k}, nil } } // Is it a special sequence, like an arrow key? - if s, ok := sequenceNames[string(buf[:n])]; ok { - return s, nil + if k, ok := sequences[string(buf[:n])]; ok { + return Key{Type: k}, nil } // Nope, just a regular, ol' rune - return string(c), nil + return Key{Type: KeyRune, Rune: c}, nil } diff --git a/tea.go b/tea.go index 53fc49e..a31acff 100644 --- a/tea.go +++ b/tea.go @@ -95,7 +95,7 @@ func (p *Program) Start() error { go func() { for { msg, _ := ReadKey(p.rw) - msgs <- KeyPressMsg(msg) + msgs <- KeyMsg(msg) } }()