Compare commits

...

2 Commits

Author SHA1 Message Date
pull[bot]
f4871884d1
Merge d83eb2800a into a30181e240 2025-01-12 15:15:37 +00:00
Junegunn Choi
d83eb2800a
Add change-nth action
Example:
  # Start with --nth 1, then 2, then 3, then back to the default, 1
  echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo

Close #4172
Close #3109
2025-01-13 00:13:31 +09:00
7 changed files with 163 additions and 102 deletions

View File

@ -67,6 +67,11 @@ Also, fzf now offers "style presets" for quick customization, which can be activ
```
- Added `toggle-multi-line` action
- Added `toggle-hscroll` action
- Added `change-nth` action for dynamically changing the value of the `--nth` option
```sh
# Start with --nth 1, then 2, then 3, then back to the default, 1
echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo
```
- A single-character delimiter is now treated as a plain string delimiter rather than a regular expression delimiter, even if it's a regular expression meta-character.
- This means you can just write `--delimiter '|'` instead of escaping it as `--delimiter '\|'`
- Bug fixes

View File

@ -496,7 +496,7 @@ the label. Label is printed on the top border line by default, add
.SS LIST SECTION
.TP
.B "\-m, \-\-multi"
.BI "\-m, \-\-multi" "[=MAX]"
Enable multi-select with tab/shift\-tab. It optionally takes an integer argument
which denotes the maximum number of items that can be selected.
.TP
@ -1525,6 +1525,7 @@ A key or an event can be bound to one or more of the following actions.
\fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string)
\fBchange\-multi\fR (enable multi-select mode with no limit)
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
\fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|')

View File

@ -33,107 +33,108 @@ func _() {
_ = x[actChangePreviewLabel-22]
_ = x[actChangePrompt-23]
_ = x[actChangeQuery-24]
_ = x[actClearScreen-25]
_ = x[actClearQuery-26]
_ = x[actClearSelection-27]
_ = x[actClose-28]
_ = x[actDeleteChar-29]
_ = x[actDeleteCharEof-30]
_ = x[actEndOfLine-31]
_ = x[actFatal-32]
_ = x[actForwardChar-33]
_ = x[actForwardWord-34]
_ = x[actKillLine-35]
_ = x[actKillWord-36]
_ = x[actUnixLineDiscard-37]
_ = x[actUnixWordRubout-38]
_ = x[actYank-39]
_ = x[actBackwardKillWord-40]
_ = x[actSelectAll-41]
_ = x[actDeselectAll-42]
_ = x[actToggle-43]
_ = x[actToggleSearch-44]
_ = x[actToggleAll-45]
_ = x[actToggleDown-46]
_ = x[actToggleUp-47]
_ = x[actToggleIn-48]
_ = x[actToggleOut-49]
_ = x[actToggleTrack-50]
_ = x[actToggleTrackCurrent-51]
_ = x[actToggleHeader-52]
_ = x[actToggleWrap-53]
_ = x[actToggleMultiLine-54]
_ = x[actToggleHscroll-55]
_ = x[actTrackCurrent-56]
_ = x[actUntrackCurrent-57]
_ = x[actDown-58]
_ = x[actUp-59]
_ = x[actPageUp-60]
_ = x[actPageDown-61]
_ = x[actPosition-62]
_ = x[actHalfPageUp-63]
_ = x[actHalfPageDown-64]
_ = x[actOffsetUp-65]
_ = x[actOffsetDown-66]
_ = x[actOffsetMiddle-67]
_ = x[actJump-68]
_ = x[actJumpAccept-69]
_ = x[actPrintQuery-70]
_ = x[actRefreshPreview-71]
_ = x[actReplaceQuery-72]
_ = x[actToggleSort-73]
_ = x[actShowPreview-74]
_ = x[actHidePreview-75]
_ = x[actTogglePreview-76]
_ = x[actTogglePreviewWrap-77]
_ = x[actTransform-78]
_ = x[actTransformBorderLabel-79]
_ = x[actTransformListLabel-80]
_ = x[actTransformInputLabel-81]
_ = x[actTransformHeader-82]
_ = x[actTransformHeaderLabel-83]
_ = x[actTransformPreviewLabel-84]
_ = x[actTransformPrompt-85]
_ = x[actTransformQuery-86]
_ = x[actPreview-87]
_ = x[actChangePreview-88]
_ = x[actChangePreviewWindow-89]
_ = x[actPreviewTop-90]
_ = x[actPreviewBottom-91]
_ = x[actPreviewUp-92]
_ = x[actPreviewDown-93]
_ = x[actPreviewPageUp-94]
_ = x[actPreviewPageDown-95]
_ = x[actPreviewHalfPageUp-96]
_ = x[actPreviewHalfPageDown-97]
_ = x[actPrevHistory-98]
_ = x[actPrevSelected-99]
_ = x[actPrint-100]
_ = x[actPut-101]
_ = x[actNextHistory-102]
_ = x[actNextSelected-103]
_ = x[actExecute-104]
_ = x[actExecuteSilent-105]
_ = x[actExecuteMulti-106]
_ = x[actSigStop-107]
_ = x[actFirst-108]
_ = x[actLast-109]
_ = x[actReload-110]
_ = x[actReloadSync-111]
_ = x[actDisableSearch-112]
_ = x[actEnableSearch-113]
_ = x[actSelect-114]
_ = x[actDeselect-115]
_ = x[actUnbind-116]
_ = x[actRebind-117]
_ = x[actBecome-118]
_ = x[actShowHeader-119]
_ = x[actHideHeader-120]
_ = x[actChangeNth-25]
_ = x[actClearScreen-26]
_ = x[actClearQuery-27]
_ = x[actClearSelection-28]
_ = x[actClose-29]
_ = x[actDeleteChar-30]
_ = x[actDeleteCharEof-31]
_ = x[actEndOfLine-32]
_ = x[actFatal-33]
_ = x[actForwardChar-34]
_ = x[actForwardWord-35]
_ = x[actKillLine-36]
_ = x[actKillWord-37]
_ = x[actUnixLineDiscard-38]
_ = x[actUnixWordRubout-39]
_ = x[actYank-40]
_ = x[actBackwardKillWord-41]
_ = x[actSelectAll-42]
_ = x[actDeselectAll-43]
_ = x[actToggle-44]
_ = x[actToggleSearch-45]
_ = x[actToggleAll-46]
_ = x[actToggleDown-47]
_ = x[actToggleUp-48]
_ = x[actToggleIn-49]
_ = x[actToggleOut-50]
_ = x[actToggleTrack-51]
_ = x[actToggleTrackCurrent-52]
_ = x[actToggleHeader-53]
_ = x[actToggleWrap-54]
_ = x[actToggleMultiLine-55]
_ = x[actToggleHscroll-56]
_ = x[actTrackCurrent-57]
_ = x[actUntrackCurrent-58]
_ = x[actDown-59]
_ = x[actUp-60]
_ = x[actPageUp-61]
_ = x[actPageDown-62]
_ = x[actPosition-63]
_ = x[actHalfPageUp-64]
_ = x[actHalfPageDown-65]
_ = x[actOffsetUp-66]
_ = x[actOffsetDown-67]
_ = x[actOffsetMiddle-68]
_ = x[actJump-69]
_ = x[actJumpAccept-70]
_ = x[actPrintQuery-71]
_ = x[actRefreshPreview-72]
_ = x[actReplaceQuery-73]
_ = x[actToggleSort-74]
_ = x[actShowPreview-75]
_ = x[actHidePreview-76]
_ = x[actTogglePreview-77]
_ = x[actTogglePreviewWrap-78]
_ = x[actTransform-79]
_ = x[actTransformBorderLabel-80]
_ = x[actTransformListLabel-81]
_ = x[actTransformInputLabel-82]
_ = x[actTransformHeader-83]
_ = x[actTransformHeaderLabel-84]
_ = x[actTransformPreviewLabel-85]
_ = x[actTransformPrompt-86]
_ = x[actTransformQuery-87]
_ = x[actPreview-88]
_ = x[actChangePreview-89]
_ = x[actChangePreviewWindow-90]
_ = x[actPreviewTop-91]
_ = x[actPreviewBottom-92]
_ = x[actPreviewUp-93]
_ = x[actPreviewDown-94]
_ = x[actPreviewPageUp-95]
_ = x[actPreviewPageDown-96]
_ = x[actPreviewHalfPageUp-97]
_ = x[actPreviewHalfPageDown-98]
_ = x[actPrevHistory-99]
_ = x[actPrevSelected-100]
_ = x[actPrint-101]
_ = x[actPut-102]
_ = x[actNextHistory-103]
_ = x[actNextSelected-104]
_ = x[actExecute-105]
_ = x[actExecuteSilent-106]
_ = x[actExecuteMulti-107]
_ = x[actSigStop-108]
_ = x[actFirst-109]
_ = x[actLast-110]
_ = x[actReload-111]
_ = x[actReloadSync-112]
_ = x[actDisableSearch-113]
_ = x[actEnableSearch-114]
_ = x[actSelect-115]
_ = x[actDeselect-116]
_ = x[actUnbind-117]
_ = x[actRebind-118]
_ = x[actBecome-119]
_ = x[actShowHeader-120]
_ = x[actHideHeader-121]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 377, 390, 407, 415, 428, 444, 456, 464, 478, 492, 503, 514, 532, 549, 556, 575, 587, 601, 610, 625, 637, 650, 661, 672, 684, 698, 719, 734, 747, 765, 781, 796, 813, 820, 825, 834, 845, 856, 869, 884, 895, 908, 923, 930, 943, 956, 973, 988, 1001, 1015, 1029, 1045, 1065, 1077, 1100, 1121, 1143, 1161, 1184, 1208, 1226, 1243, 1253, 1269, 1291, 1304, 1320, 1332, 1346, 1362, 1380, 1400, 1422, 1436, 1451, 1459, 1465, 1479, 1494, 1504, 1520, 1535, 1545, 1553, 1560, 1569, 1582, 1598, 1613, 1622, 1633, 1642, 1651, 1660, 1673, 1686}
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1265, 1281, 1303, 1316, 1332, 1344, 1358, 1374, 1392, 1412, 1434, 1448, 1463, 1471, 1477, 1491, 1506, 1516, 1532, 1547, 1557, 1565, 1572, 1581, 1594, 1610, 1625, 1634, 1645, 1654, 1663, 1672, 1685, 1698}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@ -190,11 +190,13 @@ func Run(opts *Options) (int, error) {
forward = true
}
}
nth := opts.Nth
patternCache := make(map[string]*Pattern)
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(cache, patternCache,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
opts.Filter == nil, nth, opts.Delimiter, runes)
}
inputRevision := revision{}
snapshotRevision := revision{}
@ -373,6 +375,12 @@ func Run(opts *Options) (int, error) {
command = val.command
environ = val.environ
changed = val.changed
if val.nth != nil {
// Change nth and clear caches
nth = *val.nth
patternCache = make(map[string]*Pattern)
inputRevision.bumpMajor()
}
if command != nil {
useSnapshot = val.sync
}

View File

@ -1306,7 +1306,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put|print)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header)|transform|change-(?:preview-window|preview|multi|nth)|(?:re|un)bind|pos|put|print)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@ -1684,6 +1684,8 @@ func isExecuteAction(str string) actionType {
return actChangeQuery
case "change-multi":
return actChangeMulti
case "change-nth":
return actChangeNth
case "pos":
return actPosition
case "execute":

View File

@ -299,6 +299,7 @@ type Terminal struct {
scrollbar string
previewScrollbar string
ansi bool
nth []Range
tabstop int
margin [4]sizeSpec
padding [4]sizeSpec
@ -462,6 +463,7 @@ const (
actChangePreviewLabel
actChangePrompt
actChangeQuery
actChangeNth
actClearScreen
actClearQuery
actClearSelection
@ -597,6 +599,7 @@ type placeholderFlags struct {
type searchRequest struct {
sort bool
sync bool
nth *[]Range
command *commandSpec
environ []string
changed bool
@ -880,6 +883,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
header: []string{},
header0: opts.Header,
ansi: opts.Ansi,
nth: opts.Nth,
tabstop: opts.Tabstop,
hasStartActions: false,
hasResultActions: false,
@ -4359,6 +4363,7 @@ func (t *Terminal) Loop() error {
}
for loopIndex := int64(0); looping; loopIndex++ {
var newCommand *commandSpec
var newNth *[]Range
var reloadSync bool
changed := false
beof := false
@ -4618,6 +4623,22 @@ func (t *Terminal) Loop() error {
}
t.multi = multi
req(reqList, reqInfo)
case actChangeNth:
changed = true
// Split nth expression
tokens := strings.Split(a.a, "|")
if nth, err := splitNth(tokens[0]); err == nil {
// Changed
newNth = &nth
} else {
// The default
newNth = &t.nth
}
// Cycle
if len(tokens) > 1 {
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
}
case actChangeQuery:
t.input = []rune(a.a)
t.cx = len(t.input)
@ -5537,7 +5558,7 @@ func (t *Terminal) Loop() error {
reload := changed || newCommand != nil
var reloadRequest *searchRequest
if reload {
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, environ: t.environ(), changed: changed}
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed}
}
t.mutex.Unlock() // Must be unlocked before touching reqBox

View File

@ -3718,6 +3718,29 @@ class TestGoFZF < TestBase
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_change_nth
input = [
'foo bar bar bar bar',
'foo foo bar bar bar',
'foo foo foo bar bar',
'foo foo foo foo bar'
]
writelines(input)
tmux.send_keys %(#{FZF} -qfoo -n1 --bind 'space:change-nth:2|3|4|5|' < #{tempname}), :Enter
tmux.until { |lines| assert_equal 4, lines.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert_equal 3, lines.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert_equal 4, lines.match_count }
end
end
module TestShell