Commit 281bbf66fdaa
Changed files (1)
dots
pi
agent
extensions
org-todos
dots/pi/agent/extensions/org-todos/index.ts
@@ -1061,21 +1061,50 @@ export default function (pi: ExtensionAPI) {
},
});
- // Helper: show a SelectList and return the chosen value (or null on cancel)
- async function showSelectMenu(ctx: ExtensionContext, title: string, items: SelectItem[]): Promise<string | null> {
+ // Fuzzy match: all space-separated terms must appear somewhere in label or description
+ function fuzzyMatch(item: SelectItem, query: string): boolean {
+ if (!query) return true;
+ const searchable = `${item.label} ${item.description || ""}`.toLowerCase();
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
+ return terms.every((term) => searchable.includes(term));
+ }
+
+ // Helper: show a SelectList with fuzzy search and return the chosen value (or null on cancel)
+ async function showSelectMenu(ctx: ExtensionContext, title: string, allItems: SelectItem[]): Promise<string | null> {
return ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
+ let searchQuery = "";
+
+ function getFilteredItems(): SelectItem[] {
+ if (!searchQuery) return allItems;
+ return allItems.filter((item) => fuzzyMatch(item, searchQuery));
+ }
+
+ let currentItems = getFilteredItems();
+
const container = new Container();
container.addChild(new DynamicBorder((str: string) => theme.fg("accent", str)));
- container.addChild(new Text(theme.fg("accent", theme.bold(title))));
- const selectList = new SelectList(items, Math.min(items.length, 15), {
+ const headerText = new Text("", 0, 0);
+ function updateHeader() {
+ const titleStr = theme.fg("accent", theme.bold(title));
+ if (searchQuery) {
+ headerText.setText(`${titleStr} ${theme.fg("warning", `filter: ${searchQuery}`)}`);
+ } else {
+ headerText.setText(titleStr);
+ }
+ }
+ updateHeader();
+ container.addChild(headerText);
+
+ const listTheme = {
selectedPrefix: (text: string) => theme.fg("accent", text),
selectedText: (text: string) => theme.fg("accent", text),
description: (text: string) => theme.fg("muted", text),
scrollInfo: (text: string) => theme.fg("dim", text),
noMatch: (text: string) => theme.fg("warning", text),
- });
+ };
+ let selectList = new SelectList(currentItems, Math.min(currentItems.length, 15), listTheme);
selectList.onSelect = (item: SelectItem) => done(item.value);
selectList.onCancel = () => done(null);
@@ -1083,10 +1112,43 @@ export default function (pi: ExtensionAPI) {
container.addChild(new Text(theme.fg("dim", "Type to filter · enter to confirm · esc to cancel")));
container.addChild(new DynamicBorder((str: string) => theme.fg("accent", str)));
+ function rebuildList() {
+ currentItems = getFilteredItems();
+ const newList = new SelectList(currentItems, Math.min(currentItems.length, 15), listTheme);
+ newList.onSelect = (item: SelectItem) => done(item.value);
+ newList.onCancel = () => done(null);
+ const idx = container.children.indexOf(selectList);
+ if (idx !== -1) container.children[idx] = newList;
+ selectList = newList;
+ updateHeader();
+ }
+
return {
render(width: number) { return container.render(width); },
invalidate() { container.invalidate(); },
- handleInput(data: string) { selectList.handleInput(data); tui.requestRender(); },
+ handleInput(data: string) {
+ // Backspace: remove last char from search
+ if (data === "\x7f" || data === "\b") {
+ if (searchQuery.length > 0) {
+ searchQuery = searchQuery.slice(0, -1);
+ rebuildList();
+ tui.requestRender();
+ }
+ return;
+ }
+
+ // Printable characters: append to search
+ if (data.length === 1 && data >= " " && data <= "~") {
+ searchQuery += data;
+ rebuildList();
+ tui.requestRender();
+ return;
+ }
+
+ // Everything else (arrows, enter, escape): pass to SelectList
+ selectList.handleInput(data);
+ tui.requestRender();
+ },
};
});
}