mirror of
https://github.com/peco/peco.git
synced 2026-09-10 07:16:29 -04:00
commit
4ed16af465
15
README.md
15
README.md
|
|
@ -411,6 +411,21 @@ As a similar example, a common idiom in emacs is that `C-c C-c` means "take the
|
|||
Since v0.1.8, in addition to values below, you may put a `M-` prefix on any
|
||||
key item to use Alt/Option key as a mask.
|
||||
|
||||
You can also use `C-` and `S-` prefixes on navigation keys to bind Ctrl and Shift modified keys. Multiple modifiers can be combined. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Keymap": {
|
||||
"C-ArrowLeft": "peco.BackwardWord",
|
||||
"C-ArrowRight": "peco.ForwardWord",
|
||||
"S-ArrowUp": "peco.SelectUp",
|
||||
"C-M-Delete": "peco.DeleteForwardWord"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `C-` on single characters (e.g. `C-a`) refers to ASCII control codes as before. `C-` as a modifier applies to navigation keys such as `ArrowLeft`, `Home`, `Delete`, etc.
|
||||
|
||||
| Name | Notes |
|
||||
|-------------|-------|
|
||||
| C-a ... C-z | Control + whatever character |
|
||||
|
|
|
|||
|
|
@ -239,8 +239,8 @@ func KeyEventToString(key KeyType, ch rune, mod ModifierKey) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if mod == ModAlt {
|
||||
return "M-" + s, nil
|
||||
if m := mod.String(); m != "" {
|
||||
return m + "-" + s, nil
|
||||
}
|
||||
|
||||
return s, nil
|
||||
|
|
@ -248,25 +248,48 @@ func KeyEventToString(key KeyType, ch rune, mod ModifierKey) (string, error) {
|
|||
|
||||
func ToKey(key string) (k KeyType, modifier ModifierKey, ch rune, err error) {
|
||||
modifier = ModNone
|
||||
if strings.HasPrefix(key, "M-") {
|
||||
modifier = ModAlt
|
||||
key = key[2:]
|
||||
|
||||
// Try full string first. This handles legacy key names like "C-a",
|
||||
// "C-v", "Home", "ArrowLeft", etc. that are registered in stringToKey.
|
||||
if k, ok := stringToKey[key]; ok {
|
||||
return k, modifier, 0, nil
|
||||
}
|
||||
|
||||
// Parse modifier prefixes (C-, S-, M-) iteratively.
|
||||
// After each prefix is stripped, try the remainder as a key name.
|
||||
for {
|
||||
switch {
|
||||
case strings.HasPrefix(key, "C-"):
|
||||
modifier |= ModCtrl
|
||||
key = key[2:]
|
||||
case strings.HasPrefix(key, "S-"):
|
||||
modifier |= ModShift
|
||||
key = key[2:]
|
||||
case strings.HasPrefix(key, "M-"):
|
||||
modifier |= ModAlt
|
||||
key = key[2:]
|
||||
default:
|
||||
goto done
|
||||
}
|
||||
|
||||
// After stripping a prefix, try as a registered key name.
|
||||
// This handles e.g. "M-C-v" → strip M-, then "C-v" is found.
|
||||
if k, ok := stringToKey[key]; ok {
|
||||
return k, modifier, 0, nil
|
||||
}
|
||||
|
||||
// Single ASCII char after modifier(s) → treat as rune
|
||||
if len(key) == 1 {
|
||||
ch = rune(key[0])
|
||||
return
|
||||
return 0, modifier, rune(key[0]), nil
|
||||
}
|
||||
}
|
||||
|
||||
var ok bool
|
||||
k, ok = stringToKey[key]
|
||||
if !ok {
|
||||
// If this is a single rune, just allow it
|
||||
ch, _ = utf8.DecodeRuneInString(key)
|
||||
if ch != utf8.RuneError {
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.Errorf("no such key %s", key)
|
||||
done:
|
||||
// Try as a single rune (handles multi-byte chars like "せ")
|
||||
ch, _ = utf8.DecodeRuneInString(key)
|
||||
if ch != utf8.RuneError {
|
||||
return 0, modifier, ch, nil
|
||||
}
|
||||
return
|
||||
|
||||
return 0, modifier, 0, errors.Errorf("no such key %s", key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package keyseq
|
|||
import (
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKeymapStrToKeyValue(t *testing.T) {
|
||||
|
|
@ -152,3 +154,124 @@ func TestKeymapStrToKeyValueCh(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestKeymapStrToKeyValueWithCtrl(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key KeyType
|
||||
}{
|
||||
{"C-ArrowLeft", KeyArrowLeft},
|
||||
{"C-ArrowRight", KeyArrowRight},
|
||||
{"C-ArrowUp", KeyArrowUp},
|
||||
{"C-ArrowDown", KeyArrowDown},
|
||||
{"C-Home", KeyHome},
|
||||
{"C-End", KeyEnd},
|
||||
{"C-Delete", KeyDelete},
|
||||
{"C-Insert", KeyInsert},
|
||||
{"C-Pgup", KeyPgup},
|
||||
{"C-Pgdn", KeyPgdn},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
k, modifier, ch, err := ToKey(tc.name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.key, k)
|
||||
require.Equal(t, ModCtrl, modifier)
|
||||
require.Equal(t, rune(0), ch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeymapStrToKeyValueWithShift(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key KeyType
|
||||
}{
|
||||
{"S-ArrowUp", KeyArrowUp},
|
||||
{"S-ArrowDown", KeyArrowDown},
|
||||
{"S-ArrowLeft", KeyArrowLeft},
|
||||
{"S-ArrowRight", KeyArrowRight},
|
||||
{"S-Home", KeyHome},
|
||||
{"S-End", KeyEnd},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
k, modifier, ch, err := ToKey(tc.name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.key, k)
|
||||
require.Equal(t, ModShift, modifier)
|
||||
require.Equal(t, rune(0), ch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeymapStrToKeyValueWithCombinedModifiers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key KeyType
|
||||
modifier ModifierKey
|
||||
}{
|
||||
{"C-M-ArrowLeft", KeyArrowLeft, ModCtrl | ModAlt},
|
||||
{"M-C-ArrowLeft", KeyArrowLeft, ModCtrl | ModAlt},
|
||||
{"C-S-Delete", KeyDelete, ModCtrl | ModShift},
|
||||
{"C-S-M-Home", KeyHome, ModCtrl | ModShift | ModAlt},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
k, modifier, ch, err := ToKey(tc.name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.key, k)
|
||||
require.Equal(t, tc.modifier, modifier)
|
||||
require.Equal(t, rune(0), ch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifierKeyString(t *testing.T) {
|
||||
tests := []struct {
|
||||
mod ModifierKey
|
||||
expected string
|
||||
}{
|
||||
{ModNone, ""},
|
||||
{ModAlt, "M"},
|
||||
{ModCtrl, "C"},
|
||||
{ModShift, "S"},
|
||||
{ModCtrl | ModAlt, "C-M"},
|
||||
{ModCtrl | ModShift, "C-S"},
|
||||
{ModShift | ModAlt, "S-M"},
|
||||
{ModCtrl | ModShift | ModAlt, "C-S-M"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.expected, func(t *testing.T) {
|
||||
require.Equal(t, tc.expected, tc.mod.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyEventToStringWithModifiers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key KeyType
|
||||
ch rune
|
||||
mod ModifierKey
|
||||
expected string
|
||||
}{
|
||||
{"Ctrl+Left", KeyArrowLeft, 0, ModCtrl, "C-<"},
|
||||
{"Shift+Right", KeyArrowRight, 0, ModShift, "S->"},
|
||||
{"Ctrl+Alt+Delete", KeyDelete, 0, ModCtrl | ModAlt, "C-M-Delete"},
|
||||
{"Alt+char", 0, 'v', ModAlt, "M-v"},
|
||||
{"no modifier", KeyHome, 0, ModNone, "Home"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, err := KeyEventToString(tc.key, tc.ch, tc.mod)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ var ErrNoMatch = errors.New("could not match key to any action")
|
|||
type ModifierKey int
|
||||
|
||||
const (
|
||||
ModNone ModifierKey = iota
|
||||
ModAlt
|
||||
ModMax
|
||||
ModNone ModifierKey = 0
|
||||
ModAlt ModifierKey = 1 << 0 // 0x01
|
||||
ModCtrl ModifierKey = 1 << 1 // 0x02
|
||||
ModShift ModifierKey = 1 << 2 // 0x04
|
||||
)
|
||||
|
||||
// Key is data in one trie node in the KeySequence
|
||||
|
|
@ -35,12 +36,17 @@ func (kl KeyList) String() string {
|
|||
}
|
||||
|
||||
func (m ModifierKey) String() string {
|
||||
switch m {
|
||||
case ModAlt:
|
||||
return "M"
|
||||
default:
|
||||
return ""
|
||||
var parts []string
|
||||
if m&ModCtrl != 0 {
|
||||
parts = append(parts, "C")
|
||||
}
|
||||
if m&ModShift != 0 {
|
||||
parts = append(parts, "S")
|
||||
}
|
||||
if m&ModAlt != 0 {
|
||||
parts = append(parts, "M")
|
||||
}
|
||||
return strings.Join(parts, "-")
|
||||
}
|
||||
|
||||
func (k Key) String() string {
|
||||
|
|
|
|||
|
|
@ -57,8 +57,14 @@ func tcellEventToEvent(tev tcell.Event) Event {
|
|||
switch ev := tev.(type) {
|
||||
case *tcell.EventKey:
|
||||
var mod keyseq.ModifierKey
|
||||
if ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
mod |= keyseq.ModCtrl
|
||||
}
|
||||
if ev.Modifiers()&tcell.ModShift != 0 {
|
||||
mod |= keyseq.ModShift
|
||||
}
|
||||
if ev.Modifiers()&tcell.ModAlt != 0 {
|
||||
mod = keyseq.ModAlt
|
||||
mod |= keyseq.ModAlt
|
||||
}
|
||||
|
||||
key := ev.Key()
|
||||
|
|
|
|||
Loading…
Reference in a new issue