Skip to content

Commit

Permalink
Extend support for alt key chords
Browse files Browse the repository at this point in the history
"alt-" with any case-sensitive character is allowed
  • Loading branch information
junegunn committed Dec 30, 2020
1 parent 0de7ab1 commit 7f8e0db
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 311 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ CHANGELOG
- Added `last` action to move the cursor to the last match
- The opposite action `top` is renamed to `first`, but `top` is still
recognized as a synonym for backward compatibility
- Extended support for alt key chords: alt with any case-sensitive single character
```sh
fzf --bind alt-,:first,alt-.:last
```

0.24.4
------
Expand Down
6 changes: 1 addition & 5 deletions man/man1/fzf.1
Original file line number Diff line number Diff line change
Expand Up @@ -666,9 +666,7 @@ e.g.
.br
\fIctrl-alt-[a-z]\fR
.br
\fIalt-[a-z]\fR
.br
\fIalt-[0-9]\fR
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
.br
\fIf[1-12]\fR
.br
Expand All @@ -692,8 +690,6 @@ e.g.
.br
\fIalt-bspace\fR (\fIalt-bs\fR)
.br
\fIalt-/\fR
.br
\fItab\fR
.br
\fIbtab\fR (\fIshift-tab\fR)
Expand Down
162 changes: 83 additions & 79 deletions src/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strconv"
"strings"
"unicode"
"unicode/utf8"

"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
Expand Down Expand Up @@ -211,8 +210,8 @@ type Options struct {
Exit0 bool
Filter *string
ToggleSort bool
Expect map[int]string
Keymap map[int][]action
Expect map[tui.Event]string
Keymap map[tui.Event][]action
Preview previewOpts
PrintQuery bool
ReadZero bool
Expand Down Expand Up @@ -272,8 +271,8 @@ func defaultOptions() *Options {
Exit0: false,
Filter: nil,
ToggleSort: false,
Expect: make(map[int]string),
Keymap: make(map[int][]action),
Expect: make(map[tui.Event]string),
Keymap: make(map[tui.Event][]action),
Preview: defaultPreviewOpts(""),
PrintQuery: false,
ReadZero: false,
Expand Down Expand Up @@ -445,7 +444,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderNone
}

func parseKeyChords(str string, message string) map[int]string {
func parseKeyChords(str string, message string) map[tui.Event]string {
if len(str) == 0 {
errorExit(message)
}
Expand All @@ -455,124 +454,129 @@ func parseKeyChords(str string, message string) map[int]string {
tokens = append(tokens, ",")
}

chords := make(map[int]string)
chords := make(map[tui.Event]string)
for _, key := range tokens {
if len(key) == 0 {
continue // ignore
}
lkey := strings.ToLower(key)
chord := 0
add := func(e tui.EventType) {
chords[e.AsEvent()] = key
}
switch lkey {
case "up":
chord = tui.Up
add(tui.Up)
case "down":
chord = tui.Down
add(tui.Down)
case "left":
chord = tui.Left
add(tui.Left)
case "right":
chord = tui.Right
add(tui.Right)
case "enter", "return":
chord = tui.CtrlM
add(tui.CtrlM)
case "space":
chord = tui.AltZ + int(' ')
chords[tui.Key(' ')] = key
case "bspace", "bs":
chord = tui.BSpace
add(tui.BSpace)
case "ctrl-space":
chord = tui.CtrlSpace
add(tui.CtrlSpace)
case "ctrl-^", "ctrl-6":
chord = tui.CtrlCaret
add(tui.CtrlCaret)
case "ctrl-/", "ctrl-_":
chord = tui.CtrlSlash
add(tui.CtrlSlash)
case "ctrl-\\":
chord = tui.CtrlBackSlash
add(tui.CtrlBackSlash)
case "ctrl-]":
chord = tui.CtrlRightBracket
add(tui.CtrlRightBracket)
case "change":
chord = tui.Change
add(tui.Change)
case "backward-eof":
chord = tui.BackwardEOF
add(tui.BackwardEOF)
case "alt-enter", "alt-return":
chord = tui.CtrlAltM
chords[tui.CtrlAltKey('m')] = key
case "alt-space":
chord = tui.AltSpace
case "alt-/":
chord = tui.AltSlash
chords[tui.AltKey(' ')] = key
case "alt-bs", "alt-bspace":
chord = tui.AltBS
add(tui.AltBS)
case "alt-up":
chord = tui.AltUp
add(tui.AltUp)
case "alt-down":
chord = tui.AltDown
add(tui.AltDown)
case "alt-left":
chord = tui.AltLeft
add(tui.AltLeft)
case "alt-right":
chord = tui.AltRight
add(tui.AltRight)
case "tab":
chord = tui.Tab
add(tui.Tab)
case "btab", "shift-tab":
chord = tui.BTab
add(tui.BTab)
case "esc":
chord = tui.ESC
add(tui.ESC)
case "del":
chord = tui.Del
add(tui.Del)
case "home":
chord = tui.Home
add(tui.Home)
case "end":
chord = tui.End
add(tui.End)
case "insert":
chord = tui.Insert
add(tui.Insert)
case "pgup", "page-up":
chord = tui.PgUp
add(tui.PgUp)
case "pgdn", "page-down":
chord = tui.PgDn
add(tui.PgDn)
case "alt-shift-up", "shift-alt-up":
chord = tui.AltSUp
add(tui.AltSUp)
case "alt-shift-down", "shift-alt-down":
chord = tui.AltSDown
add(tui.AltSDown)
case "alt-shift-left", "shift-alt-left":
chord = tui.AltSLeft
add(tui.AltSLeft)
case "alt-shift-right", "shift-alt-right":
chord = tui.AltSRight
add(tui.AltSRight)
case "shift-up":
chord = tui.SUp
add(tui.SUp)
case "shift-down":
chord = tui.SDown
add(tui.SDown)
case "shift-left":
chord = tui.SLeft
add(tui.SLeft)
case "shift-right":
chord = tui.SRight
add(tui.SRight)
case "left-click":
chord = tui.LeftClick
add(tui.LeftClick)
case "right-click":
chord = tui.RightClick
add(tui.RightClick)
case "double-click":
chord = tui.DoubleClick
add(tui.DoubleClick)
case "f10":
chord = tui.F10
add(tui.F10)
case "f11":
chord = tui.F11
add(tui.F11)
case "f12":
chord = tui.F12
add(tui.F12)
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
chords[tui.CtrlAltKey(rune(key[9]))] = key
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = tui.CtrlA + int(lkey[5]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
chord = tui.AltA + int(lkey[4]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
chord = tui.Alt0 + int(lkey[4]) - '0'
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
r := runes[4]
switch r {
case escapedColon:
r = ':'
case escapedComma:
r = ','
case escapedPlus:
r = '+'
}
chords[tui.AltKey(r)] = key
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
chord = tui.F1 + int(key[1]) - '1'
} else if utf8.RuneCountInString(key) == 1 {
chord = tui.AltZ + int([]rune(key)[0])
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if len(runes) == 1 {
chords[tui.Key(runes[0])] = key
} else {
errorExit("unsupported key: " + key)
}
}
if chord > 0 {
chords[chord] = key
}
}
return chords
}
Expand Down Expand Up @@ -720,11 +724,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {

var executeRegexp *regexp.Regexp

func firstKey(keymap map[int]string) int {
func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap {
return k
}
return 0
return tui.EventType(0).AsEvent()
}

const (
Expand All @@ -740,7 +744,7 @@ func init() {
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
}

func parseKeymap(keymap map[int][]action, str string) {
func parseKeymap(keymap map[tui.Event][]action, str string) {
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
symbol := ":"
if strings.HasPrefix(src, "+") {
Expand Down Expand Up @@ -776,13 +780,13 @@ func parseKeymap(keymap map[int][]action, str string) {
if len(pair) < 2 {
errorExit("bind action not specified: " + origPairStr)
}
var key int
var key tui.Event
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
key = ':' + tui.AltZ
key = tui.Key(':')
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
key = ',' + tui.AltZ
key = tui.Key(',')
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
key = '+' + tui.AltZ
key = tui.Key('+')
} else {
keys := parseKeyChords(pair[0], "key name required")
key = firstKey(keys)
Expand Down Expand Up @@ -981,7 +985,7 @@ func isExecuteAction(str string) actionType {
return actIgnore
}

func parseToggleSort(keymap map[int][]action, str string) {
func parseToggleSort(keymap map[tui.Event][]action, str string) {
keys := parseKeyChords(str, "key name required")
if len(keys) != 1 {
errorExit("multiple keys specified")
Expand Down Expand Up @@ -1188,7 +1192,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Expect[k] = v
}
case "--no-expect":
opts.Expect = make(map[int]string)
opts.Expect = make(map[tui.Event]string)
case "--no-phony":
opts.Phony = false
case "--phony":
Expand Down Expand Up @@ -1512,11 +1516,11 @@ func postProcessOptions(opts *Options) {
}
// Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil {
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
}
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
}
}

Expand Down
Loading

0 comments on commit 7f8e0db

Please sign in to comment.