summaryrefslogtreecommitdiff
path: root/components/Searchable.qml
diff options
context:
space:
mode:
Diffstat (limited to 'components/Searchable.qml')
-rw-r--r--components/Searchable.qml182
1 files changed, 152 insertions, 30 deletions
diff --git a/components/Searchable.qml b/components/Searchable.qml
index 3275321..39951e3 100644
--- a/components/Searchable.qml
+++ b/components/Searchable.qml
@@ -25,12 +25,18 @@ Singleton {
selector
}, extraOpts)) : null
+ readonly property list<var> fzfFinders: algorithm === Searchable.SearchAlgorithm.Fzf ? keys.map(k => {
+ return new Fzf.Finder(list, Object.assign({
+ selector: item => fieldString(item, k)
+ }, extraOpts));
+ }) : []
+
readonly property list<var> fuzzyPrepped: algorithm === Searchable.SearchAlgorithm.Fuzzysort ? list.map(e => {
const obj = {
_item: e
};
for (const k of keys)
- obj[k] = Fuzzy.prepare(e[k]);
+ obj[k] = Fuzzy.prepare(String(e[k] ?? ""));
return obj;
}) : []
@@ -38,8 +44,13 @@ Singleton {
return search;
}
+ function fieldString(item: var, k: string): string {
+ const v = item[k];
+ return v == null ? "" : String(v);
+ }
+
function selector(item: var): string {
- return item[key];
+ return fieldString(item, keys[0]);
}
function levendistQuery(search: string): list<var> {
@@ -54,31 +65,83 @@ Singleton {
source = queryCache[longest];
}
- const p = [], f = [];
+ const prefixGroups = keys.map(() => []);
+ const fuzzyGroups = keys.map(() => []);
+
for (const s of source) {
- const t = s[key].toLowerCase();
- if (t.startsWith(search)) {
- p.push(s);
- continue;
+ let best = null;
+ for (let i = 0; i < keys.length; i++) {
+ const t = fieldString(s, keys[i]).toLowerCase();
+ if (!t)
+ continue;
+
+ if (t.startsWith(search)) {
+ if (!best || i < best.index || best.type !== "prefix") {
+ best = {
+ type: "prefix",
+ index: i,
+ score: 0
+ };
+ if (best.index === 0)
+ break;
+ }
+ continue;
+ }
+
+ let pos = 0;
+ for (const c of search) {
+ pos = t.indexOf(c, pos);
+ if (pos < 0) {
+ pos = 0;
+ break;
+ }
+ pos++;
+ }
+ if (pos) {
+ const d = Levendist.distance(search, t);
+ if (d <= scoreThreshold) {
+ if (!best || i < best.index || (best.type === "fuzzy" && i === best.index && d < best.score) || best.type === "prefix") {
+ if (!best || i < best.index || d < best.score) {
+ best = {
+ type: "fuzzy",
+ index: i,
+ score: d
+ };
+ }
+ }
+ }
+ }
+ }
+
+ if (best) {
+ if (best.type === "prefix")
+ prefixGroups[best.index].push(s);
+ else
+ fuzzyGroups[best.index].push({
+ s,
+ score: best.score
+ });
}
- let pos = 0;
- for (const c of search) {
- pos = t.indexOf(c, pos);
- if (pos < 0) {
- pos = 0;
- break;
+ }
+
+ const results = [];
+ const seen = new Set();
+ for (let i = 0; i < keys.length; i++) {
+ for (const item of prefixGroups[i]) {
+ if (!seen.has(item)) {
+ results.push(item);
+ seen.add(item);
}
- pos++;
}
- if (pos)
- f.push({
- s,
- score: Levendist.distance(search, t)
- });
+ fuzzyGroups[i].sort((a, b) => a.score - b.score).forEach(({
+ s
+ }) => {
+ if (!seen.has(s)) {
+ results.push(s);
+ seen.add(s);
+ }
+ });
}
- const results = p.concat(f.filter(item => item.score <= scoreThreshold).sort((a, b) => a.score - b.score).map(({
- s
- }) => s));
queryCache[search] = results;
return results;
@@ -92,17 +155,76 @@ Singleton {
if (algorithm === Searchable.SearchAlgorithm.Levendist)
return levendistQuery(search.toLowerCase());
- if (algorithm === Searchable.SearchAlgorithm.Fuzzysort)
- return Fuzzy.go(search, fuzzyPrepped, Object.assign({
+ if (algorithm === Searchable.SearchAlgorithm.Fuzzysort) {
+ const res = Fuzzy.go(search, fuzzyPrepped, Object.assign({
all: true,
keys,
scoreFn: r => weights.reduce((a, w, i) => a + r[i].score * w, 0)
- }, extraOpts)).map(r => r.obj._item);
+ }, extraOpts));
+
+ const groups = keys.map(() => []);
+ const noMatch = [];
+ for (const r of res) {
+ let idx = -1;
+ for (let i = 0; i < keys.length; i++) {
+ const kr = r[i];
+ if (kr && kr.score !== -Infinity) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx === -1)
+ noMatch.push(r);
+ else
+ groups[idx].push(r);
+ }
+
+ const ordered = [];
+ for (let i = 0; i < groups.length; i++) {
+ for (const r of groups[i])
+ ordered.push(r.obj._item);
+ }
+ for (const r of noMatch)
+ ordered.push(r.obj._item);
+ return ordered;
+ }
+
+ if (algorithm === Searchable.SearchAlgorithm.Fzf) {
+ if (keys.length <= 1) {
+ return fzf.find(search).sort((a, b) => {
+ if (a.score === b.score)
+ return (selector(a.item).trim().length - selector(b.item).trim().length);
+ return b.score - a.score;
+ }).map(r => r.item);
+ }
+
+ const groups = keys.map((_, i) => {
+ const arr = fzfFinders[i].find(search);
+ arr.sort((a, b) => {
+ if (a.score === b.score) {
+ const la = fieldString(a.item, keys[i]).trim().length;
+ const lb = fieldString(b.item, keys[i]).trim().length;
+ return la - lb;
+ }
+ return b.score - a.score;
+ });
+ return arr;
+ });
+
+ const seen = new Set();
+ const out = [];
+ for (let i = 0; i < groups.length; i++) {
+ for (const r of groups[i]) {
+ const it = r.item;
+ if (!seen.has(it)) {
+ out.push(it);
+ seen.add(it);
+ }
+ }
+ }
+ return out;
+ }
- return fzf.find(search).sort((a, b) => {
- if (a.score === b.score)
- return selector(a.item).trim().length - selector(b.item).trim().length;
- return b.score - a.score;
- }).map(r => r.item);
+ return [];
}
}