mirror of
https://github.com/junegunn/fzf.git
synced 2026-03-26 02:26:50 +08:00
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:
@@ -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":
|
||||
|
||||
@@ -314,11 +314,12 @@ type Terminal struct {
|
||||
sort bool
|
||||
toggleSort bool
|
||||
track trackOption
|
||||
trackNth []Range
|
||||
idNth []Range
|
||||
trackKey string
|
||||
trackBlocked bool
|
||||
trackSync bool
|
||||
trackKeyCache map[int32]bool
|
||||
pendingSelections map[string]selectedItem
|
||||
targetIndex int32
|
||||
delimiter Delimiter
|
||||
expect map[tui.Event]string
|
||||
@@ -1049,7 +1050,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
sort: opts.Sort > 0,
|
||||
toggleSort: opts.ToggleSort,
|
||||
track: opts.Track,
|
||||
trackNth: opts.TrackNth,
|
||||
idNth: opts.IdNth,
|
||||
targetIndex: minItem.Index(),
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
@@ -1857,7 +1858,14 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
}
|
||||
if t.revision != newRevision {
|
||||
if !t.revision.compatible(newRevision) {
|
||||
// Reloaded: clear selection
|
||||
// Reloaded: capture selection keys for restoration, then clear
|
||||
if len(t.idNth) > 0 && t.multi > 0 && len(t.selected) > 0 {
|
||||
t.pendingSelections = make(map[string]selectedItem, len(t.selected))
|
||||
for _, sel := range t.selected {
|
||||
key := t.trackKeyFor(sel.item, t.idNth)
|
||||
t.pendingSelections[key] = sel
|
||||
}
|
||||
}
|
||||
t.selected = make(map[int32]selectedItem)
|
||||
t.clearNumLinesCache()
|
||||
} else {
|
||||
@@ -1912,7 +1920,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
idx := item.Index()
|
||||
match, ok := t.trackKeyCache[idx]
|
||||
if !ok {
|
||||
match = t.trackKeyFor(item, t.trackNth) == t.trackKey
|
||||
match = t.trackKeyFor(item, t.idNth) == t.trackKey
|
||||
t.trackKeyCache[idx] = match
|
||||
}
|
||||
if match {
|
||||
@@ -1943,6 +1951,18 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
t.cy = count - min(count, t.maxItems()) + pos
|
||||
}
|
||||
}
|
||||
// Restore selections by id-nth key after reload completes
|
||||
if !t.reading && t.pendingSelections != nil {
|
||||
for i := 0; i < t.merger.Length() && len(t.pendingSelections) > 0; i++ {
|
||||
item := t.merger.Get(i).item
|
||||
key := t.trackKeyFor(item, t.idNth)
|
||||
if sel, found := t.pendingSelections[key]; found {
|
||||
t.selected[item.Index()] = selectedItem{sel.at, item}
|
||||
delete(t.pendingSelections, key)
|
||||
}
|
||||
}
|
||||
t.pendingSelections = nil
|
||||
}
|
||||
needActivation := false
|
||||
if !t.reading {
|
||||
switch t.resultMerger.Length() {
|
||||
@@ -7104,12 +7124,6 @@ func (t *Terminal) Loop() error {
|
||||
if !t.track.Global() {
|
||||
t.track = trackCurrent(t.currentIndex())
|
||||
}
|
||||
// Parse optional nth argument: track-current(1) or track-current(1,3)
|
||||
if len(a.a) > 0 {
|
||||
if nth, err := splitNth(a.a); err == nil {
|
||||
t.trackNth = nth
|
||||
}
|
||||
}
|
||||
req(reqInfo)
|
||||
case actUntrackCurrent:
|
||||
if t.track.Current() {
|
||||
@@ -7469,9 +7483,9 @@ func (t *Terminal) Loop() error {
|
||||
t.reading = true
|
||||
|
||||
// Capture tracking key before reload
|
||||
if !t.track.Disabled() && len(t.trackNth) > 0 {
|
||||
if !t.track.Disabled() && len(t.idNth) > 0 {
|
||||
if item := t.currentItem(); item != nil {
|
||||
t.trackKey = t.trackKeyFor(item, t.trackNth)
|
||||
t.trackKey = t.trackKeyFor(item, t.idNth)
|
||||
t.trackKeyCache = make(map[int32]bool)
|
||||
t.trackBlocked = true
|
||||
t.trackSync = reloadSync
|
||||
|
||||
Reference in New Issue
Block a user