launcher: fd based file search
This commit is contained in:
parent
9c8d9aa975
commit
e8328bbb1c
2 changed files with 48 additions and 5 deletions
|
|
@ -5,6 +5,7 @@ import type AstalApps from "gi://AstalApps";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
import { Apps } from "../services/apps";
|
import { Apps } from "../services/apps";
|
||||||
import Math, { type HistoryItem } from "../services/math";
|
import Math, { type HistoryItem } from "../services/math";
|
||||||
|
import { HOME } from "../utils/constants";
|
||||||
import { getAppCategoryIcon } from "../utils/icons";
|
import { getAppCategoryIcon } from "../utils/icons";
|
||||||
import { launch } from "../utils/system";
|
import { launch } from "../utils/system";
|
||||||
import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets";
|
import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets";
|
||||||
|
|
@ -19,6 +20,7 @@ interface Subcommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSearchResults = 15;
|
const maxSearchResults = 15;
|
||||||
|
const fdOptions = ["-a", "-t", "f", "-E", ".git"];
|
||||||
|
|
||||||
const browser = [
|
const browser = [
|
||||||
"firefox",
|
"firefox",
|
||||||
|
|
@ -56,12 +58,25 @@ const getEmptyTextFromMode = (mode: Mode) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => {
|
const close = (self: JSX.Element) => {
|
||||||
const toplevel = self.get_toplevel();
|
const toplevel = self.get_toplevel();
|
||||||
if (toplevel instanceof Widget.Window) toplevel.hide();
|
if (toplevel instanceof Widget.Window) toplevel.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => {
|
||||||
|
close(self);
|
||||||
launch(astalApp);
|
launch(astalApp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openFileAndClose = (self: JSX.Element, path: string) => {
|
||||||
|
close(self);
|
||||||
|
execAsync([
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
`dbus-send --session --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:"file://${path}" string:"" || xdg-open "${path}"`,
|
||||||
|
]).catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
const PinnedApp = ({ names }: { names: string[] }) => {
|
const PinnedApp = ({ names }: { names: string[] }) => {
|
||||||
let app: Gio.DesktopAppInfo | null = null;
|
let app: Gio.DesktopAppInfo | null = null;
|
||||||
let astalApp: AstalApps.Application | undefined;
|
let astalApp: AstalApps.Application | undefined;
|
||||||
|
|
@ -137,7 +152,7 @@ const Result = ({
|
||||||
)}
|
)}
|
||||||
{sublabel ? (
|
{sublabel ? (
|
||||||
<box vertical valign={Gtk.Align.CENTER} className="has-sublabel">
|
<box vertical valign={Gtk.Align.CENTER} className="has-sublabel">
|
||||||
<label xalign={0} label={label} />
|
<label hexpand truncate maxWidthChars={1} xalign={0} label={label} />
|
||||||
<label hexpand truncate maxWidthChars={1} className="sublabel" xalign={0} label={sublabel} />
|
<label hexpand truncate maxWidthChars={1} className="sublabel" xalign={0} label={sublabel} />
|
||||||
</box>
|
</box>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -196,6 +211,14 @@ const MathResult = ({ math, isHistory, entry }: { math: HistoryItem; isHistory?:
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const FileResult = ({ path }: { path: string }) => (
|
||||||
|
<Result
|
||||||
|
label={path.split("/").pop()!}
|
||||||
|
sublabel={path.startsWith(HOME) ? "~" + path.slice(HOME.length) : path}
|
||||||
|
onClicked={self => openFileAndClose(self, path)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) => {
|
const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) => {
|
||||||
const empty = Variable(true);
|
const empty = Variable(true);
|
||||||
|
|
||||||
|
|
@ -248,9 +271,10 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
};
|
};
|
||||||
const subcommandList = Object.keys(subcommands);
|
const subcommandList = Object.keys(subcommands);
|
||||||
|
|
||||||
|
const updateEmpty = () => empty.set(self.get_children().length === 0);
|
||||||
|
|
||||||
const appSearch = () => {
|
const appSearch = () => {
|
||||||
const apps = Apps.fuzzy_query(entry.text);
|
const apps = Apps.fuzzy_query(entry.text);
|
||||||
empty.set(apps.length === 0);
|
|
||||||
if (apps.length > maxSearchResults) apps.length = maxSearchResults;
|
if (apps.length > maxSearchResults) apps.length = maxSearchResults;
|
||||||
for (const app of apps) self.add(<AppResult app={app} />);
|
for (const app of apps) self.add(<AppResult app={app} />);
|
||||||
};
|
};
|
||||||
|
|
@ -264,6 +288,20 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
self.add(<MathResult isHistory math={item} entry={entry} />);
|
self.add(<MathResult isHistory math={item} entry={entry} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fileSearch = () =>
|
||||||
|
execAsync(["fd", ...fdOptions, entry.text, HOME])
|
||||||
|
.then(out => {
|
||||||
|
const paths = out.split("\n").filter(path => path);
|
||||||
|
if (paths.length > maxSearchResults) paths.length = maxSearchResults;
|
||||||
|
self.foreach(ch => ch.destroy());
|
||||||
|
for (const path of paths) self.add(<FileResult path={path} />);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Ignore execAsync error
|
||||||
|
if (!(e instanceof Gio.IOErrorEnum || e instanceof GLib.SpawnError)) console.error(e);
|
||||||
|
})
|
||||||
|
.finally(updateEmpty);
|
||||||
|
|
||||||
self.hook(entry, "activate", () => {
|
self.hook(entry, "activate", () => {
|
||||||
if (!entry.text) return;
|
if (!entry.text) return;
|
||||||
if (mode.get() === "math") {
|
if (mode.get() === "math") {
|
||||||
|
|
@ -274,7 +312,10 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
});
|
});
|
||||||
self.hook(entry, "changed", () => {
|
self.hook(entry, "changed", () => {
|
||||||
if (!entry.text && mode.get() === "apps") return;
|
if (!entry.text && mode.get() === "apps") return;
|
||||||
self.foreach(ch => ch.destroy());
|
|
||||||
|
// Files has delay cause async so it does some stuff by itself
|
||||||
|
const ignoreFileAsync = !entry.text || entry.text.startsWith(">") || mode.get() !== "files";
|
||||||
|
if (ignoreFileAsync) self.foreach(ch => ch.destroy());
|
||||||
|
|
||||||
if (entry.text.startsWith(">")) {
|
if (entry.text.startsWith(">")) {
|
||||||
const args = entry.text.split(" ");
|
const args = entry.text.split(" ");
|
||||||
|
|
@ -288,8 +329,9 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
);
|
);
|
||||||
} else if (mode.get() === "apps") appSearch();
|
} else if (mode.get() === "apps") appSearch();
|
||||||
else if (mode.get() === "math") calculate();
|
else if (mode.get() === "math") calculate();
|
||||||
|
else if (mode.get() === "files") fileSearch();
|
||||||
|
|
||||||
empty.set(self.get_children().length === 0);
|
if (ignoreFileAsync) updateEmpty();
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { exec, GLib } from "astal";
|
import { exec, GLib } from "astal";
|
||||||
|
|
||||||
|
export const HOME = GLib.get_home_dir();
|
||||||
export const CACHE_DIR = GLib.get_user_cache_dir() + "/caelestia";
|
export const CACHE_DIR = GLib.get_user_cache_dir() + "/caelestia";
|
||||||
exec(`mkdir -p '${CACHE_DIR}'`);
|
exec(`mkdir -p '${CACHE_DIR}'`);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue