summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-07-06 22:02:12 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-07-06 22:02:12 +0900
commit82c9671f79c4673b0253db54533e6910f96a92a1 (patch)
treefac736ee0d72a8007db35662ddbaf6be3b6e259a
parentd364a1122e23149a6fb2e060fe5f7a0dbb752b20 (diff)
downloadfzf-82c9671f79c4673b0253db54533e6910f96a92a1.tar.gz
Fix selection lost on revision bump
-rw-r--r--src/chunklist.go7
-rw-r--r--src/matcher.go3
-rw-r--r--src/merger.go14
-rw-r--r--src/merger_test.go6
-rw-r--r--src/terminal.go4
-rw-r--r--test/test_core.rb33
6 files changed, 56 insertions, 11 deletions
diff --git a/src/chunklist.go b/src/chunklist.go
index bd98999d..ce4a56a0 100644
--- a/src/chunklist.go
+++ b/src/chunklist.go
@@ -41,6 +41,13 @@ func (c *Chunk) IsFull() bool {
return c.count == chunkSize
}
+func (c *Chunk) lastIndex(minValue int32) int32 {
+ if c.count == 0 {
+ return minValue
+ }
+ return c.items[c.count-1].Index() + 1 // Exclusive
+}
+
func (cl *ChunkList) lastChunk() *Chunk {
return cl.chunks[len(cl.chunks)-1]
}
diff --git a/src/matcher.go b/src/matcher.go
index 49d39730..daaa69ce 100644
--- a/src/matcher.go
+++ b/src/matcher.go
@@ -165,6 +165,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
}
minIndex := request.chunks[0].items[0].Index()
+ maxIndex := request.chunks[numChunks-1].lastIndex(minIndex)
cancelled := util.NewAtomicBool(false)
slices := m.sliceChunks(request.chunks)
@@ -236,7 +237,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
- return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), false
+ return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex), false
}
// Reset is called to interrupt/signal the ongoing search
diff --git a/src/merger.go b/src/merger.go
index fee8a59a..688f3571 100644
--- a/src/merger.go
+++ b/src/merger.go
@@ -4,7 +4,7 @@ import "fmt"
// EmptyMerger is a Merger with no data
func EmptyMerger(revision revision) *Merger {
- return NewMerger(nil, [][]Result{}, false, false, revision, 0)
+ return NewMerger(nil, [][]Result{}, false, false, revision, 0, 0)
}
// Merger holds a set of locally sorted lists of items and provides the view of
@@ -22,14 +22,16 @@ type Merger struct {
pass bool
revision revision
minIndex int32
+ maxIndex int32
}
// PassMerger returns a new Merger that simply returns the items in the
// original order
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
- var minIndex int32
+ var minIndex, maxIndex int32
if len(*chunks) > 0 {
minIndex = (*chunks)[0].items[0].Index()
+ maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
}
mg := Merger{
pattern: nil,
@@ -38,7 +40,8 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
count: 0,
pass: true,
revision: revision,
- minIndex: minIndex}
+ minIndex: minIndex,
+ maxIndex: maxIndex}
for _, chunk := range *mg.chunks {
mg.count += chunk.count
@@ -47,7 +50,7 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
}
// NewMerger returns a new Merger
-func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32) *Merger {
+func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32, maxIndex int32) *Merger {
mg := Merger{
pattern: pattern,
lists: lists,
@@ -59,7 +62,8 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
final: false,
count: 0,
revision: revision,
- minIndex: minIndex}
+ minIndex: minIndex,
+ maxIndex: maxIndex}
for _, list := range mg.lists {
mg.count += len(list)
diff --git a/src/merger_test.go b/src/merger_test.go
index a6b28f6e..85e4795d 100644
--- a/src/merger_test.go
+++ b/src/merger_test.go
@@ -58,7 +58,7 @@ func TestMergerUnsorted(t *testing.T) {
cnt := len(items)
// Not sorted: same order
- mg := NewMerger(nil, lists, false, false, revision{}, 0)
+ mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
for i := 0; i < cnt; i++ {
assert(t, items[i] == mg.Get(i), "Invalid Get")
@@ -70,7 +70,7 @@ func TestMergerSorted(t *testing.T) {
cnt := len(items)
// Sorted sorted order
- mg := NewMerger(nil, lists, true, false, revision{}, 0)
+ mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
sort.Sort(ByRelevance(items))
for i := 0; i < cnt; i++ {
@@ -80,7 +80,7 @@ func TestMergerSorted(t *testing.T) {
}
// Inverse order
- mg2 := NewMerger(nil, lists, true, false, revision{}, 0)
+ mg2 := NewMerger(nil, lists, true, false, revision{}, 0, 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 adffbab5..748a2dfc 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -1680,12 +1680,12 @@ func (t *Terminal) UpdateList(merger *Merger) {
// Trimmed by --tail: filter selection by index
filtered := make(map[int32]selectedItem)
minIndex := merger.minIndex
- maxIndex := minIndex + int32(merger.Length())
+ maxIndex := merger.maxIndex
for k, v := range t.selected {
var included bool
if maxIndex > minIndex {
included = k >= minIndex && k < maxIndex
- } else { // int32 overflow [==> <==]
+ } else if maxIndex < minIndex { // int32 overflow [==> <==]
included = k >= minIndex || k < maxIndex
}
if included {
diff --git a/test/test_core.rb b/test/test_core.rb
index cf4cd237..4d21c19c 100644
--- a/test/test_core.rb
+++ b/test/test_core.rb
@@ -2002,4 +2002,37 @@ class TestCore < TestInteractive
tmux.until { assert_equal 0, it.select_count }
tmux.until { refute it.any_include?('Selected') }
end
+
+ def test_preserve_selection_on_revision_bump
+ tmux.send_keys %(seq 100 | #{FZF} --multi --sync --query "'1" --bind 'a:select-all+change-header(pressed a),b:change-header(pressed b)+change-nth(1),c:exclude'), :Enter
+ tmux.until do
+ assert_equal 20, it.match_count
+ assert_equal 0, it.select_count
+ end
+ tmux.send_keys :a
+ tmux.until do
+ assert_equal 20, it.match_count
+ assert_equal 20, it.select_count
+ assert it.any_include?('pressed a')
+ end
+ tmux.send_keys :b
+ tmux.until do
+ assert_equal 20, it.match_count
+ assert_equal 20, it.select_count
+ refute it.any_include?('pressed a')
+ assert it.any_include?('pressed b')
+ end
+ tmux.send_keys :a
+ tmux.until do
+ assert_equal 20, it.match_count
+ assert_equal 20, it.select_count
+ assert it.any_include?('pressed a')
+ refute it.any_include?('pressed b')
+ end
+ tmux.send_keys :c
+ tmux.until do
+ assert_equal 19, it.match_count
+ assert_equal 19, it.select_count
+ end
+ end
end