From 8d3fcbdf155f08acfbe81672e971d6ccfe12478a Mon Sep 17 00:00:00 2001 From: Caleb Foust Date: Sun, 1 Dec 2024 09:03:23 +0800 Subject: [PATCH] fix: alt+ support --- docs/src/keybindings.md | 16 ++------ pkg/cy/api/key.go | 5 ++- pkg/cy/api/key_test.janet | 4 ++ pkg/taro/key.go | 12 ++++++ pkg/taro/key_constants.go | 2 - pkg/taro/key_test.go | 78 ++++++++++++++++++++++++++++++++++----- 6 files changed, 90 insertions(+), 27 deletions(-) diff --git a/docs/src/keybindings.md b/docs/src/keybindings.md index 84e2221a..36c78c3f 100644 --- a/docs/src/keybindings.md +++ b/docs/src/keybindings.md @@ -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. @@ -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 alt - -You may prepend `alt+` to any [specifier](/preset-keys.md) to require the alt key to be held. For example, `ctrl+a` becomes `alt+ctrl+a`. - -alt can also be used to bind sequences like alt+m, 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. diff --git a/pkg/cy/api/key.go b/pkg/cy/api/key.go index 3c55f75c..27863aa0 100644 --- a/pkg/cy/api/key.go +++ b/pkg/cy/api/key.go @@ -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 { @@ -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) @@ -112,6 +111,8 @@ func (k *KeyModule) Bind( return err } + log.Info().Msgf("%+v", translated) + scope.Set( translated, bind.Action{ diff --git a/pkg/cy/api/key_test.janet b/pkg/cy/api/key_test.janet index b6a3952c..ebb1bfea 100644 --- a/pkg/cy/api/key_test.janet +++ b/pkg/cy/api/key_test.janet @@ -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 []))) diff --git a/pkg/taro/key.go b/pkg/taro/key.go index 50e97517..4157299b 100644 --- a/pkg/taro/key.go +++ b/pkg/taro/key.go @@ -162,6 +162,7 @@ func KeysToMsg(keys ...string) (msgs []KeyMsg) { msgs = append(msgs, KeyMsg{ Type: KeyRunes, Runes: []rune(key), + Alt: alt, }) } return @@ -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{ @@ -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 } diff --git a/pkg/taro/key_constants.go b/pkg/taro/key_constants.go index 707412e8..5d473d9e 100644 --- a/pkg/taro/key_constants.go +++ b/pkg/taro/key_constants.go @@ -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}, diff --git a/pkg/taro/key_test.go b/pkg/taro/key_test.go index 506635b3..064a1bb7 100644 --- a/pkg/taro/key_test.go +++ b/pkg/taro/key_test.go @@ -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) { @@ -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) {