Skip to content

Commit

Permalink
fix: handle batched key msgs
Browse files Browse the repository at this point in the history
  • Loading branch information
muesli committed Jun 8, 2022
1 parent 958dc20 commit e6dabb3
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 68 deletions.
104 changes: 51 additions & 53 deletions key.go
Expand Up @@ -492,35 +492,7 @@ func readInputs(input io.Reader) ([]Msg, error) {
return m, nil
}

// Is it a sequence, like an arrow key?
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 runeSets [][]rune
var runes []rune
b := buf[:numBytes]

Expand All @@ -532,38 +504,64 @@ func readInputs(input io.Reader) ([]Msg, error) {
if r == utf8.RuneError {
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)
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")
} 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?
r := KeyType(runes[0])
if numBytes == 1 && r <= keyUS || r == keyDEL {
return []Msg{
KeyMsg(Key{Type: r}),
}, nil
}
var msgs []Msg
for _, runes := range runeSets {
// Is it a sequence, like an arrow key?
if k, ok := sequences[string(runes)]; ok {
msgs = append(msgs, KeyMsg(k))
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
}

// If it's a space, override the type with KeySpace (but still include the
// rune).
if runes[0] == ' ' {
return []Msg{
KeyMsg(Key{Type: KeySpace, Runes: runes}),
}, nil
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}}))
}
}

// Welp, it's just a regular, ol' single rune.
return []Msg{
KeyMsg(Key{Type: KeyRunes, Runes: runes}),
}, nil
return msgs, nil
}
120 changes: 105 additions & 15 deletions key_test.go
Expand Up @@ -48,29 +48,119 @@ func TestKeyTypeString(t *testing.T) {
}

func TestReadInput(t *testing.T) {
for out, in := range map[string][]byte{
"a": {'a'},
"ctrl+a": {byte(keySOH)},
"alt+a": {0x1b, 'a'},
"abcd": {'a', 'b', 'c', 'd'},
"up": []byte("\x1b[A"),
"wheel up": {'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
"shift+tab": {'\x1b', '[', 'Z'},
type test struct {
in []byte
out []Msg
}
for out, td := range map[string]test{
"a": {
[]byte{'a'},
[]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) {
msgs, err := readInputs(bytes.NewReader(in))
msgs, err := readInputs(bytes.NewReader(td.in))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(msgs) == 0 {
t.Fatalf("unexpected empty message list")
if len(msgs) != len(td.out) {
t.Fatalf("unexpected message list length")
}

if m, ok := msgs[0].(KeyMsg); ok && m.String() != out {
t.Fatalf(`expected a keymsg %q, got %q`, out, m)
if len(msgs) == 1 {
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])
}
}
})
}
Expand Down

0 comments on commit e6dabb3

Please sign in to comment.