forked from Mirrors/bubbletea
fix: handle batched key msgs
This commit is contained in:
parent
958dc20024
commit
e6dabb3c36
106
key.go
106
key.go
|
@ -492,35 +492,7 @@ func readInputs(input io.Reader) ([]Msg, error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is it a sequence, like an arrow key?
|
var runeSets [][]rune
|
||||||
if k, ok := sequences[string(buf[:numBytes])]; ok {
|
|
||||||
return []Msg{
|
|
||||||
KeyMsg(k),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some of these need special handling.
|
|
||||||
hex := fmt.Sprintf("%x", buf[:numBytes])
|
|
||||||
if k, ok := hexes[hex]; ok {
|
|
||||||
return []Msg{
|
|
||||||
KeyMsg(k),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the alt key pressed? If so, the buffer will be prefixed with an
|
|
||||||
// escape.
|
|
||||||
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 []Msg{
|
|
||||||
KeyMsg(Key{Alt: true, Type: KeyRunes, Runes: []rune{c}}),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var runes []rune
|
var runes []rune
|
||||||
b := buf[:numBytes]
|
b := buf[:numBytes]
|
||||||
|
|
||||||
|
@ -532,38 +504,64 @@ func readInputs(input io.Reader) ([]Msg, error) {
|
||||||
if r == utf8.RuneError {
|
if r == utf8.RuneError {
|
||||||
return nil, errors.New("could not decode rune")
|
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)
|
runes = append(runes, r)
|
||||||
w = width
|
w = width
|
||||||
}
|
}
|
||||||
|
// add the final set of runes we decoded
|
||||||
|
runeSets = append(runeSets, runes)
|
||||||
|
|
||||||
if len(runes) == 0 {
|
if len(runeSets) == 0 {
|
||||||
return nil, errors.New("received 0 runes from input")
|
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 []Msg{
|
|
||||||
KeyMsg(Key{Type: KeyRunes, Runes: runes}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the first rune a control character?
|
var msgs []Msg
|
||||||
r := KeyType(runes[0])
|
for _, runes := range runeSets {
|
||||||
if numBytes == 1 && r <= keyUS || r == keyDEL {
|
// Is it a sequence, like an arrow key?
|
||||||
return []Msg{
|
if k, ok := sequences[string(runes)]; ok {
|
||||||
KeyMsg(Key{Type: r}),
|
msgs = append(msgs, KeyMsg(k))
|
||||||
}, nil
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some of these need special handling.
|
||||||
|
hex := fmt.Sprintf("%x", runes)
|
||||||
|
if k, ok := hexes[hex]; ok {
|
||||||
|
msgs = append(msgs, KeyMsg(k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the alt key pressed? If so, the buffer will be prefixed with an
|
||||||
|
// escape.
|
||||||
|
if len(runes) > 1 && runes[0] == 0x1b {
|
||||||
|
msgs = append(msgs, KeyMsg(Key{Alt: true, Type: KeyRunes, Runes: runes[1:]}))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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}))
|
||||||
|
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}}))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Welp, just regular, ol' runes.
|
||||||
|
msgs = append(msgs, KeyMsg(Key{Type: KeyRunes, Runes: []rune{v}}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a space, override the type with KeySpace (but still include the
|
return msgs, nil
|
||||||
// rune).
|
|
||||||
if runes[0] == ' ' {
|
|
||||||
return []Msg{
|
|
||||||
KeyMsg(Key{Type: KeySpace, Runes: runes}),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Welp, it's just a regular, ol' single rune.
|
|
||||||
return []Msg{
|
|
||||||
KeyMsg(Key{Type: KeyRunes, Runes: runes}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
120
key_test.go
120
key_test.go
|
@ -48,29 +48,119 @@ func TestKeyTypeString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadInput(t *testing.T) {
|
func TestReadInput(t *testing.T) {
|
||||||
for out, in := range map[string][]byte{
|
type test struct {
|
||||||
"a": {'a'},
|
in []byte
|
||||||
"ctrl+a": {byte(keySOH)},
|
out []Msg
|
||||||
"alt+a": {0x1b, 'a'},
|
}
|
||||||
"abcd": {'a', 'b', 'c', 'd'},
|
for out, td := range map[string]test{
|
||||||
"up": []byte("\x1b[A"),
|
"a": {
|
||||||
"wheel up": {'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
|
[]byte{'a'},
|
||||||
"shift+tab": {'\x1b', '[', 'Z'},
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyRunes,
|
||||||
|
Runes: []rune{'a'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
" ": {
|
||||||
|
[]byte{' '},
|
||||||
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeySpace,
|
||||||
|
Runes: []rune{' '},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ctrl+a": {
|
||||||
|
[]byte{byte(keySOH)},
|
||||||
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyCtrlA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"alt+a": {
|
||||||
|
[]byte{byte(0x1b), 'a'},
|
||||||
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyRunes,
|
||||||
|
Alt: true,
|
||||||
|
Runes: []rune{'a'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"abcd": {
|
||||||
|
[]byte{'a', 'b', 'c', 'd'},
|
||||||
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyRunes,
|
||||||
|
Runes: []rune{'a'},
|
||||||
|
},
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyRunes,
|
||||||
|
Runes: []rune{'b'},
|
||||||
|
},
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyRunes,
|
||||||
|
Runes: []rune{'c'},
|
||||||
|
},
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyRunes,
|
||||||
|
Runes: []rune{'d'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
[]byte("\x1b[A"),
|
||||||
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyUp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"wheel up": {
|
||||||
|
[]byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
|
||||||
|
[]Msg{
|
||||||
|
MouseMsg{
|
||||||
|
Type: MouseWheelUp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"shift+tab": {
|
||||||
|
[]byte{'\x1b', '[', 'Z'},
|
||||||
|
[]Msg{
|
||||||
|
KeyMsg{
|
||||||
|
Type: KeyShiftTab,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(out, func(t *testing.T) {
|
t.Run(out, func(t *testing.T) {
|
||||||
msgs, err := readInputs(bytes.NewReader(in))
|
msgs, err := readInputs(bytes.NewReader(td.in))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if len(msgs) == 0 {
|
if len(msgs) != len(td.out) {
|
||||||
t.Fatalf("unexpected empty message list")
|
t.Fatalf("unexpected message list length")
|
||||||
}
|
}
|
||||||
|
|
||||||
if m, ok := msgs[0].(KeyMsg); ok && m.String() != out {
|
if len(msgs) == 1 {
|
||||||
t.Fatalf(`expected a keymsg %q, got %q`, out, m)
|
if m, ok := msgs[0].(KeyMsg); ok && m.String() != out {
|
||||||
|
t.Fatalf(`expected a keymsg %q, got %q`, out, m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if m, ok := msgs[0].(MouseMsg); ok && mouseEventTypes[m.Type] != out {
|
|
||||||
t.Fatalf(`expected a mousemsg %q, got %q`, out, mouseEventTypes[m.Type])
|
for i, v := range msgs {
|
||||||
|
if m, ok := v.(KeyMsg); ok &&
|
||||||
|
m.String() != td.out[i].(KeyMsg).String() {
|
||||||
|
t.Fatalf(`expected a keymsg %q, got %q`, td.out[i].(KeyMsg), m)
|
||||||
|
}
|
||||||
|
if m, ok := v.(MouseMsg); ok &&
|
||||||
|
(mouseEventTypes[m.Type] != out || m.Type != td.out[i].(MouseMsg).Type) {
|
||||||
|
t.Fatalf(`expected a mousemsg %q, got %q`,
|
||||||
|
out,
|
||||||
|
mouseEventTypes[td.out[i].(MouseMsg).Type])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue