Add 'trigger(KEY_OR_EVENT[,...])' action

This commit is contained in:
Junegunn Choi
2025-07-23 19:39:01 +09:00
parent 5818b58350
commit 4efcc344c3
7 changed files with 145 additions and 88 deletions

View File

@@ -17,6 +17,22 @@ CHANGELOG
echo "execute-silent(echo -n \{} | pbcopy)+bell"
'
```
- Added `trigger(...)` action that triggers events bound to another key or event.
```sh
# You can click on each key name to trigger the actions bound to that key
fzf --footer 'Ctrl-E: Edit / Ctrl-V: View / Ctrl-Y: Copy to clipboard' \
--with-shell 'bash -c' \
--bind 'ctrl-e:execute:vim {}' \
--bind 'ctrl-v:execute:view {}' \
--bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell' \
--bind 'click-footer:transform:
[[ $FZF_CLICK_FOOTER_WORD =~ Ctrl ]] && echo "trigger(${FZF_CLICK_FOOTER_WORD%:})"
'
```
- You can specify a series of keys and events
```sh
fzf --bind 'a:up,b:trigger(a,a,a)'
```
- Added support for `{*n}` and `{*nf}` placeholder.
- `{*n}` evaluates to the zero-based ordinal index of all matched items.
- `{*nf}` evaluates to the temporary file containing that.

View File

@@ -1817,6 +1817,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
\fBtransform\-query(...)\fR (transform query string using an external command)
\fBtransform\-search(...)\fR (trigger fzf search with the output of an external command)
\fBtrigger(...)\fR (trigger actions bound to a comma-separated list of keys and events)
\fBunbind(...)\fR (unbind bindings)
\fBunix\-line\-discard\fR \fIctrl\-u\fR
\fBunix\-word\-rubout\fR \fIctrl\-w\fR

View File

@@ -113,65 +113,66 @@ func _() {
_ = x[actTransformPrompt-102]
_ = x[actTransformQuery-103]
_ = x[actTransformSearch-104]
_ = x[actBgTransform-105]
_ = x[actBgTransformBorderLabel-106]
_ = x[actBgTransformGhost-107]
_ = x[actBgTransformHeader-108]
_ = x[actBgTransformFooter-109]
_ = x[actBgTransformHeaderLabel-110]
_ = x[actBgTransformFooterLabel-111]
_ = x[actBgTransformInputLabel-112]
_ = x[actBgTransformListLabel-113]
_ = x[actBgTransformNth-114]
_ = x[actBgTransformPointer-115]
_ = x[actBgTransformPreviewLabel-116]
_ = x[actBgTransformPrompt-117]
_ = x[actBgTransformQuery-118]
_ = x[actBgTransformSearch-119]
_ = x[actBgCancel-120]
_ = x[actSearch-121]
_ = x[actPreview-122]
_ = x[actPreviewTop-123]
_ = x[actPreviewBottom-124]
_ = x[actPreviewUp-125]
_ = x[actPreviewDown-126]
_ = x[actPreviewPageUp-127]
_ = x[actPreviewPageDown-128]
_ = x[actPreviewHalfPageUp-129]
_ = x[actPreviewHalfPageDown-130]
_ = x[actPrevHistory-131]
_ = x[actPrevSelected-132]
_ = x[actPrint-133]
_ = x[actPut-134]
_ = x[actNextHistory-135]
_ = x[actNextSelected-136]
_ = x[actExecute-137]
_ = x[actExecuteSilent-138]
_ = x[actExecuteMulti-139]
_ = x[actSigStop-140]
_ = x[actFirst-141]
_ = x[actLast-142]
_ = x[actReload-143]
_ = x[actReloadSync-144]
_ = x[actDisableSearch-145]
_ = x[actEnableSearch-146]
_ = x[actSelect-147]
_ = x[actDeselect-148]
_ = x[actUnbind-149]
_ = x[actRebind-150]
_ = x[actToggleBind-151]
_ = x[actBecome-152]
_ = x[actShowHeader-153]
_ = x[actHideHeader-154]
_ = x[actBell-155]
_ = x[actExclude-156]
_ = x[actExcludeMulti-157]
_ = x[actAsync-158]
_ = x[actTrigger-105]
_ = x[actBgTransform-106]
_ = x[actBgTransformBorderLabel-107]
_ = x[actBgTransformGhost-108]
_ = x[actBgTransformHeader-109]
_ = x[actBgTransformFooter-110]
_ = x[actBgTransformHeaderLabel-111]
_ = x[actBgTransformFooterLabel-112]
_ = x[actBgTransformInputLabel-113]
_ = x[actBgTransformListLabel-114]
_ = x[actBgTransformNth-115]
_ = x[actBgTransformPointer-116]
_ = x[actBgTransformPreviewLabel-117]
_ = x[actBgTransformPrompt-118]
_ = x[actBgTransformQuery-119]
_ = x[actBgTransformSearch-120]
_ = x[actBgCancel-121]
_ = x[actSearch-122]
_ = x[actPreview-123]
_ = x[actPreviewTop-124]
_ = x[actPreviewBottom-125]
_ = x[actPreviewUp-126]
_ = x[actPreviewDown-127]
_ = x[actPreviewPageUp-128]
_ = x[actPreviewPageDown-129]
_ = x[actPreviewHalfPageUp-130]
_ = x[actPreviewHalfPageDown-131]
_ = x[actPrevHistory-132]
_ = x[actPrevSelected-133]
_ = x[actPrint-134]
_ = x[actPut-135]
_ = x[actNextHistory-136]
_ = x[actNextSelected-137]
_ = x[actExecute-138]
_ = x[actExecuteSilent-139]
_ = x[actExecuteMulti-140]
_ = x[actSigStop-141]
_ = x[actFirst-142]
_ = x[actLast-143]
_ = x[actReload-144]
_ = x[actReloadSync-145]
_ = x[actDisableSearch-146]
_ = x[actEnableSearch-147]
_ = x[actSelect-148]
_ = x[actDeselect-149]
_ = x[actUnbind-150]
_ = x[actRebind-151]
_ = x[actToggleBind-152]
_ = x[actBecome-153]
_ = x[actShowHeader-154]
_ = x[actHideHeader-155]
_ = x[actBell-156]
_ = x[actExclude-157]
_ = x[actExcludeMulti-158]
_ = x[actAsync-159]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1562, 1587, 1606, 1626, 1646, 1671, 1696, 1720, 1743, 1760, 1781, 1807, 1827, 1846, 1866, 1877, 1886, 1896, 1909, 1925, 1937, 1951, 1967, 1985, 2005, 2027, 2041, 2056, 2064, 2070, 2084, 2099, 2109, 2125, 2140, 2150, 2158, 2165, 2174, 2187, 2203, 2218, 2227, 2238, 2247, 2256, 2269, 2278, 2291, 2304, 2311, 2321, 2336, 2344}
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1558, 1572, 1597, 1616, 1636, 1656, 1681, 1706, 1730, 1753, 1770, 1791, 1817, 1837, 1856, 1876, 1887, 1896, 1906, 1919, 1935, 1947, 1961, 1977, 1995, 2015, 2037, 2051, 2066, 2074, 2080, 2094, 2109, 2119, 2135, 2150, 2160, 2168, 2175, 2184, 2197, 2213, 2228, 2237, 2248, 2257, 2266, 2279, 2288, 2301, 2314, 2321, 2331, 2346, 2354}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -932,15 +932,12 @@ func parseBorder(str string, optional bool) (tui.BorderShape, error) {
return tui.BorderNone, errors.New("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
}
func parseKeyChords(str string, message string) (map[tui.Event]string, error) {
return parseKeyChordsImpl(str, message)
}
func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error) {
func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Event, error) {
if len(str) == 0 {
return nil, errors.New(message)
return nil, nil, errors.New(message)
}
list := []tui.Event{}
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
@@ -956,6 +953,7 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
lkey := strings.ToLower(key)
add := func(e tui.EventType) {
chords[e.AsEvent()] = key
list = append(list, e.AsEvent())
}
switch lkey {
case "up":
@@ -969,7 +967,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case "enter", "return":
add(tui.Enter)
case "space":
chords[tui.Key(' ')] = key
evt := tui.Key(' ')
chords[evt] = key
list = append(list, evt)
case "backspace", "bspace", "bs":
add(tui.Backspace)
case "ctrl-space":
@@ -1013,9 +1013,13 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case "multi":
add(tui.Multi)
case "alt-enter", "alt-return":
chords[tui.CtrlAltKey('m')] = key
evt := tui.CtrlAltKey('m')
chords[evt] = key
list = append(list, evt)
case "alt-space":
chords[tui.AltKey(' ')] = key
evt := tui.AltKey(' ')
chords[evt] = key
list = append(list, evt)
case "alt-bs", "alt-bspace", "alt-backspace":
add(tui.AltBackspace)
case "alt-up":
@@ -1093,7 +1097,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
chords[tui.CtrlAltKey(rune(key[9]))] = key
evt := tui.CtrlAltKey(rune(key[9]))
chords[evt] = key
list = append(list, evt)
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
@@ -1106,17 +1112,21 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case escapedPlus:
r = '+'
}
chords[tui.AltKey(r)] = key
evt := tui.AltKey(r)
chords[evt] = key
list = append(list, evt)
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if len(runes) == 1 {
chords[tui.Key(runes[0])] = key
evt := tui.Key(runes[0])
chords[evt] = key
list = append(list, evt)
} else {
return nil, errors.New("unsupported key: " + key)
return nil, list, errors.New("unsupported key: " + key)
}
}
}
return chords, nil
return chords, list, nil
}
func parseScheme(str string) (string, []criterion, error) {
@@ -1439,7 +1449,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1736,7 +1746,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
}
switch t {
case actUnbind, actRebind, actToggleBind:
if _, err := parseKeyChordsImpl(actionArg, spec[0:offset]+" target required"); err != nil {
if _, _, err := parseKeyChords(actionArg, spec[0:offset]+" target required"); err != nil {
return nil, err
}
case actChangePreviewWindow:
@@ -1781,7 +1791,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
} else if len(keyName) == 1 && keyName[0] == escapedPlus {
key = tui.Key('+')
} else {
keys, err := parseKeyChordsImpl(keyName, "key name required")
keys, _, err := parseKeyChords(keyName, "key name required")
if err != nil {
return err
}
@@ -1928,6 +1938,8 @@ func isExecuteAction(str string) actionType {
return actBgTransformQuery
case "bg-transform-search":
return actBgTransformSearch
case "trigger":
return actTrigger
case "search":
return actSearch
}
@@ -1935,7 +1947,7 @@ func isExecuteAction(str string) actionType {
}
func parseToggleSort(keymap map[tui.Event][]*action, str string) error {
keys, err := parseKeyChords(str, "key name required")
keys, _, err := parseKeyChords(str, "key name required")
if err != nil {
return err
}
@@ -2474,7 +2486,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err != nil {
return err
}
chords, err := parseKeyChords(str, "key names required")
chords, _, err := parseKeyChords(str, "key names required")
if err != nil {
return err
}

View File

@@ -142,7 +142,7 @@ func TestIrrelevantNth(t *testing.T) {
}
func TestParseKeys(t *testing.T) {
pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
pairs, _, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
checkEvent := func(e tui.Event, s string) {
if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s)
@@ -168,7 +168,7 @@ func TestParseKeys(t *testing.T) {
checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms
pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
pairs, _, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
if len(pairs) != 9 {
t.Error(9)
}
@@ -182,7 +182,7 @@ func TestParseKeys(t *testing.T) {
check(tui.Left, "left")
check(tui.Right, "right")
pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
pairs, _, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
if len(pairs) != 11 {
t.Error(11)
}
@@ -211,40 +211,40 @@ func TestParseKeysWithComma(t *testing.T) {
}
}
pairs, _ := parseKeyChords(",", "")
pairs, _, _ := parseKeyChords(",", "")
checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",,a,b", "")
pairs, _, _ = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,b,,", "")
pairs, _, _ = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,,,b", "")
pairs, _, _ = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,,,b,c", "")
pairs, _, _ = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key('c'), "c")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",,,", "")
pairs, _, _ = parseKeyChords(",,,", "")
checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",ALT-,,", "")
pairs, _, _ = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,")
}

View File

@@ -604,6 +604,8 @@ const (
actTransformQuery
actTransformSearch
actTrigger
actBgTransform
actBgTransformBorderLabel
actBgTransformGhost
@@ -5443,6 +5445,7 @@ func (t *Terminal) Loop() error {
return nil
}
}
triggering := map[tui.Event]struct{}{}
previousInput := t.input
previousCx := t.cx
previousVersion := t.version
@@ -6234,6 +6237,20 @@ func (t *Terminal) Loop() error {
case actDisableSearch:
t.paused = true
req(reqPrompt)
case actTrigger:
if _, chords, err := parseKeyChords(a.a, ""); err == nil {
for _, chord := range chords {
if _, prs := triggering[chord]; prs {
// Avoid recursive triggering
continue
}
if acts, prs := t.keymap[chord]; prs {
triggering[chord] = struct{}{}
doActions(acts)
delete(triggering, chord)
}
}
}
case actSigStop:
p, err := os.FindProcess(os.Getpid())
if err == nil {
@@ -6555,13 +6572,13 @@ func (t *Terminal) Loop() error {
t.reading = true
}
case actUnbind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys {
delete(t.keymap, key)
}
}
case actRebind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys {
if originalAction, found := t.keymapOrg[key]; found {
t.keymap[key] = originalAction
@@ -6569,7 +6586,7 @@ func (t *Terminal) Loop() error {
}
}
case actToggleBind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys {
if _, bound := t.keymap[key]; bound {
delete(t.keymap, key)

View File

@@ -2035,4 +2035,14 @@ class TestCore < TestInteractive
assert_equal 19, it.select_count
end
end
def test_trigger
tmux.send_keys %(seq 100 | #{FZF} --bind 'a:up+trigger(a),b:trigger(a,a,b,a)'), :Enter
tmux.until { assert_equal 100, it.match_count }
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys :a
tmux.until { |lines| assert_includes lines, '> 3' }
tmux.send_keys :b
tmux.until { |lines| assert_includes lines, '> 9' }
end
end