Replace --track=NTH with --id-nth for cross-reload item identity

Separate item identity from cursor tracking:
- Add --id-nth=NTH to define item identity fields for cross-reload ops
- --track reverts to a simple boolean flag
- track-current action no longer accepts nth argument
- With --multi, selections are preserved across reload-sync by matching
  identity keys in the reloaded list

Close #4718
Close #4701
Close #4483
Close #4409
Close #3460
Close #2441
This commit is contained in:
Junegunn Choi
2026-03-14 21:49:16 +09:00
parent e6b9a08699
commit b5f7221580
5 changed files with 118 additions and 72 deletions
+13 -20
View File
@@ -101,6 +101,7 @@ Usage: fzf [options]
--no-multi-line Disable multi-line display of items when using --read0
--raw Enable raw mode (show non-matching items)
--track Track the current selection when the result is updated
--id-nth=N[,..] Define item identity fields for cross-reload operations
--tac Reverse the order of the input
--gap[=N] Render empty lines between each item
--gap-line[=STR] Draw horizontal line on each gap using the string
@@ -594,7 +595,7 @@ type Options struct {
Sort int
Raw bool
Track trackOption
TrackNth []Range
IdNth []Range
Tac bool
Tail int
Criteria []criterion
@@ -1631,7 +1632,7 @@ const (
func init() {
argActionRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger|track(?:-current)?)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|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-]+")
}
@@ -1955,12 +1956,6 @@ func parseActionList(masked string, original string, prevActions []*action, putA
if _, _, err := parseKeyChords(actionArg, spec[0:offset]+" target required"); err != nil {
return nil, err
}
case actTrackCurrent:
if len(actionArg) > 0 {
if _, err := splitNth(actionArg); err != nil {
return nil, err
}
}
case actChangePreviewWindow:
opts := previewOpts{}
for _, arg := range strings.Split(actionArg, "|") {
@@ -2166,8 +2161,6 @@ func isExecuteAction(str string) actionType {
return actTrigger
case "search":
return actSearch
case "track", "track-current":
return actTrackCurrent
}
return actIgnore
}
@@ -2818,18 +2811,18 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Raw = false
case "--track":
opts.Track = trackEnabled
if ok, str := optionalNextString(); ok {
nth, err := splitNth(str)
if err != nil {
return err
}
opts.TrackNth = nth
} else {
opts.TrackNth = nil
}
case "--no-track":
opts.Track = trackDisabled
opts.TrackNth = nil
case "--id-nth":
str, err := nextString("nth expression required")
if err != nil {
return err
}
if opts.IdNth, err = splitNth(str); err != nil {
return err
}
case "--no-id-nth":
opts.IdNth = nil
case "--tac":
opts.Tac = true
case "--no-tac":