diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2025-07-29 19:29:24 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2025-07-29 19:29:35 +0800 |
| commit | 7be05c2696c3371caf29ac09dcc2022ccccb82fb (patch) | |
| tree | 83d87240452054c9dfe2625fabdd01d4d6173a1e /components | |
| parent | ba59c249436bedfea2302e9349fa392d9682f719 (diff) | |
perf(AppSearch): cacheing
Diffstat (limited to 'components')
| -rw-r--r-- | components/AppSearch.qml | 164 |
1 files changed, 112 insertions, 52 deletions
diff --git a/components/AppSearch.qml b/components/AppSearch.qml index 9336fc0..afab48f 100644 --- a/components/AppSearch.qml +++ b/components/AppSearch.qml @@ -12,8 +12,10 @@ import qs.config */ Singleton { id: root + property bool sloppySearch: General.sloppySearch ?? false property real scoreThreshold: 0.2 + property var substitutions: ({ "code-url-handler": "visual-studio-code", "Code": "visual-studio-code", @@ -25,25 +27,30 @@ Singleton { "zen": "zen-browser", "brave-browser": "brave-desktop" }) + property var regexSubstitutions: [ { - "regex": /^steam_app_(\d+)$/, - "replace": "steam_icon_$1" + regex: /^steam_app_(\d+)$/, + replace: "steam_icon_$1" }, { - "regex": /Minecraft.*/, - "replace": "minecraft" + regex: /Minecraft.*/, + replace: "minecraft" }, { - "regex": /.*polkit.*/, - "replace": "system-lock-screen" + regex: /.*polkit.*/, + replace: "system-lock-screen" }, { - "regex": /gcr.prompter/, - "replace": "system-lock-screen" + regex: /gcr.prompter/, + replace: "system-lock-screen" } ] + property var fuzzyQueryCache: ({}) + property var iconExistsCache: ({}) + property var guessIconCache: ({}) + readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values).sort((a, b) => a.name.localeCompare(b.name)) readonly property var preppedNames: list.map(a => ({ @@ -51,68 +58,121 @@ Singleton { entry: a })) - function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work + /** + * Returns an Array<DesktopEntry> for a given search. + * Results are cached by exact search string. + */ + function fuzzyQuery(search) { + if (root.fuzzyQueryCache[search]) { + return root.fuzzyQueryCache[search]; + } + + let results; if (root.sloppySearch) { - const results = list.map(obj => ({ + results = list.map(obj => ({ entry: obj, score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase()) - })).filter(item => item.score > root.scoreThreshold).sort((a, b) => b.score - a.score); - return results.map(item => item.entry); + })).filter(item => item.score > root.scoreThreshold).sort((a, b) => b.score - a.score).map(item => item.entry); + } else { + results = Fuzzy.go(search, preppedNames, { + all: true, + key: "name" + }).map(r => r.obj.entry); } - return Fuzzy.go(search, preppedNames, { - all: true, - key: "name" - }).map(r => { - return r.obj.entry; - }); + root.fuzzyQueryCache[search] = results; + return results; } + /** + * Caches whether an icon actually exists on disk. + */ function iconExists(iconName) { - if (!iconName || iconName.length == 0) + if (!iconName || iconName.length === 0) { return false; - return (Quickshell.iconPath(iconName, true).length > 0) && !iconName.includes("image-missing"); + } + if (root.iconExistsCache.hasOwnProperty(iconName)) { + return root.iconExistsCache[iconName]; + } + const exists = Quickshell.iconPath(iconName, true).length > 0 && !iconName.includes("image-missing"); + root.iconExistsCache[iconName] = exists; + return exists; } + /** + * Heavy logic to guess an icon name. We memoize results by the + * exact input string. + */ function guessIcon(str) { - if (!str || str.length == 0) + if (!str || str.length === 0) { return "image-missing"; + } + if (root.guessIconCache[str]) { + return root.guessIconCache[str]; + } - // Normal substitutions - if (substitutions[str]) - return substitutions[str]; + let result; + + // 1) direct substitution table + if (substitutions[str]) { + result = substitutions[str]; + } else + // 2) regex rules + { + result = null; + for (let i = 0; i < regexSubstitutions.length; i++) { + const { + regex, + replace + } = regexSubstitutions[i]; + const replaced = str.replace(regex, replace); + if (replaced !== str) { + result = replaced; + break; + } + } + } + + // 3) if still not found, see if the original exists + if (!result) { + if (root.iconExists(str)) { + result = str; + } + } + + // 4) try domain-reverse name + if (!result) { + let guessStr = str.split(".").slice(-1)[0].toLowerCase(); + if (root.iconExists(guessStr)) { + result = guessStr; + } + } + + // 5) kebab-case + if (!result) { + let guessStr = str.toLowerCase().replace(/\s+/g, "-"); + if (root.iconExists(guessStr)) { + result = guessStr; + } + } - // Regex substitutions - for (let i = 0; i < regexSubstitutions.length; i++) { - const substitution = regexSubstitutions[i]; - const replacedName = str.replace(substitution.regex, substitution.replace); - if (replacedName != str) - return replacedName; + // 6) fallback to first fuzzy match’s icon + if (!result) { + const matches = root.fuzzyQuery(str); + if (matches.length > 0) { + const candidate = matches[0].icon; + if (root.iconExists(candidate)) { + result = candidate; + } + } } - // If it gets detected normally, no need to guess - if (iconExists(str)) - return str; - - let guessStr = str; - // Guess: Take only app name of reverse domain name notation - guessStr = str.split('.').slice(-1)[0].toLowerCase(); - if (iconExists(guessStr)) - return guessStr; - // Guess: normalize to kebab case - guessStr = str.toLowerCase().replace(/\s+/g, "-"); - if (iconExists(guessStr)) - return guessStr; - // Guess: First fuzzy desktop entry match - const searchResults = root.fuzzyQuery(str); - if (searchResults.length > 0) { - const firstEntry = searchResults[0]; - guessStr = firstEntry.icon; - if (iconExists(guessStr)) - return guessStr; + // 7) give up + if (!result) { + result = str; } - // Give up - return str; + root.guessIconCache[str] = result; + return result; } } |
