summaryrefslogtreecommitdiff
path: root/modules/launcher.tsx
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-14 19:24:34 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-14 19:24:34 +1100
commite8328bbb1cdc31abb0a6fcee8d0957bfcaf99b54 (patch)
tree9b5161673ceb09fc5e9c0d7dd96b22b08f15031f /modules/launcher.tsx
parentlauncher: better empty text (diff)
downloadcaelestia-shell-e8328bbb1cdc31abb0a6fcee8d0957bfcaf99b54.tar.gz
caelestia-shell-e8328bbb1cdc31abb0a6fcee8d0957bfcaf99b54.tar.bz2
caelestia-shell-e8328bbb1cdc31abb0a6fcee8d0957bfcaf99b54.zip
launcher: fd based file search
Diffstat (limited to 'modules/launcher.tsx')
-rw-r--r--modules/launcher.tsx52
1 files changed, 47 insertions, 5 deletions
diff --git a/modules/launcher.tsx b/modules/launcher.tsx
index 5015b5b..b9ab20b 100644
--- a/modules/launcher.tsx
+++ b/modules/launcher.tsx
@@ -5,6 +5,7 @@ import type AstalApps from "gi://AstalApps";
import AstalHyprland from "gi://AstalHyprland";
import { Apps } from "../services/apps";
import Math, { type HistoryItem } from "../services/math";
+import { HOME } from "../utils/constants";
import { getAppCategoryIcon } from "../utils/icons";
import { launch } from "../utils/system";
import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets";
@@ -19,6 +20,7 @@ interface Subcommand {
}
const maxSearchResults = 15;
+const fdOptions = ["-a", "-t", "f", "-E", ".git"];
const browser = [
"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();
if (toplevel instanceof Widget.Window) toplevel.hide();
+};
+
+const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => {
+ close(self);
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[] }) => {
let app: Gio.DesktopAppInfo | null = null;
let astalApp: AstalApps.Application | undefined;
@@ -137,7 +152,7 @@ const Result = ({
)}
{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} />
</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 empty = Variable(true);
@@ -248,9 +271,10 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
};
const subcommandList = Object.keys(subcommands);
+ const updateEmpty = () => empty.set(self.get_children().length === 0);
+
const appSearch = () => {
const apps = Apps.fuzzy_query(entry.text);
- empty.set(apps.length === 0);
if (apps.length > maxSearchResults) apps.length = maxSearchResults;
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} />);
};
+ 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", () => {
if (!entry.text) return;
if (mode.get() === "math") {
@@ -274,7 +312,10 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
});
self.hook(entry, "changed", () => {
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(">")) {
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() === "math") calculate();
+ else if (mode.get() === "files") fileSearch();
- empty.set(self.get_children().length === 0);
+ if (ignoreFileAsync) updateEmpty();
});
}}
/>