From 4e9f1d69c4f0951da73e21ca197ba858bbcf7481 Mon Sep 17 00:00:00 2001 From: Ksaper Date: Fri, 3 Mar 2023 04:17:06 +0200 Subject: [PATCH] Use desktop app info search instead --- widget/app_launcher/fzy.lua | 275 ----------------------------------- widget/app_launcher/init.lua | 67 +-------- 2 files changed, 8 insertions(+), 334 deletions(-) delete mode 100644 widget/app_launcher/fzy.lua diff --git a/widget/app_launcher/fzy.lua b/widget/app_launcher/fzy.lua deleted file mode 100644 index d80b543..0000000 --- a/widget/app_launcher/fzy.lua +++ /dev/null @@ -1,275 +0,0 @@ --- The lua implementation of the fzy string matching algorithm - -local SCORE_GAP_LEADING = -0.005 -local SCORE_GAP_TRAILING = -0.005 -local SCORE_GAP_INNER = -0.01 -local SCORE_MATCH_CONSECUTIVE = 1.0 -local SCORE_MATCH_SLASH = 0.9 -local SCORE_MATCH_WORD = 0.8 -local SCORE_MATCH_CAPITAL = 0.7 -local SCORE_MATCH_DOT = 0.6 -local SCORE_MAX = math.huge -local SCORE_MIN = -math.huge -local MATCH_MAX_LENGTH = 1024 - -local fzy = {} - --- Check if `needle` is a subsequence of the `haystack`. --- --- Usually called before `score` or `positions`. --- --- Args: --- needle (string) --- haystack (string) --- case_sensitive (bool, optional): defaults to false --- --- Returns: --- bool -function fzy.has_match(needle, haystack, case_sensitive) - if not case_sensitive then - needle = string.lower(needle) - haystack = string.lower(haystack) - end - - local j = 1 - for i = 1, string.len(needle) do - j = string.find(haystack, needle:sub(i, i), j, true) - if not j then - return false - else - j = j + 1 - end - end - - return true -end - -local function is_lower(c) - return c:match("%l") -end - -local function is_upper(c) - return c:match("%u") -end - -local function precompute_bonus(haystack) - local match_bonus = {} - - local last_char = "/" - for i = 1, string.len(haystack) do - local this_char = haystack:sub(i, i) - if last_char == "/" or last_char == "\\" then - match_bonus[i] = SCORE_MATCH_SLASH - elseif last_char == "-" or last_char == "_" or last_char == " " then - match_bonus[i] = SCORE_MATCH_WORD - elseif last_char == "." then - match_bonus[i] = SCORE_MATCH_DOT - elseif is_lower(last_char) and is_upper(this_char) then - match_bonus[i] = SCORE_MATCH_CAPITAL - else - match_bonus[i] = 0 - end - - last_char = this_char - end - - return match_bonus -end - -local function compute(needle, haystack, D, M, case_sensitive) - -- Note that the match bonuses must be computed before the arguments are - -- converted to lowercase, since there are bonuses for camelCase. - local match_bonus = precompute_bonus(haystack) - local n = string.len(needle) - local m = string.len(haystack) - - if not case_sensitive then - needle = string.lower(needle) - haystack = string.lower(haystack) - end - - -- Because lua only grants access to chars through substring extraction, - -- get all the characters from the haystack once now, to reuse below. - local haystack_chars = {} - for i = 1, m do - haystack_chars[i] = haystack:sub(i, i) - end - - for i = 1, n do - D[i] = {} - M[i] = {} - - local prev_score = SCORE_MIN - local gap_score = i == n and SCORE_GAP_TRAILING or SCORE_GAP_INNER - local needle_char = needle:sub(i, i) - - for j = 1, m do - if needle_char == haystack_chars[j] then - local score = SCORE_MIN - if i == 1 then - score = ((j - 1) * SCORE_GAP_LEADING) + match_bonus[j] - elseif j > 1 then - local a = M[i - 1][j - 1] + match_bonus[j] - local b = D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE - score = math.max(a, b) - end - D[i][j] = score - prev_score = math.max(score, prev_score + gap_score) - M[i][j] = prev_score - else - D[i][j] = SCORE_MIN - prev_score = prev_score + gap_score - M[i][j] = prev_score - end - end - end -end - --- Compute a matching score. --- --- Args: --- needle (string): must be a subequence of `haystack`, or the result is --- undefined. --- haystack (string) --- case_sensitive (bool, optional): defaults to false --- --- Returns: --- number: higher scores indicate better matches. See also `get_score_min` --- and `get_score_max`. -function fzy.score(needle, haystack, case_sensitive) - local n = string.len(needle) - local m = string.len(haystack) - - if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > m then - return SCORE_MIN - elseif n == m then - return SCORE_MAX - else - local D = {} - local M = {} - compute(needle, haystack, D, M, case_sensitive) - return M[n][m] - end -end - --- Compute the locations where fzy matches a string. --- --- Determine where each character of the `needle` is matched to the `haystack` --- in the optimal match. --- --- Args: --- needle (string): must be a subequence of `haystack`, or the result is --- undefined. --- haystack (string) --- case_sensitive (bool, optional): defaults to false --- --- Returns: --- {int,...}: indices, where `indices[n]` is the location of the `n`th --- character of `needle` in `haystack`. --- number: the same matching score returned by `score` -function fzy.positions(needle, haystack, case_sensitive) - local n = string.len(needle) - local m = string.len(haystack) - - if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > m then - return {}, SCORE_MIN - elseif n == m then - local consecutive = {} - for i = 1, n do - consecutive[i] = i - end - return consecutive, SCORE_MAX - end - - local D = {} - local M = {} - compute(needle, haystack, D, M, case_sensitive) - - local positions = {} - local match_required = false - local j = m - for i = n, 1, -1 do - while j >= 1 do - if D[i][j] ~= SCORE_MIN and (match_required or D[i][j] == M[i][j]) then - match_required = (i ~= 1) and (j ~= 1) and ( - M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE) - positions[i] = j - j = j - 1 - break - else - j = j - 1 - end - end - end - - return positions, M[n][m] -end - --- Apply `has_match` and `positions` to an array of haystacks. --- --- Args: --- needle (string) --- haystack ({string, ...}) --- case_sensitive (bool, optional): defaults to false --- --- Returns: --- {{idx, positions, score}, ...}: an array with one entry per matching line --- in `haystacks`, each entry giving the index of the line in `haystacks` --- as well as the equivalent to the return value of `positions` for that --- line. -function fzy.filter(needle, haystacks, case_sensitive) - local result = {} - - for i, line in ipairs(haystacks) do - if fzy.has_match(needle, line, case_sensitive) then - local p, s = fzy.positions(needle, line, case_sensitive) - table.insert(result, {i, p, s}) - end - end - - return result -end - --- The lowest value returned by `score`. --- --- In two special cases: --- - an empty `needle`, or --- - a `needle` or `haystack` larger than than `get_max_length`, --- the `score` function will return this exact value, which can be used as a --- sentinel. This is the lowest possible score. -function fzy.get_score_min() - return SCORE_MIN -end - --- The score returned for exact matches. This is the highest possible score. -function fzy.get_score_max() - return SCORE_MAX -end - --- The maximum size for which `fzy` will evaluate scores. -function fzy.get_max_length() - return MATCH_MAX_LENGTH -end - --- The minimum score returned for normal matches. --- --- For matches that don't return `get_score_min`, their score will be greater --- than than this value. -function fzy.get_score_floor() - return MATCH_MAX_LENGTH * SCORE_GAP_INNER -end - --- The maximum score for non-exact matches. --- --- For matches that don't return `get_score_max`, their score will be less than --- this value. -function fzy.get_score_ceiling() - return MATCH_MAX_LENGTH * SCORE_MATCH_CONSECUTIVE -end - --- The name of the currently-running implmenetation, "lua" or "native". -function fzy.get_implementation_name() - return "lua" -end - -return fzy \ No newline at end of file diff --git a/widget/app_launcher/init.lua b/widget/app_launcher/init.lua index 4cbd161..01e5b2c 100644 --- a/widget/app_launcher/init.lua +++ b/widget/app_launcher/init.lua @@ -6,7 +6,6 @@ local gtimer = require("gears.timer") local wibox = require("wibox") local beautiful = require("beautiful") local prompt_widget = require(... .. ".prompt") -local fzy = require(... .. ".fzy") local dpi = beautiful.xresources.apply_dpi local string = string local table = table @@ -33,47 +32,6 @@ local function default_value(value, default) end end -local function string_levenshtein(str1, str2) - local len1 = string.len(str1) - local len2 = string.len(str2) - local matrix = {} - local cost = 0 - - -- quick cut-offs to save time - if (len1 == 0) then - return len2 - elseif (len2 == 0) then - return len1 - elseif (str1 == str2) then - return 0 - end - - -- initialise the base matrix values - for i = 0, len1, 1 do - matrix[i] = {} - matrix[i][0] = i - end - for j = 0, len2, 1 do - matrix[0][j] = j - end - - -- actual Levenshtein algorithm - for i = 1, len1, 1 do - for j = 1, len2, 1 do - if (str1:byte(i) == str2:byte(j)) then - cost = 0 - else - cost = 1 - end - - matrix[i][j] = math.min(matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost) - end - end - - -- return the last value - this is the Levenshtein distance - return matrix[len1][len2] -end - local function has_value(tab, val) for _, value in ipairs(tab) do if val:lower():find(value:lower(), 1, true) then @@ -503,24 +461,16 @@ function app_launcher:search() if text == "" then self._private.matched_apps = self._private.all_apps else - for _, app in ipairs(self._private.all_apps) do - text = text:gsub( "%W", "" ) - - -- Filter with fzy - if fzy.has_match(text:lower(), app.name) or (self.search_commands and fzy.has_match(text:lower(), app.exec)) then - table.insert(self._private.matched_apps, app) + local matched_apps = Gio.DesktopAppInfo.search(text:lower()) + for _, matched_app in ipairs(matched_apps) do + for _, app_id in ipairs(matched_app) do + for _, app in ipairs(self._private.all_apps) do + if app.id == app_id then + table.insert(self._private.matched_apps, app) + end + end end end - - -- Sort by string similarity - table.sort(self._private.matched_apps, function(a, b) - if self.search_commands then - return string_levenshtein(text, a.name) + string_levenshtein(text, a.exec) < - string_levenshtein(text, b.name) + string_levenshtein(text, b.exec) - else - return string_levenshtein(text, a.name) < string_levenshtein(text, b.name) - end - end) end for _, app in ipairs(self._private.matched_apps) do -- Only add the widgets for apps that are part of the first page @@ -759,7 +709,6 @@ local function new(args) args.sort_fn = default_value(args.sort_fn, nil) args.favorites = default_value(args.favorites, {}) - args.search_commands = default_value(args.search_commands, true) args.skip_names = default_value(args.skip_names, {}) args.skip_commands = default_value(args.skip_commands, {}) args.skip_empty_icons = default_value(args.skip_empty_icons, false)