Skip to content

Commit

Permalink
fix: alt+ support
Browse files Browse the repository at this point in the history
  • Loading branch information
cfoust committed Dec 1, 2024
1 parent c503cb0 commit 8d3fcbd
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 27 deletions.
16 changes: 3 additions & 13 deletions docs/src/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ Here are some valid key sequences:
["ctrl+a" "a"]
["ctrl+a" "ж"] # unicode is OK
[" " "l"]
# You can also prepend alt+, as expected
["alt+ctrl+a"]
["alt+o"]
```

It is important to note that `cy` **does not send partial sequences to the current pane**. In other words, defining a sequence that begins with `" "` means that you will no longer be able to type the space character.
Expand Down Expand Up @@ -132,16 +135,3 @@ It is sometimes convenient to change the activation sequence for many bindings a
```

Similarly, you may also delete keybindings with {{api key/unbind}}.

## About <kbd>alt</kbd>

You may prepend `alt+` to any [specifier](/preset-keys.md) to require the <kbd>alt</kbd> key to be held. For example, `ctrl+a` becomes `alt+ctrl+a`.

<kbd>alt</kbd> can also be used to bind sequences like <kbd>alt+m</kbd>, but
it's a little counterintuitive:

```janet
(key/bind :root ["alt" "m"] action/next-pane)
```

This is because your terminal emulator does not actually send a single, unambiguous byte sequence for `alt+` key combinations.
5 changes: 3 additions & 2 deletions pkg/cy/api/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/cfoust/cy/pkg/bind/trie"
"github.com/cfoust/cy/pkg/janet"
"github.com/cfoust/cy/pkg/mux/screen/tree"
"github.com/rs/zerolog/log"
)

type KeyModule struct {
Expand Down Expand Up @@ -38,8 +39,6 @@ func getKeySequence(value *janet.Value) (result []interface{}, err error) {
switch str {
case "ctrl+i":
str = "tab"
case "alt":
str = "esc"
}

result = append(result, str)
Expand Down Expand Up @@ -112,6 +111,8 @@ func (k *KeyModule) Bind(
return err
}

log.Info().Msgf("%+v", translated)

scope.Set(
translated,
bind.Action{
Expand Down
4 changes: 4 additions & 0 deletions pkg/cy/api/key_test.janet
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
(test "(key/current)"
# Should not error
(key/current))

(test "(key/bind)"
(key/bind :root ["alt+o"] (fn []))
(key/bind :root ["alt+ctrl+a"] (fn [])))
12 changes: 12 additions & 0 deletions pkg/taro/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func KeysToMsg(keys ...string) (msgs []KeyMsg) {
msgs = append(msgs, KeyMsg{
Type: KeyRunes,
Runes: []rune(key),
Alt: alt,
})
}
return
Expand All @@ -173,6 +174,10 @@ func KeysToBytes(keys ...KeyMsg) (data []byte, err error) {
case KeySpace:
data = append(data, []byte(" ")...)
case KeyRunes:
if key.Alt {
data = append(data, '\x1b')
}

data = append(data, []byte(string(key.Runes))...)
default:
if seq, ok := inverseSequences[keyLookup{
Expand All @@ -183,6 +188,13 @@ func KeysToBytes(keys ...KeyMsg) (data []byte, err error) {
continue
}

// ESC is also used to indicate an alt+ key sequence,
// so we can't just use a predefined sequence.
if key.Type == keyESC {
data = append(data, '\x1b')
continue
}

err = fmt.Errorf("unknown key type %+v", key)
return
}
Expand Down
2 changes: 0 additions & 2 deletions pkg/taro/key_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,6 @@ var keyRefs = map[string]KeyType{

// Sequence mappings.
var sequences = map[string]Key{
"\x1b": {Type: KeyEscape},

// Arrow keys
"\x1b[A": {Type: KeyUp},
"\x1b[B": {Type: KeyDown},
Expand Down
78 changes: 68 additions & 10 deletions pkg/taro/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,23 @@ func TestKeysToMsg(t *testing.T) {
Type: KeyCtrlA,
Alt: true,
},
}, KeysToMsg("test", "ctrl+a", "alt+ctrl+a"))
{
Type: KeyRunes,
Runes: []rune("o"),
Alt: true,
},
{
Type: KeyRunes,
Runes: []rune("й"),
Alt: true,
},
}, KeysToMsg(
"test",
"ctrl+a",
"alt+ctrl+a",
"alt+o",
"alt+й",
))
}

func TestKeysToBytes(t *testing.T) {
Expand All @@ -34,20 +50,62 @@ func TestKeysToBytes(t *testing.T) {
{
Type: keyETX,
},
{
Type: keyESC,
},
{
Type: keyESC,
Alt: true,
},
{
Type: KeyRunes,
Runes: []rune("a"),
Alt: true,
},
}

bytes, err := KeysToBytes(keys...)
assert.NoError(t, err)
for _, key := range keys {
bytes, err := KeysToBytes(key)
assert.NoError(t, err)

parsed := make([]KeyMsg, 0)
for i, w := 0, 0; i < len(bytes); i += w {
var msg Msg
w, msg = DetectOneMsg(bytes[i:])
if key, ok := msg.(KeyMsg); ok {
parsed = append(parsed, key)
parsed := make([]KeyMsg, 0)
for i, w := 0, 0; i < len(bytes); i += w {
var msg Msg
w, msg = DetectOneMsg(bytes[i:])
if key, ok := msg.(KeyMsg); ok {
parsed = append(parsed, key)
}
}
assert.Equal(t, []KeyMsg{key}, parsed)
}
}

func TestDetect(t *testing.T) {
type testCase struct {
input []byte
msg Msg
}
cases := []testCase{
{
input: []byte("\x1b"),
msg: KeyMsg{
Type: KeyEscape,
},
},
{
input: []byte("\x1bo"),
msg: KeyMsg{
Type: KeyRunes,
Runes: []rune("o"),
Alt: true,
},
},
}

for _, c := range cases {
_, msg := DetectOneMsg(c.input)
assert.Equal(t, c.msg, msg)
}
assert.Equal(t, keys, parsed)
}

func testMouseInput(t *testing.T, input string) {
Expand Down

0 comments on commit 8d3fcbd

Please sign in to comment.