From b395cfbd91a9e24cbcfca6ecbc8d7e39bbe54101 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Mon, 31 Aug 2026 18:54:53 +0900 Subject: [PATCH] Fix --tiebreak=pathname with non-ASCII text Fix #4902 --- CHANGELOG.md | 1 + src/result.go | 6 +++--- src/result_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d8f9e8f..b7deb432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ CHANGELOG 0.74.4 ------ +- Fixed `--tiebreak=pathname` not detecting the last path separator when the line contains a non-ASCII character before it (#4902) - Vim plugin - fzf no longer blocks the editor, so live previews keep working while fzf is open - `fzf#run` returns an empty list when it runs fzf asynchronously. Use `sink`, `sinklist`, or `exit` to get the result diff --git a/src/result.go b/src/result.go index bcc5d84b..0eefce90 100644 --- a/src/result.go +++ b/src/result.go @@ -82,10 +82,10 @@ func buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int, val = item.TrimLength() case byPathname: if validOffsetFound { + // Rune index, to be comparable with minBegin lastDelim := -1 - s := item.text.ToString() - for i := len(s) - 1; i >= 0; i-- { - if s[i] == '/' || s[i] == '\\' { + for i := numChars - 1; i >= 0; i-- { + if r := item.text.Get(i); r == '/' || r == '\\' { lastDelim = i break } diff --git a/src/result_test.go b/src/result_test.go index a040e118..6efa24a5 100644 --- a/src/result_test.go +++ b/src/result_test.go @@ -272,3 +272,28 @@ func TestRadixSortResults(t *testing.T) { } } } + +func TestPathnameTiebreak(t *testing.T) { + // FIXME global + sortCriteria = []criterion{byScore, byPathname} + + score := 100 + test := func(input string, offset Offset, expected uint16) { + for _, chars := range []util.Chars{util.ToChars([]byte(input)), util.RunesToChars([]rune(input))} { + item := buildResult(withIndex(&Item{text: chars}, 1), []Offset{offset}, score) + if item.points[3] != math.MaxUint16-uint16(score) || item.points[2] != expected { + t.Error(input, item.points, expected) + } + } + } + + // Match in the file name + test("x/foo/foo.txt", Offset{6, 9}, 1) + // Match in the directory path + test("x/foo/aa.txt", Offset{2, 5}, math.MaxUint16) + + // Offsets are rune indexes, so a multi-byte character before the last + // delimiter must not shift the delimiter position + test("一x/foo/foo.txt", Offset{7, 10}, 1) + test("一x/foo/aa.txt", Offset{3, 6}, math.MaxUint16) +}