add fuzzy score

This commit is contained in:
neveix 2026-08-31 00:53:45 +05:00
parent 02f0e20010
commit 1e69b6df13
4 changed files with 92 additions and 59 deletions

View file

@ -1,5 +1,7 @@
local search_utils = require("search_utils") local search_utils = require("search_utils")
local score_func = search_utils.fuzzy_score
function on_open(params) function on_open(params)
if params then if params then
mode = params.mode mode = params.mode
@ -65,6 +67,18 @@ function reposition_func(_pack)
return pos[1], pos[2] return pos[1], pos[2]
end end
local function update_pack_id_title(packinfo)
local id_el = document["pack_"..packinfo.id.."_id"]
local title_el = document["pack_"..packinfo.id.."_title"]
local id_md = packinfo.id_md or packinfo.id
if packinfo.has_indices then
id_md = id_md .. "\\*"
end
id_el.text = string.format("[%s %s]", id_md, packinfo.version)
title_el.text = packinfo.title_md or packinfo.title
end
function refresh_search() function refresh_search()
local search_text = document.search_textbox.text:lower() local search_text = document.search_textbox.text:lower()
@ -73,22 +87,27 @@ function refresh_search()
local new_excluded = table.copy(packs_excluded) local new_excluded = table.copy(packs_excluded)
local smart_score_cache = {} local smart_score_cache = {}
local colored_ids = {}
local colored_titles = {}
local function smart_fuzzy_score(id) local function smart_score(id)
if smart_score_cache[id] then if smart_score_cache[id] then
return smart_score_cache[id] return smart_score_cache[id]
end end
local res = math.max( local id_score, colored_id = score_func(id, search_text, true)
search_utils.fuzzy_score(id, search_text), local title_score, colored_title = score_func(
search_utils.fuzzy_score(packs_info[id].title, search_text) packs_info[id].title, search_text, true)
) local res = math.max(id_score, title_score)
smart_score_cache[id] = res smart_score_cache[id] = res
colored_ids[id] = colored_id
colored_titles[id] = colored_title
return res return res
end end
local function cmp(a, b) local function cmp(a, b)
local score_a = smart_fuzzy_score(a) local score_a = smart_score(a)
local score_b = smart_fuzzy_score(b) local score_b = smart_score(b)
if score_a ~= score_b then if score_a ~= score_b then
return score_a > score_b return score_a > score_b
@ -106,6 +125,10 @@ function refresh_search()
for _, id in ipairs(table.merge(table.copy(packs_included), packs_excluded)) do for _, id in ipairs(table.merge(table.copy(packs_included), packs_excluded)) do
local content = document["pack_" .. id] local content = document["pack_" .. id]
content:reposition() content:reposition()
local packinfo = pack.get_info(id)
packinfo.id_md = colored_ids[id]
packinfo.title_md = colored_titles[id]
update_pack_id_title(packinfo)
end end
end end
@ -174,21 +197,20 @@ function move_right()
refresh_changes() refresh_changes()
end end
function place_pack(panel, packinfo, callback, position_func) function place_pack(panel, packinfo, callback, position_func)
if packinfo.error then if packinfo.error then
callback = nil callback = nil
end end
if packinfo.has_indices then packinfo.id_verbose = ""
packinfo.id_verbose = packinfo.id.."*"
else
packinfo.id_verbose = packinfo.id
end
packinfo.callback = callback packinfo.callback = callback
packinfo.position_func = position_func or function () end packinfo.position_func = position_func or function () end
panel:add(gui.template("pack", packinfo)) panel:add(gui.template("pack", packinfo))
if not callback then if not callback then
document["pack_"..packinfo.id].enabled = false document["pack_"..packinfo.id].enabled = false
end end
update_pack_id_title(packinfo)
end end
local Version = {} local Version = {}

View file

