Skip to content

Commit

Permalink
fix: use KeyRunes to indicate text input
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Aug 14, 2024
1 parent 3075646 commit f2bdd36
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 34 deletions.
45 changes: 42 additions & 3 deletions key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ package tea
// KeySym is a keyboard symbol.
type KeySym int

// Key Symbol constants.
// Special key symbols.
const (
KeyNone KeySym = iota
// KeyRunes indicates that the key represents rune(s), like 'a', 'b', 'c',
// and so on.
KeyRunes KeySym = iota - 1

// KeyNone indicates that this key is not a special key. Combined with
// `len(k.Runes) > 0`, this indicates that the key represents rune(s) with
// modifiers like [ModCtrl], [ModAlt], [ModShift], and so on.
KeyNone

// Special names in C0

Check failure on line 17 in key.go

View workflow job for this annotation

GitHub Actions / lint-soft

Comment should end in a period (godot)

Expand Down Expand Up @@ -177,7 +184,39 @@ const (
KeyIsoLevel5Shift
)

// Key represents a key event.
// Key contains information about a key or release. Keys are always sent to the
// program's update function. There are a couple general patterns you could use
// to check for key presses or releases:
//
// // Switch on the string representation of the key (shorter)
// switch msg := msg.(type) {
// case KeyPressMsg:
// switch msg.String() {
// case "enter":
// fmt.Println("you pressed enter!")
// case "a":
// fmt.Println("you pressed a!")
// }
// }
//
// // Switch on the key type (more foolproof)
// switch msg := msg.(type) {
// case KeyReleaseMsg:
// switch msg.Sym {
// case KeyEnter:
// fmt.Println("you pressed enter!")
// case KeyRunes:
// switch string(msg.Runes) {
// case "a":
// fmt.Println("you pressed a!")
// }
// }
// }
//
// 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 Key struct {
// Sym is a special key, like enter, tab, backspace, and so on.
Sym KeySym
Expand Down
5 changes: 1 addition & 4 deletions key_deprecated.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type KeyType = KeySym

// Control key aliases.
const (
KeyNull KeyType = -iota - 1
KeyNull KeyType = -iota - 10
KeyBreak

KeyCtrlAt // ctrl+@
Expand Down Expand Up @@ -159,9 +159,6 @@ const (

// Deprecated: Use KeyEscape instead.
KeyEsc = KeyEscape

// Deprecated: Use KeyNone instead.
KeyRunes = KeyNone
)

// Mappings for control keys and other special keys to friendly consts.
Expand Down
38 changes: 19 additions & 19 deletions key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func TestParseSequence(t *testing.T) {
seqTest{
[]byte{'a'},
[]Msg{
KeyPressMsg{Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
},
},
seqTest{
Expand All @@ -156,16 +156,16 @@ func TestParseSequence(t *testing.T) {
seqTest{
[]byte{'a', 'a', 'a'},
[]Msg{
KeyPressMsg{Runes: []rune{'a'}},
KeyPressMsg{Runes: []rune{'a'}},
KeyPressMsg{Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
},
},
// Multi-byte rune.
seqTest{
[]byte("☃"),
[]Msg{
KeyPressMsg{Runes: []rune{'☃'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'☃'}},
},
},
seqTest{
Expand Down Expand Up @@ -249,7 +249,7 @@ func TestParseSequence(t *testing.T) {
func TestReadLongInput(t *testing.T) {
expect := make([]Msg, 1000)
for i := 0; i < 1000; i++ {
expect[i] = KeyPressMsg{Runes: []rune{'a'}}
expect[i] = KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}}
}
input := strings.Repeat("a", 1000)
drv, err := newDriver(strings.NewReader(input), "dumb", 0)
Expand Down Expand Up @@ -285,7 +285,7 @@ func TestReadInput(t *testing.T) {
"a",
[]byte{'a'},
[]Msg{
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
},
},
{
Expand All @@ -299,17 +299,17 @@ func TestReadInput(t *testing.T) {
"a alt+a",
[]byte{'a', '\x1b', 'a'},
[]Msg{
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}, Mod: ModAlt},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
KeyPressMsg{Runes: []rune{'a'}, Mod: ModAlt},
},
},
{
"a alt+a a",
[]byte{'a', '\x1b', 'a', 'a'},
[]Msg{
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}, Mod: ModAlt},
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
KeyPressMsg{Runes: []rune{'a'}, Mod: ModAlt},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
},
},
{
Expand Down Expand Up @@ -338,10 +338,10 @@ func TestReadInput(t *testing.T) {
"a b c d",
[]byte{'a', 'b', 'c', 'd'},
[]Msg{
KeyPressMsg{Runes: []rune{'a'}},
KeyPressMsg{Runes: []rune{'b'}},
KeyPressMsg{Runes: []rune{'c'}},
KeyPressMsg{Runes: []rune{'d'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'b'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'c'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'d'}},
},
},
{
Expand Down Expand Up @@ -470,7 +470,7 @@ func TestReadInput(t *testing.T) {
PasteStartMsg{},
PasteMsg("a b"),
PasteEndMsg{},
KeyPressMsg{Sym: KeyNone, Runes: []rune{'o'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'o'}},
},
},
{
Expand All @@ -497,10 +497,10 @@ func TestReadInput(t *testing.T) {
"a ?0xfe? b",
[]byte{'a', '\xfe', ' ', 'b'},
[]Msg{
KeyPressMsg{Sym: KeyNone, Runes: []rune{'a'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'a'}},
UnknownMsg(rune(0xfe)),
KeyPressMsg{Sym: KeySpace, Runes: []rune{' '}},
KeyPressMsg{Sym: KeyNone, Runes: []rune{'b'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'b'}},
},
},
}
Expand Down
4 changes: 4 additions & 0 deletions kitty.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg {
r = utf8.RuneError
}

key.Sym = KeyRunes
key.Runes = []rune{r}

// alternate key reporting
Expand Down Expand Up @@ -257,6 +258,9 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg {
mod := params[0]
if mod > 1 {
key.Mod = fromKittyMod(mod - 1)
if key.Sym == KeyRunes {
key.Sym = KeyNone
}
}
if len(params) > 1 {
switch params[1] {
Expand Down
9 changes: 6 additions & 3 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ func parseSequence(buf []byte) (n int, msg Msg) {
return parseApc(buf)
default:
n, e := parseSequence(buf[1:])
if k, ok := e.(KeyPressMsg); ok && !k.Mod.HasAlt() {
if k, ok := e.(KeyPressMsg); ok {
k.Mod |= ModAlt
if k.Sym == KeyRunes {
k.Sym = KeyNone
}
return n + 1, k
}

Expand Down Expand Up @@ -759,15 +762,15 @@ func parseUtf8(b []byte) (int, Msg) {
return 1, parseControl(c)
} else if c > ansi.US && c < ansi.DEL {
// ASCII printable characters
return 1, KeyPressMsg{Runes: []rune{rune(c)}}
return 1, KeyPressMsg{Sym: KeyRunes, Runes: []rune{rune(c)}}
}

if r, _ := utf8.DecodeRune(b); r == utf8.RuneError {
return 1, UnknownMsg(b[0])
}

cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1)
return len(cluster), KeyPressMsg{Runes: []rune(string(cluster))}
return len(cluster), KeyPressMsg{Sym: KeyRunes, Runes: []rune(string(cluster))}
}

func parseControl(b byte) Msg {
Expand Down
10 changes: 5 additions & 5 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ func TestParseSequence_Events(t *testing.T) {
input := []byte("\x1b\x1b[Ztest\x00\x1b]10;rgb:1234/1234/1234\x07\x1b[27;2;27~\x1b[?1049;2$y")
want := []Msg{
KeyPressMsg{Sym: KeyTab, Mod: ModShift | ModAlt},
KeyPressMsg{Runes: []rune{'t'}},
KeyPressMsg{Runes: []rune{'e'}},
KeyPressMsg{Runes: []rune{'s'}},
KeyPressMsg{Runes: []rune{'t'}},
KeyPressMsg{Runes: []rune{' '}, Sym: KeySpace, Mod: ModCtrl},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'t'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'e'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'s'}},
KeyPressMsg{Sym: KeyRunes, Runes: []rune{'t'}},
KeyPressMsg{Sym: KeySpace, Runes: []rune{' '}, Mod: ModCtrl},
ForegroundColorMsg{color.RGBA{R: 0x12, G: 0x12, B: 0x12, A: 0xff}},
KeyPressMsg{Sym: KeyEscape, Mod: ModShift},
ReportModeMsg{Mode: 1049, Value: 2},
Expand Down

0 comments on commit f2bdd36

Please sign in to comment.