feat(icons): improve search ranking with priority-based matching

Restructure icon search to use a tiered ranking system:
- Exact matches get highest priority (rank 0)
- Prefix matches ranked by name length (rank 100+)
- Contains matches ranked by relevance (rank 500+)
- Fuzzy matches as fallback (rank 1000+)

Also refactors InitCache to use switch statements for clarity
and updates goutils submodule.
This commit is contained in:
yusing
2026-02-18 18:13:34 +08:00
parent 115fba4ff4
commit add7884a36
2 changed files with 26 additions and 12 deletions

View File

@@ -56,7 +56,8 @@ func init() {
func InitCache() {
m := make(IconMap)
err := serialization.LoadFileIfExist(common.IconListCachePath, &m, sonic.Unmarshal)
if err != nil {
switch {
case err != nil:
// backward compatible
oldFormat := struct {
Icons IconMap
@@ -70,11 +71,11 @@ func InitCache() {
// store it to disk immediately
_ = serialization.SaveFile(common.IconListCachePath, &m, 0o644, sonic.Marshal)
}
} else if len(m) > 0 {
case len(m) > 0:
log.Info().
Int("icons", len(m)).
Msg("icons loaded")
} else {
default:
if err := updateIcons(m); err != nil {
log.Error().Err(err).Msg("failed to update icons")
}
@@ -142,33 +143,46 @@ func SearchIcons(keyword string, limit int) []*IconMetaSearch {
return a.rank - b.rank
}
var rank int
dashedKeyword := strings.ReplaceAll(keyword, " ", "-")
whitespacedKeyword := strings.ReplaceAll(keyword, "-", " ")
icons := ListAvailableIcons()
for k, icon := range icons {
if strutils.ContainsFold(string(k), keyword) || strutils.ContainsFold(icon.DisplayName, keyword) {
source, ref := k.SourceRef()
var rank int
switch {
case strings.EqualFold(ref, dashedKeyword):
// exact match: best rank, use source as tiebreaker (lower index = higher priority)
rank = 0
} else {
rank = fuzzy.RankMatchFold(keyword, string(k))
case strutils.HasPrefixFold(ref, dashedKeyword):
// prefix match: rank by how much extra the name has (shorter = better)
rank = 100 + len(ref) - len(dashedKeyword)
case strutils.ContainsFold(ref, dashedKeyword) || strutils.ContainsFold(icon.DisplayName, whitespacedKeyword):
// contains match
rank = 500 + len(ref) - len(dashedKeyword)
default:
rank = fuzzy.RankMatchFold(keyword, ref)
if rank == -1 || rank > 3 {
continue
}
rank += 1000
}
source, ref := k.SourceRef()
ranked := &IconMetaSearch{
Source: source,
Ref: ref,
Meta: icon,
rank: rank,
}
// Sorted insert based on rank (lower rank = better match)
insertPos, _ := slices.BinarySearchFunc(results, ranked, sortByRank)
results = slices.Insert(results, insertPos, ranked)
results = append(results, ranked)
if len(results) == searchLimit {
break
}
}
slices.SortStableFunc(results, sortByRank)
// Extract results and limit to the requested count
return results[:min(len(results), limit)]
}