From 4c70745cc1c8856486dab9e07985cbcf5081e490 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sat, 27 May 2023 15:43:31 +0900 Subject: [PATCH] Fix bug where preview is not updated after reload when --disabled is set Fix #3311 --- src/core.go | 25 +++++++++--------- src/matcher.go | 29 +++++++++++---------- src/merger.go | 64 +++++++++++++++++++++++++++------------------- src/merger_test.go | 6 ++--- src/terminal.go | 7 +++-- test/test_go.rb | 8 ++++++ 6 files changed, 81 insertions(+), 58 deletions(-) diff --git a/src/core.go b/src/core.go index e049fb4..e21e8c0 100644 --- a/src/core.go +++ b/src/core.go @@ -138,7 +138,9 @@ func Run(opts *Options, version string, revision string) { opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos, opts.Filter == nil, opts.Nth, opts.Delimiter, runes) } - matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox) + inputRevision := 0 + snapshotRevision := 0 + matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision) // Filtering mode if opts.Filter != nil { @@ -209,8 +211,6 @@ func Run(opts *Options, version string, revision string) { // Event coordination reading := true - clearCache := util.Once(false) - clearSelection := util.Once(false) ticks := 0 var nextCommand *string eventBox.Watch(EvtReadNew) @@ -234,17 +234,17 @@ func Run(opts *Options, version string, revision string) { var count int restart := func(command string) { reading = true - clearCache = util.Once(true) - clearSelection = util.Once(true) chunkList.Clear() itemIndex = 0 + inputRevision++ header = make([]string, 0, opts.HeaderLines) go reader.restart(command) } for { delay := true ticks++ - input := func(reloaded bool) []rune { + input := func() []rune { + reloaded := snapshotRevision != inputRevision paused, input := terminal.Input() if reloaded && paused { query = []rune{} @@ -277,18 +277,18 @@ func Run(opts *Options, version string, revision string) { } if !useSnapshot { snapshot, count = chunkList.Snapshot() + snapshotRevision = inputRevision } total = count terminal.UpdateCount(total, !reading, value.(*string)) if opts.Sync { opts.Sync = false - terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false) + terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision)) } if heightUnknown && !deferred { determine(!reading) } - reset := !useSnapshot && clearCache() - matcher.Reset(snapshot, input(reset), false, !reading, sort, reset) + matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision) case EvtSearchNew: var command *string @@ -313,17 +313,16 @@ func Run(opts *Options, version string, revision string) { if !changed { break } - reset := false if !useSnapshot { newSnapshot, _ := chunkList.Snapshot() // We want to avoid showing empty list when reload is triggered // and the query string is changed at the same time i.e. command != nil && changed if command == nil || len(newSnapshot) > 0 { snapshot = newSnapshot - reset = clearCache() + snapshotRevision = inputRevision } } - matcher.Reset(snapshot, input(reset), true, !reading, sort, reset) + matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision) delay = false case EvtSearchProgress: @@ -363,7 +362,7 @@ func Run(opts *Options, version string, revision string) { determine(val.final) } } - terminal.UpdateList(val, clearSelection()) + terminal.UpdateList(val) } } } diff --git a/src/matcher.go b/src/matcher.go index 22aa819..b9288bb 100644 --- a/src/matcher.go +++ b/src/matcher.go @@ -12,11 +12,11 @@ import ( // MatchRequest represents a search request type MatchRequest struct { - chunks []*Chunk - pattern *Pattern - final bool - sort bool - clearCache bool + chunks []*Chunk + pattern *Pattern + final bool + sort bool + revision int } // Matcher is responsible for performing search @@ -29,6 +29,7 @@ type Matcher struct { partitions int slab []*util.Slab mergerCache map[string]*Merger + revision int } const ( @@ -38,7 +39,7 @@ const ( // NewMatcher returns a new Matcher func NewMatcher(patternBuilder func([]rune) *Pattern, - sort bool, tac bool, eventBox *util.EventBox) *Matcher { + sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher { partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions) return &Matcher{ patternBuilder: patternBuilder, @@ -48,7 +49,8 @@ func NewMatcher(patternBuilder func([]rune) *Pattern, reqBox: util.NewEventBox(), partitions: partitions, slab: make([]*util.Slab, partitions), - mergerCache: make(map[string]*Merger)} + mergerCache: make(map[string]*Merger), + revision: revision} } // Loop puts Matcher in action @@ -70,8 +72,9 @@ func (m *Matcher) Loop() { events.Clear() }) - if request.sort != m.sort || request.clearCache { + if request.sort != m.sort || request.revision != m.revision { m.sort = request.sort + m.revision = request.revision m.mergerCache = make(map[string]*Merger) clearChunkCache() } @@ -140,11 +143,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { numChunks := len(request.chunks) if numChunks == 0 { - return EmptyMerger, false + return EmptyMerger(request.revision), false } pattern := request.pattern if pattern.IsEmpty() { - return PassMerger(&request.chunks, m.tac), false + return PassMerger(&request.chunks, m.tac, request.revision), false } cancelled := util.NewAtomicBool(false) @@ -218,11 +221,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { partialResult := <-resultChan partialResults[partialResult.index] = partialResult.matches } - return NewMerger(pattern, partialResults, m.sort, m.tac), false + return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision), false } // Reset is called to interrupt/signal the ongoing search -func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) { +func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision int) { pattern := m.patternBuilder(patternRunes) var event util.EventType @@ -231,5 +234,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final } else { event = reqRetry } - m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache}) + m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision}) } diff --git a/src/merger.go b/src/merger.go index 5c12575..6a23504 100644 --- a/src/merger.go +++ b/src/merger.go @@ -3,32 +3,36 @@ package fzf import "fmt" // EmptyMerger is a Merger with no data -var EmptyMerger = NewMerger(nil, [][]Result{}, false, false) +func EmptyMerger(revision int) *Merger { + return NewMerger(nil, [][]Result{}, false, false, revision) +} // Merger holds a set of locally sorted lists of items and provides the view of // a single, globally-sorted list type Merger struct { - pattern *Pattern - lists [][]Result - merged []Result - chunks *[]*Chunk - cursors []int - sorted bool - tac bool - final bool - count int - pass bool + pattern *Pattern + lists [][]Result + merged []Result + chunks *[]*Chunk + cursors []int + sorted bool + tac bool + final bool + count int + pass bool + revision int } // PassMerger returns a new Merger that simply returns the items in the // original order -func PassMerger(chunks *[]*Chunk, tac bool) *Merger { +func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger { mg := Merger{ - pattern: nil, - chunks: chunks, - tac: tac, - count: 0, - pass: true} + pattern: nil, + chunks: chunks, + tac: tac, + count: 0, + pass: true, + revision: revision} for _, chunk := range *mg.chunks { mg.count += chunk.count @@ -37,17 +41,18 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger { } // NewMerger returns a new Merger -func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger { +func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision int) *Merger { mg := Merger{ - pattern: pattern, - lists: lists, - merged: []Result{}, - chunks: nil, - cursors: make([]int, len(lists)), - sorted: sorted, - tac: tac, - final: false, - count: 0} + pattern: pattern, + lists: lists, + merged: []Result{}, + chunks: nil, + cursors: make([]int, len(lists)), + sorted: sorted, + tac: tac, + final: false, + count: 0, + revision: revision} for _, list := range mg.lists { mg.count += len(list) @@ -55,6 +60,11 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merge return &mg } +// Revision returns revision number +func (mg *Merger) Revision() int { + return mg.revision +} + // Length returns the number of items func (mg *Merger) Length() int { return mg.count diff --git a/src/merger_test.go b/src/merger_test.go index c6af4f6..5894bea 100644 --- a/src/merger_test.go +++ b/src/merger_test.go @@ -57,7 +57,7 @@ func TestMergerUnsorted(t *testing.T) { cnt := len(items) // Not sorted: same order - mg := NewMerger(nil, lists, false, false) + mg := NewMerger(nil, lists, false, false, 0) assert(t, cnt == mg.Length(), "Invalid Length") for i := 0; i < cnt; i++ { assert(t, items[i] == mg.Get(i), "Invalid Get") @@ -69,7 +69,7 @@ func TestMergerSorted(t *testing.T) { cnt := len(items) // Sorted sorted order - mg := NewMerger(nil, lists, true, false) + mg := NewMerger(nil, lists, true, false, 0) assert(t, cnt == mg.Length(), "Invalid Length") sort.Sort(ByRelevance(items)) for i := 0; i < cnt; i++ { @@ -79,7 +79,7 @@ func TestMergerSorted(t *testing.T) { } // Inverse order - mg2 := NewMerger(nil, lists, true, false) + mg2 := NewMerger(nil, lists, true, false, 0) for i := cnt - 1; i >= 0; i-- { if items[i] != mg2.Get(i) { t.Error("Not sorted", items[i], mg2.Get(i)) diff --git a/src/terminal.go b/src/terminal.go index 7f6adad..3179d92 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -228,6 +228,7 @@ type Terminal struct { merger *Merger selected map[int32]selectedItem version int64 + revision int reqBox *util.EventBox initialPreviewOpts previewOpts previewOpts previewOpts @@ -644,7 +645,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { jumpLabels: opts.JumpLabels, printer: opts.Printer, printsep: opts.PrintSep, - merger: EmptyMerger, + merger: EmptyMerger(0), selected: make(map[int32]selectedItem), reqBox: util.NewEventBox(), initialPreviewOpts: opts.Preview, @@ -930,9 +931,10 @@ func (t *Terminal) UpdateProgress(progress float32) { } // UpdateList updates Merger to display the list -func (t *Terminal) UpdateList(merger *Merger, reset bool) { +func (t *Terminal) UpdateList(merger *Merger) { t.mutex.Lock() var prevIndex int32 = -1 + reset := t.revision != merger.Revision() if !reset && t.track != trackDisabled { if t.merger.Length() > 0 { prevIndex = t.merger.Get(t.cy).item.Index() @@ -944,6 +946,7 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) { t.merger = merger if reset { t.selected = make(map[int32]selectedItem) + t.revision = merger.Revision() t.version++ } if t.triggerLoad { diff --git a/test/test_go.rb b/test/test_go.rb index 469d007..5ac0eee 100755 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -2935,6 +2935,14 @@ class TestGoFZF < TestBase tmux.send_keys "sleep 0.5 | #{FZF} --bind 'start:reload:ls' --bind 'load:become:tty'", :Enter tmux.until { |lines| assert_includes lines, '/dev/tty' } end + + def test_disabled_preview_update + tmux.send_keys "echo bar | #{FZF} --disabled --bind 'change:reload:echo foo' --preview 'echo [{q}-{}]'", :Enter + tmux.until { |lines| assert_equal 1, lines.match_count } + tmux.until { |lines| assert(lines.any? { |line| line.include?('[-bar]') }) } + tmux.send_keys :x + tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) } + end end module TestShell