Compare commits

...

1 Commits

Author SHA1 Message Date
Junegunn Choi e0d081906f Reward non-word match at word boundary
CodeQL / Analyze (go) (push) Waiting to run
build / build (push) Waiting to run
Test fzf on macOS / build (push) Waiting to run
A non-word character (e.g. '.') used to receive a flat bonusNonWord
regardless of context. Now it gets bonusBoundaryWhite at the start of
input and bonusBoundaryDelimiter right after a delimiter, matching the
treatment of word characters at the same boundaries.

Without this, '.completion' matching '.completion' lost to
'bash_completion.d/completions/X' because the consecutive chunk anchor
in the long path (the 'c' after '/') received bonusBoundaryDelimiter
while the exact match's '.' was capped at bonusNonWord.

Fix #4795
2026-05-15 00:46:36 +09:00
2 changed files with 10 additions and 1 deletions
+1 -1
View File
@@ -266,7 +266,7 @@ func charClassOf(char rune) charClass {
} }
func bonusFor(prevClass charClass, class charClass) int16 { func bonusFor(prevClass charClass, class charClass) int16 {
if class > charNonWord { if class >= charNonWord {
switch prevClass { switch prevClass {
case charWhite: case charWhite:
// Word boundary after whitespace // Word boundary after whitespace
+9
View File
@@ -57,6 +57,15 @@ func TestFuzzyMatch(t *testing.T) {
scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3) scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3)
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13, assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter)) scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))
// Non-word character at start of input is treated as a strong boundary
assertMatch(t, fn, false, forward, ".vimrc", ".vimrc", 0, 6,
scoreMatch*6+int(bonusBoundaryWhite)*(bonusFirstCharMultiplier+5))
// Non-word character right after a delimiter inherits the delimiter boundary
assertMatch(t, fn, false, forward, "/.vimrc", ".vimrc", 1, 7,
scoreMatch*6+int(bonusBoundaryDelimiter)*(bonusFirstCharMultiplier+5))
// Non-word character in the middle of a word stays at bonusNonWord
assertMatch(t, fn, false, forward, "a.vimrc", ".vimrc", 1, 7,
scoreMatch*6+bonusBoundary*(bonusFirstCharMultiplier+5))
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10, assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension) scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10, assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,