Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

68 lines
2.2 KiB
Go

// Package fuzzy provides lightweight case-insensitive fuzzy subsequence
// matching with relevance scoring. It ranks in-memory search results (the
// playlist search and the file browser filter) without an external dependency:
// a greedy subsequence scan with a few positional bonuses is enough to rank
// track and file names, and stays small and allocation-light on the hot path
// of every keystroke.
package fuzzy
import "unicode"
// Scoring weights. In order of preference, a candidate ranks higher when the
// match starts at the very beginning of the string, follows a word boundary,
// or forms a run of consecutive matched characters.
const (
scoreMatch = 1 // base score for each matched rune
bonusFirstChar = 6 // matched rune is the first rune of the target
bonusBoundary = 4 // matched rune follows a word separator
bonusConsecutive = 4 // matched rune immediately follows the previous match
)
// isSeparator reports whether r delimits a word for boundary scoring.
func isSeparator(r rune) bool {
switch r {
case ' ', '-', '_', '/', '\\', '.', ':', '(', ')', '[', ']':
return true
}
return false
}
// Match reports whether query is a case-insensitive subsequence of target and,
// if so, a relevance score where higher is a better match. An empty query
// matches every target with score 0. ok is false when query is not a
// subsequence of target.
func Match(query, target string) (score int, ok bool) {
if query == "" {
return 0, true
}
q := []rune(query)
qi := 0
qlow := unicode.ToLower(q[0]) // current query rune, folded once per advance
ti := 0 // rune index within target
lastMatch := -2 // rune index of the previous matched rune
var prev rune // previous target rune, for boundary detection
for _, r := range target {
if unicode.ToLower(r) == qlow {
score += scoreMatch
switch {
case ti == 0:
score += bonusFirstChar
case isSeparator(prev):
score += bonusBoundary
}
if ti == lastMatch+1 {
score += bonusConsecutive
}
lastMatch = ti
qi++
if qi == len(q) {
return score, true
}
qlow = unicode.ToLower(q[qi])
}
prev = r
ti++
}
return 0, false
}