add fuzzy search for packs

This commit is contained in:
neveix 2026-08-30 01:02:13 +05:00
parent 25d2d03de4
commit 2e55380f2c
3 changed files with 126 additions and 12 deletions

View file

@ -1,3 +1,5 @@
local search_utils = require("search_utils")
function on_open(params)
if params then
mode = params.mode
@ -67,16 +69,23 @@ function refresh_search()
local new_included = table.copy(packs_included)
local new_excluded = table.copy(packs_excluded)
local function score(pack_id, pack_name)
if pack_name:lower():find(search_text) or pack_id:lower():find(search_text) then
return 1
local smart_score_cache = {}
local function smart_fuzzy_score(id)
if smart_score_cache[id] then
return smart_score_cache[id]
end
return 0
local res = math.max(
search_utils.fuzzy_score(id, search_text),
search_utils.fuzzy_score(packs_info[id][2], search_text)
)
smart_score_cache[id] = res
return res
end
local function sorting(a, b)
local score_a = score(a, packs_info[a][2])
local score_b = score(b, packs_info[b][2])
local function cmp(a, b)
local score_a = smart_fuzzy_score(a)
local score_b = smart_fuzzy_score(b)
if score_a ~= score_b then
return score_a > score_b
@ -85,8 +94,8 @@ function refresh_search()
end
end
table.sort(new_included, sorting)
table.sort(new_excluded, sorting)
table.sort(new_included, cmp)
table.sort(new_excluded, cmp)
packs_included = new_included
packs_excluded = new_excluded

View file

@ -1,3 +1,5 @@
local search_utils = require("search_utils")
local packs_installed = {}
local pack_open = {}
local PARSERS = {
@ -33,14 +35,45 @@ function refresh_search()
local interval = 4
local step = -1
for i, v in ipairs(packs_installed) do
local packs = table.copy(packs_installed)
local score_non_zero_num = 0
local smart_score_cache = {}
local function smart_fuzzy_score(a)
if smart_score_cache[a[1]] then
return smart_score_cache[a[1]]
end
local res = math.max(
search_utils.fuzzy_score(a[1], search_text),
search_utils.fuzzy_score(a[2], search_text)
)
smart_score_cache[a[1]] = res
if res > 0 then
score_non_zero_num = score_non_zero_num + 1
end
return res
end
local function cmp(a, b)
local score_a = smart_fuzzy_score(a)
local score_b = smart_fuzzy_score(b)
if score_a == score_b then
return a[2] > b[2]
else
return score_a > score_b
end
end
table.sort(packs, cmp)
for i, v in ipairs(packs) do
local id = v[1]
local title = v[2]
local content = document["pack_" .. id]
local pos = content.pos
local size = content.size
if title:lower():find(search_text) or id:lower():find(search_text) or search_text == '' then
if i <= score_non_zero_num then
content.enabled = true
content.pos = {pos[1], visible * (size[2] + interval) - step}
visible = visible + 1

View file

@ -0,0 +1,72 @@
local M = {}
-- Оценивает схожесть строк и возвращает целое число,
-- чем оно больше, тем больше совпадение
function M.fuzzy_score(sample, pattern)
if pattern == "" then return 0 end
local sample_lower = string.lower(sample)
local pattern_lower = string.lower(pattern)
local sample_len = #sample_lower
local pattern_len = #pattern_lower
if pattern_len > sample_len then return 0 end
local pattern_idx = 1
local consecutive = 0
local max_consecutive = 0
local matched_chars = 0
local total_distance = 0
local last_match_pos = -1
for i = 1, sample_len do
if pattern_idx <= pattern_len and
sample_lower:sub(i, i) == pattern_lower:sub(pattern_idx, pattern_idx) then
matched_chars = matched_chars + 1
consecutive = consecutive + 1
if consecutive > max_consecutive then
max_consecutive = consecutive
end
if last_match_pos ~= -1 then
total_distance = total_distance + (i - last_match_pos - 1)
end
last_match_pos = i
pattern_idx = pattern_idx + 1
else
consecutive = 0
end
end
if pattern_idx <= pattern_len then
return 0
end
local coverage = matched_chars / pattern_len * 20
local consecutive_bonus = max_consecutive * 10
local start_bonus = 0
if sample_lower:sub(1, 1) == pattern_lower:sub(1, 1) then
start_bonus = 15
end
local prefix_bonus = 0
if sample_lower:find(pattern_lower, 1, true) == 1 then
prefix_bonus = 25
end
local distance_penalty = total_distance * 2
local score = coverage + consecutive_bonus + start_bonus + prefix_bonus - distance_penalty
if sample_lower:find(pattern_lower, 1, true) then
score = score + 30
end
return math.max(0, score)
end
return M