✨ feat(runner/plugins): implement fuzzy search in wallpapers and clipboard plugins
now the clipboard and the wallpapers runner plugins support fuzzy searching! it's much better than the previous way, as it can match results with characters in-between words and sorted results based on matching score. thanks to fuse.js!
This commit is contained in:
+2
-1
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ags": "link:../../../../usr/share/ags/js",
|
"ags": "link:../../../../usr/share/ags/js",
|
||||||
"gnim-utils": "github:retrozinndev/gnim-utils"
|
"gnim-utils": "github:retrozinndev/gnim-utils",
|
||||||
|
"fuse.js": "^7.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export interface Plugin {
|
|||||||
/** runs when runner opens */
|
/** runs when runner opens */
|
||||||
readonly init?: () => void;
|
readonly init?: () => void;
|
||||||
/** handle the user input to return results (does not include plugin's prefix) */
|
/** handle the user input to return results (does not include plugin's prefix) */
|
||||||
readonly handle: (inputText: string) => (Result|Array<Result>|null|undefined);
|
readonly handle: (inputText: string, limit?: number) => (Result|Array<Result>|null|undefined);
|
||||||
/** runs when runner closes */
|
/** runs when runner closes */
|
||||||
readonly onClose?: () => void;
|
readonly onClose?: () => void;
|
||||||
/** prioritize this plugin's results over other results.
|
/** prioritize this plugin's results over other results.
|
||||||
@@ -171,7 +171,8 @@ function getPluginResults(input: string, limit?: number): Array<Result> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const results = calledPlugins.map(plugin => plugin.handle(
|
const results = calledPlugins.map(plugin => plugin.handle(
|
||||||
plugin.prefix ? input.replace(plugin.prefix, "") : input)
|
plugin.prefix ? input.replace(plugin.prefix, "") : input),
|
||||||
|
limit
|
||||||
).filter(value => value !== undefined && value !== null).flat(1);
|
).filter(value => value !== undefined && value !== null).flat(1);
|
||||||
|
|
||||||
return limit != null && limit > 0 ?
|
return limit != null && limit > 0 ?
|
||||||
|
|||||||
@@ -1,33 +1,62 @@
|
|||||||
import { Gtk } from "ags/gtk4";
|
import { Gtk } from "ags/gtk4";
|
||||||
import { Clipboard } from "../../modules/clipboard";
|
import { Clipboard, ClipboardItem } from "../../modules/clipboard";
|
||||||
import { Runner } from "../Runner";
|
import { Runner } from "../Runner";
|
||||||
import { jsx } from "ags/gtk4/jsx-runtime";
|
import { jsx } from "ags/gtk4/jsx-runtime";
|
||||||
|
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
export const PluginClipboard = {
|
|
||||||
prefix: '>',
|
class _PluginClipboard implements Runner.Plugin {
|
||||||
prioritize: true,
|
#fuse!: Fuse<unknown>;
|
||||||
handle: (search) => {
|
prefix = '>';
|
||||||
|
prioritize = true;
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const items: ReadonlyArray<ClipboardItem> = [...Clipboard.getDefault().history];
|
||||||
|
this.#fuse = new Fuse(
|
||||||
|
items,
|
||||||
|
{
|
||||||
|
keys: [ "id", "preview" ] satisfies Array<keyof ClipboardItem>,
|
||||||
|
ignoreDiacritics: false,
|
||||||
|
isCaseSensitive: false,
|
||||||
|
shouldSort: true,
|
||||||
|
useExtendedSearch: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clipboardResult(item: ClipboardItem): Runner.Result {
|
||||||
|
return {
|
||||||
|
icon: jsx(Gtk.Label, {
|
||||||
|
label: `${item.id}`,
|
||||||
|
css: "font-size: 16px; margin-right: 8px; font-weight: 600;"
|
||||||
|
}),
|
||||||
|
title: item.preview,
|
||||||
|
actionClick: () => Clipboard.getDefault().selectItem(item).catch((err: Error) => {
|
||||||
|
console.error(`Runner(Plugin/Clipboard): An error occurred while selecting clipboard item. Stderr:\n${
|
||||||
|
err.message ? `${err.message}\n` : ""}Stack: ${err.stack}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handle(search: string, limit?: number) {
|
||||||
if(Clipboard.getDefault().history.length < 1)
|
if(Clipboard.getDefault().history.length < 1)
|
||||||
return {
|
return {
|
||||||
icon: "edit-paste-symbolic",
|
icon: "edit-paste-symbolic",
|
||||||
title: "Clipboard is empty",
|
title: "Clipboard is empty",
|
||||||
description: "Copy something and it will be shown right here!"
|
description: "Copy something and it will be shown right here!"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(search.trim().length === 0)
|
||||||
|
return Clipboard.getDefault().history.map(item =>
|
||||||
|
this.clipboardResult(item)
|
||||||
|
);
|
||||||
|
|
||||||
return Clipboard.getDefault().history.filter(item =>
|
return this.#fuse.search(search, {
|
||||||
// not the best way to search, but it works
|
limit: limit ?? Infinity
|
||||||
Runner.regExMatch(search, item.id) || Runner.regExMatch(search, item.preview)).map((item) => ({
|
}).map(result => this.clipboardResult(result.item as ClipboardItem))
|
||||||
icon: jsx(Gtk.Label, {
|
|
||||||
label: `${item.id}`,
|
|
||||||
css: "font-size: 16px; margin-right: 8px; font-weight: 600;"
|
|
||||||
}),
|
|
||||||
title: item.preview,
|
|
||||||
actionClick: () => Clipboard.getDefault().selectItem(item).catch((err: Error) => {
|
|
||||||
console.error(`Runner(Plugin/Clipboard): An error occurred while selecting clipboard item. Stderr:\n${
|
|
||||||
err.message ? `${err.message}\n` : ""}Stack: ${err.stack}`
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
} as Runner.Plugin;
|
}
|
||||||
|
|
||||||
|
export const PluginClipboard = new _PluginClipboard();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import Fuse from "fuse.js";
|
||||||
import { Wallpaper } from "../../modules/wallpaper";
|
import { Wallpaper } from "../../modules/wallpaper";
|
||||||
import { Runner } from "../Runner";
|
import { Runner } from "../Runner";
|
||||||
|
|
||||||
@@ -7,7 +8,8 @@ import Gio from "gi://Gio?version=2.0";
|
|||||||
class _PluginWallpapers implements Runner.Plugin {
|
class _PluginWallpapers implements Runner.Plugin {
|
||||||
prefix = "#";
|
prefix = "#";
|
||||||
prioritize = true;
|
prioritize = true;
|
||||||
#files: (Array<string>|undefined);
|
#fuse!: Fuse<string>;
|
||||||
|
#files!: Array<string>;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.#files = [];
|
this.#files = [];
|
||||||
@@ -21,17 +23,34 @@ class _PluginWallpapers implements Runner.Plugin {
|
|||||||
this.#files.push(`${dir.get_path()}/${file.get_name()}`);
|
this.#files.push(`${dir.get_path()}/${file.get_name()}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#fuse = new Fuse<string>(
|
||||||
|
this.#files as ReadonlyArray<string>,
|
||||||
|
{
|
||||||
|
useExtendedSearch: false,
|
||||||
|
shouldSort: true,
|
||||||
|
isCaseSensitive: false
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(search: string) {
|
private wallpaperResult(path: string): Runner.Result {
|
||||||
if(this.#files!.length > 0)
|
return {
|
||||||
return this.#files!.filter(file =>
|
title: path.split('/')[path.split('/').length-1].replace(/\..*$/, ""),
|
||||||
// also not the best way to search, but it works
|
actionClick: () => Wallpaper.getDefault().setWallpaper(path)
|
||||||
Runner.regExMatch(search, file.split('/')[file.split('/').length-1])
|
};
|
||||||
).map(path => ({
|
}
|
||||||
title: path.split('/')[path.split('/').length-1].replace(/\..*$/, ""),
|
|
||||||
actionClick: () => Wallpaper.getDefault().setWallpaper(path)
|
handle(search: string, limit?: number) {
|
||||||
}));
|
if(search.trim().length === 0)
|
||||||
|
return this.#files.map(path =>
|
||||||
|
this.wallpaperResult(path)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(this.#files.length > 0)
|
||||||
|
return this.#fuse.search(search, {
|
||||||
|
limit: limit ?? Infinity
|
||||||
|
}).map(result => this.wallpaperResult(result.item));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "No wallpapers found!",
|
title: "No wallpapers found!",
|
||||||
|
|||||||
Reference in New Issue
Block a user