feat: fzf-like search instead of fuzzy

Also add license for fuzzysort lib
This commit is contained in:
2 * r + 2 * t 2025-07-19 14:25:39 +10:00
parent 4320904a39
commit ce26c8a754
8 changed files with 1402 additions and 67 deletions

View file

@ -1,18 +1,17 @@
pragma Singleton pragma Singleton
import "root:/utils/scripts/fuzzysort.js" as Fuzzy
import qs.services import qs.services
import qs.config import qs.config
import qs.utils
import Quickshell import Quickshell
import Quickshell.Io
import QtQuick import QtQuick
Singleton { Searcher {
id: root id: root
property string qalcResult property string qalcResult
readonly property list<Action> list: [ readonly property list<Action> actions: [
Action { Action {
name: qsTr("Calculator") name: qsTr("Calculator")
desc: qsTr("Do simple math equations (powered by Qalc)") desc: qsTr("Do simple math equations (powered by Qalc)")
@ -134,24 +133,16 @@ Singleton {
} }
] ]
readonly property list<var> preppedActions: list.filter(a => !a.disabled).map(a => ({ function transformSearch(search: string): string {
name: Fuzzy.prepare(a.name), return search.slice(Config.launcher.actionPrefix.length);
desc: Fuzzy.prepare(a.desc),
action: a
}))
function fuzzyQuery(search: string): var {
return Fuzzy.go(search.slice(Config.launcher.actionPrefix.length), preppedActions, {
all: true,
keys: ["name", "desc"],
scoreFn: r => r[0].score > 0 ? r[0].score * 0.9 + r[1].score * 0.1 : 0
}).map(r => r.obj.action);
} }
function autocomplete(list: AppList, text: string): void { function autocomplete(list: AppList, text: string): void {
list.search.text = `${Config.launcher.actionPrefix}${text} `; list.search.text = `${Config.launcher.actionPrefix}${text} `;
} }
list: actions.filter(a => !a.disabled)
component Action: QtObject { component Action: QtObject {
required property string name required property string name
required property string desc required property string desc

View file

@ -23,14 +23,14 @@ StyledListView {
if (isCalc) if (isCalc)
return [0]; return [0];
if (isScheme) if (isScheme)
return Schemes.fuzzyQuery(text); return Schemes.query(text);
if (isVariant) if (isVariant)
return M3Variants.fuzzyQuery(text); return M3Variants.query(text);
if (isAction) if (isAction)
return Actions.fuzzyQuery(text); return Actions.query(text);
if (text.startsWith(Config.launcher.actionPrefix)) if (text.startsWith(Config.launcher.actionPrefix))
text = search.text.slice(Config.launcher.actionPrefix.length); text = search.text.slice(Config.launcher.actionPrefix.length);
return Apps.fuzzyQuery(text); return Apps.query(text);
} }
model: ScriptModel { model: ScriptModel {

View file

@ -1,14 +1,17 @@
pragma Singleton pragma Singleton
import "root:/utils/scripts/fuzzysort.js" as Fuzzy import qs.utils
import qs.config
import Quickshell import Quickshell
import QtQuick import QtQuick
Singleton { Searcher {
id: root id: root
readonly property list<Variant> list: [ function transformSearch(search: string): var {
return search.slice(`${Config.launcher.actionPrefix}variant `.length);
}
list: [
Variant { Variant {
variant: "vibrant" variant: "vibrant"
icon: "sentiment_very_dissatisfied" icon: "sentiment_very_dissatisfied"
@ -65,18 +68,6 @@ Singleton {
} }
] ]
readonly property list<var> preppedVariants: list.map(v => ({
name: Fuzzy.prepare(v.variant),
variant: v
}))
function fuzzyQuery(search: string): var {
return Fuzzy.go(search.slice(`${Config.launcher.actionPrefix}variant `.length), preppedVariants, {
all: true,
key: "name"
}).map(r => r.obj.variant);
}
component Variant: QtObject { component Variant: QtObject {
required property string variant required property string variant
required property string icon required property string icon

View file

@ -1,28 +1,19 @@
pragma Singleton pragma Singleton
import "root:/utils/scripts/fuzzysort.js" as Fuzzy import qs.utils
import qs.config
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick import QtQuick
Singleton { Searcher {
id: root id: root
readonly property list<var> preppedSchemes: schemes.instances.map(s => ({ function transformSearch(search: string): var {
name: Fuzzy.prepare(s.name), return search.slice(`${Config.launcher.actionPrefix}scheme `.length);
flavour: Fuzzy.prepare(s.flavour),
scheme: s
}))
function fuzzyQuery(search: string): var {
return Fuzzy.go(search.slice(`${Config.launcher.actionPrefix}scheme `.length), preppedSchemes, {
all: true,
keys: ["name", "flavour"],
scoreFn: r => r[0].score > 0 ? r[0].score * 0.9 + r[1].score * 0.1 : 0
}).map(r => r.obj.scheme);
} }
list: schemes.instances
Variants { Variants {
id: schemes id: schemes

View file

@ -1,25 +1,12 @@
pragma Singleton pragma Singleton
import "root:/utils/scripts/fuzzysort.js" as Fuzzy import qs.utils
import Quickshell import Quickshell
Singleton { Searcher {
id: root id: root
readonly property list<DesktopEntry> list: DesktopEntries.applications.values.filter(a => !a.noDisplay).sort((a, b) => a.name.localeCompare(b.name)) list: DesktopEntries.applications.values.filter(a => !a.noDisplay).sort((a, b) => a.name.localeCompare(b.name))
readonly property list<var> preppedApps: list.map(a => ({
name: Fuzzy.prepare(a.name),
comment: Fuzzy.prepare(a.comment),
entry: a
}))
function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work
return Fuzzy.go(search, preppedApps, {
all: true,
keys: ["name", "comment"],
scoreFn: r => r[0].score > 0 ? r[0].score * 0.9 + r[1].score * 0.1 : 0
}).map(r => r.obj.entry);
}
function launch(entry: DesktopEntry): void { function launch(entry: DesktopEntry): void {
if (entry.runInTerminal) if (entry.runInTerminal)

42
utils/Searcher.qml Normal file
View file

@ -0,0 +1,42 @@
import Quickshell
import "scripts/fzf.js" as Fzf
import "scripts/fuzzysort.js" as Fuzzy
import QtQuick
Singleton {
required property list<QtObject> list
property string key: "name"
property bool useFuzzy: false
property var extraOpts: ({})
readonly property var fzf: useFuzzy ? [] : new Fzf.Finder(list, Object.assign({
selector: e => e[key]
}, extraOpts))
readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => ({
[key]: e[key],
_item: e
})) : []
function transformSearch(search: string): string {
return search;
}
function query(search: string): list<var> {
search = transformSearch(search);
if (!search)
return [...list];
if (useFuzzy)
return Fuzzy.go(search, fuzzyPrepped, Object.assign({
all: true,
key
}, extraOpts)).map(r => r.obj._item);
return fzf.find(search).sort((a, b) => {
if (a.score === b.score)
return a.item[key].trim().length - b.item[key].trim().length;
return b.score - a.score;
}).map(r => r.item);
}
}

View file

@ -1,5 +1,31 @@
.pragma library .pragma library
/*
https://github.com/farzher/fuzzysort
MIT License
Copyright (c) 2018 Stephen Kamenar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
var single = (search, target) => { var single = (search, target) => {
if(!search || !target) return NULL if(!search || !target) return NULL

1307
utils/scripts/fzf.js Normal file

File diff suppressed because it is too large Load diff