@ -1,7 +1,6 @@
local search_utils = require("search_utils") local search_utils = require("search_utils")
local MATCHED_COLOR = "[#4FC3F7]" local score_func = search_utils.fuzzy_score
local NORMAL_COLOR = "[#FFFFFF]"
local packs_installed = {} local packs_installed = {}
local pack_open = {} local pack_open = {}
@ -19,18 +18,28 @@ function on_open()
document.search_textbox.focused = true document.search_textbox.focused = true
end end
local function update_pack_id_title(packinfo)
local id_el = document["pack_"..packinfo.id.."_id"]
local title_el = document["pack_"..packinfo.id.."_title"]
local id_md = packinfo.id_md or packinfo.id
if packinfo.has_indices then
id_md = id_md .. "\\*"
end
id_el.text = string.format("[%s %s]", id_md, packinfo.version)
title_el.text = packinfo.title_md or packinfo.title
end
function place_pack(panel, packinfo, callback) function place_pack(panel, packinfo, callback)
if packinfo.error then if packinfo.error then
callback = nil callback = nil
end end
local id_md = packinfo.id_md or packinfo.id packinfo.id_verbose = ""
if packinfo.has_indices then
packinfo.id_verbose = id_md.."\\*"
else
packinfo.id_verbose = id_md
end
packinfo.callback = callback packinfo.callback = callback
panel:add(gui.template("pack", packinfo)) panel:add(gui.template("pack", packinfo))
update_pack_id_title(packinfo)
end end
function refresh_search() function refresh_search()
@ -46,14 +55,12 @@ function refresh_search()
local colored_ids = {} local colored_ids = {}
local colored_titles = {} local colored_titles = {}
local function smart_fuzzy_score(a) local function smart_score(a)
if smart_score_cache[a.id] then if smart_score_cache[a.id] then
return smart_score_cache[a.id] return smart_score_cache[a.id]
end end
local id_score, colored_id = search_utils.fuzzy_score(a.id, search_text, local id_score, colored_id = score_func(a.id, search_text, true)
MATCHED_COLOR, NORMAL_COLOR) local title_score, colored_title = score_func(a.title, search_text, true)
local title_score, colored_title = search_utils.fuzzy_score(a.title, search_text,
MATCHED_COLOR, NORMAL_COLOR)
local res = math.max(id_score, title_score) local res = math.max(id_score, title_score)
smart_score_cache[a.id] = res smart_score_cache[a.id] = res
@ -67,8 +74,8 @@ function refresh_search()
end end
local function cmp(a, b) local function cmp(a, b)
local score_a = smart_fuzzy_score(a) local score_a = smart_score(a)
local score_b = smart_fuzzy_score(b) local score_b = smart_score(b)
if score_a == score_b then if score_a == score_b then
return a.title > b.title return a.title > b.title
else else
@ -78,9 +85,6 @@ function refresh_search()
table.sort(packs, cmp) table.sort(packs, cmp)
local contents = document.contents
contents:clear()
for i, v in ipairs(packs) do for i, v in ipairs(packs) do
local id = v.id local id = v.id
@ -89,10 +93,9 @@ function refresh_search()
packinfo.id_md = colored_ids[id] packinfo.id_md = colored_ids[id]
end end
if colored_titles[id] then if colored_titles[id] then
packinfo.title = colored_titles[id] packinfo.title_md = colored_titles[id]
end end
update_pack_id_title(packinfo)
place_pack(contents, packinfo, callback)
local content = document["pack_" .. id] local content = document["pack_" .. id]
local pos = content.pos local pos = content.pos
@ -154,7 +157,7 @@ end
local function create_config(i, config, name, path) local function create_config(i, config, name, path)
create_label(name, ('**[' .. name .. ']**'):upper(), "#FFFFFF") create_label(name, ('**[' .. name .. ']**'):upper(), "#FFFFFF")
pack_open[2][i] = {elements = {}, path = path} pack_open[2][i] = { elements = {}, path = path }
for _, a in ipairs(config.checkboxes) do for _, a in ipairs(config.checkboxes) do
create_checkbox(i .. '_' .. a[1], a[1], a[2]) create_checkbox(i .. '_' .. a[1], a[1], a[2])
pack_open[2][i]["elements"][a[1]] = type(a[2]) pack_open[2][i]["elements"][a[1]] = type(a[2])
@ -203,7 +206,6 @@ local function has_valid_config(id)
end end
local function load_config_file(path) local function load_config_file(path)
local extension = path:match("^.+%.(.+)$") local extension = path:match("^.+%.(.+)$")
local name = path:match("([^/]+)%.([^%.]+)$") local name = path:match("([^/]+)%.([^%.]+)$")
@ -220,10 +222,11 @@ end
local function load_config(path) local function load_config(path)
if file.exists(path) then if file.exists(path) then
local value = load_config_file(path) local value = load_config_file(path)
local config = {checkboxes = {}, local config = {
trackbars = {}, checkboxes = {},
textboxes = {} trackbars = {},
} textboxes = {}
}
if not value then if not value then
return return
@ -231,11 +234,11 @@ local function load_config(path)
for i, v in pairs(value) do for i, v in pairs(value) do
if type(v) == "string" then if type(v) == "string" then
table.insert(config.textboxes, {i, v}) table.insert(config.textboxes, { i, v })
elseif type(v) == "boolean" then elseif type(v) == "boolean" then
table.insert(config.checkboxes, {i, v}) table.insert(config.checkboxes, { i, v })
elseif type(v) == "number" then elseif type(v) == "number" then
table.insert(config.trackbars, {i, v}) table.insert(config.trackbars, { i, v })
end end
end end
@ -275,16 +278,18 @@ end
function open_pack(id) function open_pack(id)
local packinfo = pack.get_info(id) local packinfo = pack.get_info(id)
pack_open = {id, {}} pack_open = { id, {} }
document.configs.visible = false document.configs.visible = false
document.content_info.visible = true document.content_info.visible = true
if has_valid_config(id) then document.open_config.enabled = true else document.open_config.enabled = false end if has_valid_config(id) then document.open_config.enabled = true else document.open_config.enabled = false end
if packinfo['dependencies'] == nil then document.dependencies.text = 'None' else document.dependencies.text = table.tostring(packinfo['dependencies']) end if packinfo['dependencies'] == nil then document.dependencies.text = 'None' else document.dependencies.text = table
.tostring(packinfo['dependencies']) end
if packinfo['creator'] == '' then document.author.text = 'None' else document.author.text = packinfo['creator'] end if packinfo['creator'] == '' then document.author.text = 'None' else document.author.text = packinfo['creator'] end
if packinfo['version'] == nil then document.version.text = 'None' else document.version.text = packinfo['version'] end if packinfo['version'] == nil then document.version.text = 'None' else document.version.text = packinfo['version'] end
if packinfo['description'] == nil then document.description.text = 'None' else document.description.text = packinfo['description'] end if packinfo['description'] == nil then document.description.text = 'None' else document.description.text = packinfo
['description'] end
end end
function open_config() function open_config()

