From 2e55380f2ccf918358fed63a3b0d8af564e4af55 Mon Sep 17 00:00:00 2001 From: neveix Date: Sun, 30 Aug 2026 01:02:13 +0500 Subject: [PATCH 1/4] add fuzzy search for packs --- res/layouts/pages/content.xml.lua | 27 ++++++---- res/layouts/pages/content_menu.xml.lua | 39 ++++++++++++-- res/modules/search_utils.lua | 72 ++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 res/modules/search_utils.lua diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 302f60644..f770c1649 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -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 diff --git a/res/layouts/pages/content_menu.xml.lua b/res/layouts/pages/content_menu.xml.lua index 6eb7e779b..852e05fe2 100644 --- a/res/layouts/pages/content_menu.xml.lua +++ b/res/layouts/pages/content_menu.xml.lua @@ -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 diff --git a/res/modules/search_utils.lua b/res/modules/search_utils.lua new file mode 100644 index 000000000..cc72ad6ff --- /dev/null +++ b/res/modules/search_utils.lua @@ -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 From ccc11232bbc59ee35af3cba72231f376a2e583ca Mon Sep 17 00:00:00 2001 From: neveix Date: Sun, 30 Aug 2026 01:20:34 +0500 Subject: [PATCH 2/4] small refactor of packs_info --- res/layouts/pages/content.xml.lua | 19 ++++++++++++++----- res/layouts/pages/content_menu.xml.lua | 19 +++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index f770c1649..4c60bf7fb 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -52,7 +52,10 @@ function reposition_func(_pack) else tbl = packs_excluded local packinfo = pack.get_info(_pack) - packinfo[packinfo.id] = {packinfo.id, packinfo.title} + packinfo[packinfo.id] = { + id = packinfo.id, + title = packinfo.title + } table.insert(packs_excluded, packinfo.id) end @@ -77,7 +80,7 @@ function refresh_search() end local res = math.max( search_utils.fuzzy_score(id, search_text), - search_utils.fuzzy_score(packs_info[id][2], search_text) + search_utils.fuzzy_score(packs_info[id].title, search_text) ) smart_score_cache[id] = res return res @@ -90,7 +93,7 @@ function refresh_search() if score_a ~= score_b then return score_a > score_b else - return packs_info[a][2] < packs_info[b][2] + return packs_info[a].title < packs_info[b].title end end @@ -350,12 +353,18 @@ function refresh() for _,id in ipairs(base_packs) do local packinfo = pack.get_info(id) - packs_info[id] = {packinfo.id, packinfo.title} + packs_info[id] = { + id = packinfo.id, + title = packinfo.title + } end for _,id in ipairs(packs_all) do local packinfo = pack.get_info(id) - packs_info[id] = {packinfo.id, packinfo.title} + packs_info[id] = { + id = packinfo.id, + title = packinfo.title + } end if #packs_excluded == 0 then packs_excluded = table.copy(packs_available) end diff --git a/res/layouts/pages/content_menu.xml.lua b/res/layouts/pages/content_menu.xml.lua index 852e05fe2..f910a4a74 100644 --- a/res/layouts/pages/content_menu.xml.lua +++ b/res/layouts/pages/content_menu.xml.lua @@ -41,14 +41,14 @@ function refresh_search() local smart_score_cache = {} local function smart_fuzzy_score(a) - if smart_score_cache[a[1]] then - return smart_score_cache[a[1]] + if smart_score_cache[a.id] then + return smart_score_cache[a.id] end local res = math.max( - search_utils.fuzzy_score(a[1], search_text), - search_utils.fuzzy_score(a[2], search_text) + search_utils.fuzzy_score(a.id, search_text), + search_utils.fuzzy_score(a.title, search_text) ) - smart_score_cache[a[1]] = res + smart_score_cache[a.id] = res if res > 0 then score_non_zero_num = score_non_zero_num + 1 end @@ -59,7 +59,7 @@ function refresh_search() 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] + return a.title > b.title else return score_a > score_b end @@ -68,7 +68,7 @@ function refresh_search() table.sort(packs, cmp) for i, v in ipairs(packs) do - local id = v[1] + local id = v.id local content = document["pack_" .. id] local pos = content.pos local size = content.size @@ -304,7 +304,10 @@ function refresh() local packinfo = packinfos[id] packinfo.id = id - packs_installed[i] = {packinfo.id, packinfo.title} + packs_installed[i] = { + id = packinfo.id, + title = packinfo.title + } local callback = string.format('open_pack("%s")', id) place_pack(contents, packinfo, callback) end From 02f0e200103bd4b15ba2e1b6f031e558fb34bceb Mon Sep 17 00:00:00 2001 From: neveix Date: Sun, 30 Aug 2026 22:11:08 +0500 Subject: [PATCH 3/4] add coloring for content_menu.xml.lua --- res/layouts/pages/content_menu.xml.lua | 48 +++++++++++++++++++------- res/layouts/templates/pack.xml | 4 +-- res/modules/search_utils.lua | 38 ++++++++++++++++---- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/res/layouts/pages/content_menu.xml.lua b/res/layouts/pages/content_menu.xml.lua index f910a4a74..e029380e5 100644 --- a/res/layouts/pages/content_menu.xml.lua +++ b/res/layouts/pages/content_menu.xml.lua @@ -1,5 +1,8 @@ local search_utils = require("search_utils") +local MATCHED_COLOR = "[#4FC3F7]" +local NORMAL_COLOR = "[#FFFFFF]" + local packs_installed = {} local pack_open = {} local PARSERS = { @@ -20,10 +23,11 @@ function place_pack(panel, packinfo, callback) if packinfo.error then callback = nil end + local id_md = packinfo.id_md or packinfo.id if packinfo.has_indices then - packinfo.id_verbose = packinfo.id.."*" + packinfo.id_verbose = id_md.."\\*" else - packinfo.id_verbose = packinfo.id + packinfo.id_verbose = id_md end packinfo.callback = callback panel:add(gui.template("pack", packinfo)) @@ -39,16 +43,23 @@ function refresh_search() local score_non_zero_num = 0 local smart_score_cache = {} + local colored_ids = {} + local colored_titles = {} local function smart_fuzzy_score(a) if smart_score_cache[a.id] then return smart_score_cache[a.id] end - local res = math.max( - search_utils.fuzzy_score(a.id, search_text), - search_utils.fuzzy_score(a.title, search_text) - ) + local id_score, colored_id = search_utils.fuzzy_score(a.id, search_text, + MATCHED_COLOR, NORMAL_COLOR) + 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) + smart_score_cache[a.id] = res + colored_ids[a.id] = colored_id + colored_titles[a.id] = colored_title + if res > 0 then score_non_zero_num = score_non_zero_num + 1 end @@ -67,19 +78,33 @@ function refresh_search() table.sort(packs, cmp) + local contents = document.contents + contents:clear() + for i, v in ipairs(packs) do local id = v.id + + local packinfo = table.copy(packs[i]) + if colored_ids[id] then + packinfo.id_md = colored_ids[id] + end + if colored_titles[id] then + packinfo.title = colored_titles[id] + end + + place_pack(contents, packinfo, callback) + local content = document["pack_" .. id] local pos = content.pos local size = content.size if i <= score_non_zero_num then content.enabled = true - content.pos = {pos[1], visible * (size[2] + interval) - step} + content.pos = { pos[1], visible * (size[2] + interval) - step } visible = visible + 1 else content.enabled = false - content.pos = {pos[1], (visible + #packs_installed - i) * (size[2] + interval) - step} + content.pos = { pos[1], (visible + #packs_installed - i) * (size[2] + interval) - step } end end end @@ -302,12 +327,9 @@ function refresh() local packinfos = pack.get_info(packs_installed) for i, id in ipairs(packs_installed) do local packinfo = packinfos[id] - packinfo.id = id - packs_installed[i] = { - id = packinfo.id, - title = packinfo.title - } + packs_installed[i] = packinfo + local callback = string.format('open_pack("%s")', id) place_pack(contents, packinfo, callback) end diff --git a/res/layouts/templates/pack.xml b/res/layouts/templates/pack.xml index d4e721d0c..3ad6c641c 100644 --- a/res/layouts/templates/pack.xml +++ b/res/layouts/templates/pack.xml @@ -1,8 +1,8 @@ -