diff --git a/src/algo/algo.go b/src/algo/algo.go index 7748a070..2fddcab3 100644 --- a/src/algo/algo.go +++ b/src/algo/algo.go @@ -630,6 +630,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util. s2 = H[I+j0-1] } + row := i if s > s1 && (s > s2 || s == s2 && preferMatch) { *pos = append(*pos, j+minIdx) if i == 0 { @@ -637,7 +638,10 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util. } i-- } - preferMatch = C[I+j0] > 1 || I+width+j0+1 < len(C) && C[I+width+j0+1] > 0 + // Row below is only written from column F[row+1]; don't read + // stale slab data left of it + preferMatch = C[I+j0] > 1 || + row+1 < M && j < lastIdx && int32(j+1) >= F[row+1] && C[I+width+j0+1] > 0 j-- } } diff --git a/src/algo/algo_test.go b/src/algo/algo_test.go index cd214f5d..45578521 100644 --- a/src/algo/algo_test.go +++ b/src/algo/algo_test.go @@ -218,3 +218,26 @@ func TestLongStringWithNormalize(t *testing.T) { unicodeString := string(bytes) + " MinĂ­mal example" assertMatch2(t, FuzzyMatchV1, false, true, false, unicodeString, "minim", 30001, 30006, 140) } + +func TestResultPositionsWithReusedSlab(t *testing.T) { + // Backtrace positions in equal-score ties must not depend on data + // a previous match left in the slab + pattern := []rune("co/") + target := util.ToChars([]byte("core_color/view/server.txt")) + _, freshPos := FuzzyMatchV2(false, false, true, &target, pattern, true, util.MakeSlab(100*1024, 2048)) + + slab := util.MakeSlab(100*1024, 2048) + dirty := util.ToChars([]byte("completion/keybinding/client/handler/writer_index.txt")) + FuzzyMatchV2(false, false, true, &dirty, pattern, true, slab) + _, reusedPos := FuzzyMatchV2(false, false, true, &target, pattern, true, slab) + + if len(*freshPos) != len(*reusedPos) { + t.Fatalf("position count mismatch: %v vs %v", *freshPos, *reusedPos) + } + for i := range *freshPos { + if (*freshPos)[i] != (*reusedPos)[i] { + t.Errorf("positions differ with reused slab: %v vs %v", *freshPos, *reusedPos) + break + } + } +}