View file

@ -1,8 +1,10 @@
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}"> <container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}">
<label color='#FFFFFF80' size='340,25' align='right' gravity='top-right' markup='md'> <label id='pack_%{id}_id' color='#FFFFFF80' size='340,25' align='right' gravity='top-right' markup='md'>
[%{id_verbose} %{version}] [%{id_verbose} %{version}]
</label>
<label id='pack_%{id}_title' pos='78,6' markup='md'>
%{title}
</label> </label>
<label pos='78,6' markup='md'>%{title}</label>
<label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right' <label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right'
gravity='bottom-right'> gravity='bottom-right'>
%{creator} %{creator}

View file

@ -1,18 +1,22 @@
local M = {} local M = {}
-- Оценивает схожесть строк и возвращает совпадение (>= 0) и если переданы M.MATCHED_COLOR_DEFAULT = "[#4FC3F7]"
-- параметры color_matched и color_normal, возвращает также исходную строку, M.NORMAL_COLOR_DEFAULT = "[#FFFFFF]"
-- в которой совпадающие части обрамлены в color_matched и color_normal
---@param sample str где ищем совпадения ---Calculate fuzzy score
---@param pattern str какие совпадения ищем ---@param sample str
---@param color_matched? str ---@param pattern str
---@param color_normal? str ---@param do_coloring? bool if passed, then function will also return colored_sample, where matched
---@return int match ---characters batched will be wrapped in color_matched and color_normal strings
---@param color_matched? str if not passed, uses the default M.MATCHED_COLOR_DEFAULT
---@param color_normal? str if not passed, uses the default M.NORMAL_COLOR_DEFAULT
---@return int match pattern match intensity, 0 for absence
---@return str? colored_sample ---@return str? colored_sample
function M.fuzzy_score(sample, pattern, color_matched, color_normal) function M.fuzzy_score(sample, pattern, do_coloring, color_matched, color_normal)
if pattern == "" then return 0 end if pattern == "" then return 0 end
local do_coloring = color_matched and color_normal color_matched = color_matched or M.MATCHED_COLOR_DEFAULT
color_normal = color_normal or M.NORMAL_COLOR_DEFAULT
local colored_sample local colored_sample
if do_coloring then if do_coloring then
colored_sample = "" colored_sample = ""