Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions gopls/internal/fuzzy/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const (
// MaxPatternSize is the maximum size of the pattern used to construct the fuzzy matcher. Longer
// inputs are truncated to this size.
MaxPatternSize = 63

shortPatternSize = 3
)

type scoreVal int
Expand Down Expand Up @@ -88,7 +90,7 @@ func NewMatcher(pattern string) *Matcher {
}
}

if len(pattern) > 3 {
if len(pattern) > shortPatternSize {
m.patternShort = m.patternLower[:3]
} else {
m.patternShort = m.patternLower
Expand Down Expand Up @@ -123,12 +125,12 @@ func (m *Matcher) ScoreChunks(chunks []string) float32 {
// Empty patterns perfectly match candidates.
return 1
}

if m.match(candidate, lower) {
sc := m.computeScore(candidate, lower)
ok, l := m.match(candidate, lower)
if ok {
sc := m.computeScore(candidate, lower, l)
if sc > minScore/2 && !m.poorMatch() {
m.lastCandidateMatched = true
if len(m.pattern) == len(candidate) {
if l == len(candidate) {
// Perfect match.
return 1
}
Expand Down Expand Up @@ -182,25 +184,26 @@ func (m *Matcher) MatchedRanges() []int {
return ret
}

func (m *Matcher) match(candidate []byte, candidateLower []byte) bool {
func (m *Matcher) match(candidate []byte, candidateLower []byte) (bool, int) {
i, j := 0, 0
for ; i < len(candidateLower) && j < len(m.patternLower); i++ {
if candidateLower[i] == m.patternLower[j] {
j++
}
}
if j != len(m.patternLower) {
return false
// if the candidate is short the characters have to match completely.
if len(candidate) <= shortPatternSize && j != len(m.patternLower) {
return false, 0
}

// The input passes the simple test against pattern, so it is time to classify its characters.
// Character roles are used below to find the last segment.
m.roles = RuneRoles(candidate, m.rolesBuf[:])

return true
return true, j
}

func (m *Matcher) computeScore(candidate []byte, candidateLower []byte) int {
func (m *Matcher) computeScore(candidate []byte, candidateLower []byte, matchPatterLen int) int {
pattLen, candLen := len(m.pattern), len(candidate)

for j := 0; j <= len(m.pattern); j++ {
Expand Down Expand Up @@ -328,8 +331,7 @@ func (m *Matcher) computeScore(candidate []byte, candidateLower []byte) int {
}
}
}

result := m.scores[len(candidate)][len(m.pattern)][m.bestK(len(candidate), len(m.pattern))].val()
result := m.scores[len(candidate)][matchPatterLen][m.bestK(len(candidate), matchPatterLen)].val()

return result
}
Expand Down
6 changes: 6 additions & 0 deletions gopls/internal/fuzzy/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ var scoreTestCases = []struct {
// We want the next two items to have roughly similar scores.
{p: "up", str: "unique_ptr", want: 0.75},
{p: "up", str: "upper_bound", want: 1},
// Pattern with some spelling errors.
{p: "tstincrementatlnope", str: "TestIncrementalNope", want: 0.61842},
{p: "tstincre", str: "TestIncrementalNope", want: 0.84375},
{p: "testssssss", str: "TestIncrementalNope", want: 0.4},
// Pattern longer than candidate.
{p: "foobarbaz", str: "foo", want: 0},
}

func TestScores(t *testing.T) {
Expand Down