diff --git a/scripts/uosc/elements/Menu.lua b/scripts/uosc/elements/Menu.lua index dd26f8c5..ebd0d436 100644 --- a/scripts/uosc/elements/Menu.lua +++ b/scripts/uosc/elements/Menu.lua @@ -645,15 +645,50 @@ function Menu:search_update_items() self:reset_navigation() end +local function substring_hamming_distance(text, p, p_n) + local best_score, t, t_n = p_n, utf8_chars(text) + for i = 1, t_n - p_n + 1 do + local score = 0 + for j = 1, p_n do + if t[i + j - 1] ~= p[j] then + score = score + 1 + end + end + if score < best_score then + best_score = score + end + end + return best_score +end + +local function fuzzy_search(items, query) + local q, q_n = utf8_chars(query) + + local t, n, threshold = {}, 1, q_n / 3 + 1e-9 + for _, item in ipairs(items) do + local title = item.title and item.title:lower() + local hint = item.hint and item.hint:lower() + local td = title and substring_hamming_distance(title, q, q_n) or q_n + local hd = hint and substring_hamming_distance(hint, q, q_n) or q_n + local tcd = title and substring_hamming_distance(table.concat(first_word_chars(title)), q, q_n) or q_n + local hcd = hint and substring_hamming_distance(table.concat(first_word_chars(hint)), q, q_n) or q_n + local distance = math.min(td, hd, tcd, hcd) + if distance <= threshold then + t[n] = { distance, n, item } + n = n + 1 + end + end + table.sort(t, function(a, b) return a[1] == b[1] and a[2] < b[2] or a[1] < b[1] end) + for i, e in ipairs(t) do + t[i] = e[3] + end + return t +end + ---@param menu MenuStack function Menu:search_internal(menu) local query = menu.search.query:lower() - menu.items = query ~= '' and itable_filter(menu.search.source.items, function(item) - local title = item.title and item.title:lower() or '' - local hint = item.hint and item.hint:lower() or '' - return title:find(query, 1, true) or hint:find(query, 1, true) or - (table.concat(first_word_chars(title)) .. table.concat(first_word_chars(hint))):find(query, 1, true) - end) or menu.search.source.items + menu.items = query ~= '' and fuzzy_search(menu.search.source.items, query) or menu.search.source.items self:search_update_items() end diff --git a/scripts/uosc/lib/text.lua b/scripts/uosc/lib/text.lua index 72f084a9..c106ab10 100644 --- a/scripts/uosc/lib/text.lua +++ b/scripts/uosc/lib/text.lua @@ -74,6 +74,15 @@ local function utf8_iter(str) end end +function utf8_chars(s) + local t, i = {}, 0 + for _, c in utf8_iter(s) do + i = i + 1 + t[i] = c + end + return t, i +end + ---Extract Unicode code point from utf-8 character at index i in str ---@param str string ---@param i integer