From e0d081906fd7a31b656b1d022042abcd1dcc0927 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Fri, 15 May 2026 00:46:36 +0900 Subject: [PATCH] Reward non-word match at word boundary 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 --- src/algo/algo.go | 2 +- src/algo/algo_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/algo/algo.go b/src/algo/algo.go index 77d346b1..2f0af3e4 100644 --- a/src/algo/algo.go +++ b/src/algo/algo.go @@ -266,7 +266,7 @@ func charClassOf(char rune) charClass { } func bonusFor(prevClass charClass, class charClass) int16 { - if class > charNonWord { + if class >= charNonWord { switch prevClass { case charWhite: // Word boundary after whitespace diff --git a/src/algo/algo_test.go b/src/algo/algo_test.go index c2ed9e2c..cd214f5d 100644 --- a/src/algo/algo_test.go +++ b/src/algo/algo_test.go @@ -57,6 +57,15 @@ func TestFuzzyMatch(t *testing.T) { scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3) assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13, 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, scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension) assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,