@d3ara1n/pi-command-palette 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/package.json +1 -1
- package/src/index.ts +209 -187
package/README.md
CHANGED
|
@@ -34,12 +34,17 @@ Or add to `~/.pi/agent/settings.json`:
|
|
|
34
34
|
|----------|--------|
|
|
35
35
|
| `Ctrl+Shift+P` _(default, configurable)_ | Open command palette |
|
|
36
36
|
|
|
37
|
+
The palette opens as a single macOS-launcher-style overlay with nested pages. Selecting a category with **Enter** replaces the current list in the same overlay instead of opening a second overlay. Press **Backspace** with an empty search field to return to the parent page; press **Esc** to close the palette immediately.
|
|
38
|
+
|
|
37
39
|
The palette lists:
|
|
38
40
|
|
|
39
|
-
- **Built-in
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **Skills
|
|
41
|
+
- **Built-in Actions** — curated shortcuts for common operations (detailed below)
|
|
42
|
+
- **Extension Actions** — entries registered by other extensions that run a callback directly (see below)
|
|
43
|
+
- **Commands** — all registered `/command` entries
|
|
44
|
+
- **Skills** — installed skill commands
|
|
45
|
+
- **Templates** — prompt templates
|
|
46
|
+
|
|
47
|
+
Use **↑/↓** to move through entries and **←/→** to edit the search cursor. Search is fuzzy within the current page and updates as you type; Backspace uses the normal text-editing behavior while the query is non-empty.
|
|
43
48
|
|
|
44
49
|
### Built-in actions
|
|
45
50
|
|
|
@@ -90,7 +95,7 @@ When a command replaces your editor text, or you run **Editor: Clear Content**,
|
|
|
90
95
|
|
|
91
96
|
### Model selector
|
|
92
97
|
|
|
93
|
-
The "Model: Switch Model"
|
|
98
|
+
The "Model: Switch Model" entry opens a model page inside the same overlay. Models are loaded when the page is first entered, then can be searched and selected without stacking another overlay.
|
|
94
99
|
|
|
95
100
|
**Scoped models float to the top**, marked with a ★ (favorite) prefix. "Scoped" here means the same set pi uses for its built-in selector's scoped tab and `Ctrl+P` cycling — the `enabledModels` patterns in your `settings.json` (project `.pi/settings.json` overrides global `~/.pi/agent/settings.json`). Everything else follows alphabetically. Filtering preserves that boundary too — scoped matches stay above the rest while you type, rather than collapsing into one score-ordered list. If no scope is configured, the list is a plain alphabetical roster — nothing breaks.
|
|
96
101
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
Container,
|
|
21
21
|
type SelectItem,
|
|
22
22
|
fuzzyFilter,
|
|
23
|
+
Input,
|
|
23
24
|
Key,
|
|
24
25
|
matchesKey,
|
|
25
26
|
SelectList,
|
|
@@ -33,6 +34,7 @@ type CommandAction =
|
|
|
33
34
|
| { type: "editor"; text: string }
|
|
34
35
|
| { type: "native"; id: string }
|
|
35
36
|
| { type: "model-select" }
|
|
37
|
+
| { type: "model"; provider: string; modelId: string }
|
|
36
38
|
| { type: "compact" }
|
|
37
39
|
| { type: "reload" }
|
|
38
40
|
| { type: "restore" }
|
|
@@ -257,231 +259,240 @@ export function partitionedFuzzyFilter<T>(
|
|
|
257
259
|
return [...fuzzyFilter(primary, query, getText), ...fuzzyFilter(secondary, query, getText)];
|
|
258
260
|
}
|
|
259
261
|
|
|
260
|
-
|
|
261
|
-
let models: Awaited<ReturnType<typeof ctx.modelRegistry.getAvailable>>;
|
|
262
|
-
try {
|
|
263
|
-
models = await ctx.modelRegistry.getAvailable();
|
|
264
|
-
} catch {
|
|
265
|
-
ctx.ui.notify("Cannot enumerate models. Use Ctrl+L instead.", "warning");
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (models.length === 0) {
|
|
270
|
-
ctx.ui.notify("No models available.", "warning");
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const scopedIds = new Set(ctx.scopedModels.map((s) => `${s.model.provider}/${s.model.id}`));
|
|
275
|
-
|
|
276
|
-
// Build each model's SelectItem alongside whether it's scope-pinned, then
|
|
277
|
-
// sort into two stable groups — scoped first (★), then the rest —
|
|
278
|
-
// alphabetical within each. We keep the groups as separate arrays so the
|
|
279
|
-
// search pipeline below can re-apply the same partitioning.
|
|
280
|
-
const decorated = models
|
|
281
|
-
.map((m) => {
|
|
282
|
-
const value = `${m.provider}/${m.id}`;
|
|
283
|
-
const scoped = scopedIds.has(value);
|
|
284
|
-
return {
|
|
285
|
-
scoped,
|
|
286
|
-
item: {
|
|
287
|
-
value,
|
|
288
|
-
label: scoped ? `${STAR}${m.name}` : m.name,
|
|
289
|
-
description: m.provider,
|
|
290
|
-
} satisfies SelectItem,
|
|
291
|
-
};
|
|
292
|
-
})
|
|
293
|
-
.sort((a, b) => {
|
|
294
|
-
if (a.scoped !== b.scoped) return a.scoped ? -1 : 1;
|
|
295
|
-
return a.item.label.localeCompare(b.item.label);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Scoped models (★) stay above the rest whether browsing or filtering: each
|
|
299
|
-
// group is filtered and ranked independently, then concatenated, so a search
|
|
300
|
-
// never merges the two into one score-ordered list.
|
|
301
|
-
const scopedItems: SelectItem[] = decorated.filter((d) => d.scoped).map((d) => d.item);
|
|
302
|
-
const otherItems: SelectItem[] = decorated.filter((d) => !d.scoped).map((d) => d.item);
|
|
303
|
-
const items: SelectItem[] = [...scopedItems, ...otherItems];
|
|
304
|
-
|
|
305
|
-
const result = await ctx.ui.custom<string | null>(
|
|
306
|
-
(tui, theme, _kb, done) => {
|
|
307
|
-
const container = new Container();
|
|
308
|
-
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
309
|
-
container.addChild(new Text(theme.fg("accent", theme.bold("Switch Model")), 1, 0));
|
|
310
|
-
|
|
311
|
-
const selectList = new SelectList(items, Math.min(items.length, 12), {
|
|
312
|
-
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
313
|
-
selectedText: (t: string) => theme.fg("accent", t),
|
|
314
|
-
description: (t: string) => theme.fg("muted", t),
|
|
315
|
-
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
316
|
-
noMatch: (t: string) => theme.fg("warning", t),
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
selectList.onSelect = (item) => done(item.value);
|
|
320
|
-
selectList.onCancel = () => done(null);
|
|
321
|
-
|
|
322
|
-
// Type-to-filter state
|
|
323
|
-
let query = "";
|
|
324
|
-
const queryText = new Text(theme.fg("accent", "> "), 1, 0);
|
|
325
|
-
|
|
326
|
-
function applyQuery() {
|
|
327
|
-
const filtered = partitionedFuzzyFilter(
|
|
328
|
-
scopedItems,
|
|
329
|
-
otherItems,
|
|
330
|
-
query,
|
|
331
|
-
(item: SelectItem) => `${item.label} ${item.description ?? ""}`,
|
|
332
|
-
);
|
|
333
|
-
// FRAGILE: SelectList has no public filter/setItems API, so we poke its
|
|
334
|
-
// private filteredItems directly. If pi-tui renames it, filtering breaks
|
|
335
|
-
// silently with no compile error.
|
|
336
|
-
(selectList as any).filteredItems = filtered;
|
|
337
|
-
selectList.setSelectedIndex(0);
|
|
338
|
-
queryText.setText(theme.fg("accent", `> ${query}▎`));
|
|
339
|
-
container.invalidate();
|
|
340
|
-
tui.requestRender();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
container.addChild(queryText);
|
|
344
|
-
container.addChild(selectList);
|
|
345
|
-
container.addChild(
|
|
346
|
-
new Text(theme.fg("dim", "type to filter • ↑↓ navigate • enter select • esc cancel"), 1, 0),
|
|
347
|
-
);
|
|
348
|
-
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
render(w: number) {
|
|
352
|
-
return container.render(w);
|
|
353
|
-
},
|
|
354
|
-
invalidate() {
|
|
355
|
-
container.invalidate();
|
|
356
|
-
},
|
|
357
|
-
handleInput(data: string) {
|
|
358
|
-
// Backspace → trim query
|
|
359
|
-
if (matchesKey(data, Key.backspace)) {
|
|
360
|
-
if (query.length > 0) {
|
|
361
|
-
query = query.slice(0, -1);
|
|
362
|
-
applyQuery();
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
// Printable character → append to query
|
|
367
|
-
if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
368
|
-
query += data;
|
|
369
|
-
applyQuery();
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
// Navigation / confirm / cancel → pass to SelectList
|
|
373
|
-
selectList.handleInput(data);
|
|
374
|
-
tui.requestRender();
|
|
375
|
-
},
|
|
376
|
-
};
|
|
377
|
-
},
|
|
378
|
-
{ overlay: true },
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
if (!result) return;
|
|
262
|
+
// ── Command palette overlay ────────────────────────────────────────
|
|
382
263
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const model = ctx.modelRegistry.find(provider, modelId);
|
|
387
|
-
if (model) {
|
|
388
|
-
const success = await pi.setModel(model);
|
|
389
|
-
if (success) {
|
|
390
|
-
ctx.ui.notify(`Model: ${provider}/${modelId}`, "info");
|
|
391
|
-
} else {
|
|
392
|
-
ctx.ui.notify(`No API key for ${provider}/${modelId}`, "error");
|
|
393
|
-
}
|
|
394
|
-
}
|
|
264
|
+
interface PalettePage {
|
|
265
|
+
title: string;
|
|
266
|
+
items: Array<PaletteItem | { type: "page"; value: string; label: string; description: string; page: PalettePage }>;
|
|
395
267
|
}
|
|
396
268
|
|
|
397
|
-
|
|
269
|
+
function pageItem(
|
|
270
|
+
value: string,
|
|
271
|
+
label: string,
|
|
272
|
+
description: string,
|
|
273
|
+
page: PalettePage,
|
|
274
|
+
): PalettePage["items"][number] {
|
|
275
|
+
return { type: "page", value, label, description, page };
|
|
276
|
+
}
|
|
398
277
|
|
|
399
278
|
async function showCommandPalette(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
400
|
-
if (
|
|
279
|
+
if (ctx.mode !== "tui") return;
|
|
401
280
|
|
|
402
281
|
const paletteItems = buildPaletteItems(pi);
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
282
|
+
const leaves = (category: string) =>
|
|
283
|
+
paletteItems.filter((item) => item.category === category);
|
|
284
|
+
const leafPage = (title: string, items: PaletteItem[]): PalettePage => ({ title, items });
|
|
285
|
+
|
|
286
|
+
const builtins = leaves("Built-in").filter((item) => item.action.type !== "model-select");
|
|
287
|
+
const native = leaves("Native");
|
|
288
|
+
const commands = leaves("Command");
|
|
289
|
+
const skills = leaves("Skill");
|
|
290
|
+
const templates = leaves("Template");
|
|
291
|
+
const modelPage: PalettePage = { title: "Models", items: [] };
|
|
292
|
+
const rootItems: PalettePage["items"] = [
|
|
293
|
+
pageItem("models", "Model: Switch Model", "Choose a model", modelPage),
|
|
294
|
+
...(builtins.length ? [pageItem("builtins", "Built-in Actions", "Session and editor actions", leafPage("Built-in Actions", builtins))] : []),
|
|
295
|
+
...(native.length ? [pageItem("native", "Extension Actions", "Actions provided by extensions", leafPage("Extension Actions", native))] : []),
|
|
296
|
+
...(commands.length ? [pageItem("commands", "Commands", "Extension slash commands", leafPage("Commands", commands))] : []),
|
|
297
|
+
...(skills.length ? [pageItem("skills", "Skills", "Installed skills", leafPage("Skills", skills))] : []),
|
|
298
|
+
...(templates.length ? [pageItem("templates", "Templates", "Prompt templates", leafPage("Templates", templates))] : []),
|
|
299
|
+
];
|
|
300
|
+
const root: PalettePage = { title: "Command Palette", items: rootItems };
|
|
408
301
|
|
|
409
302
|
const result = await ctx.ui.custom<PaletteItem | null>(
|
|
410
303
|
(tui, theme, _kb, done) => {
|
|
411
304
|
const container = new Container();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const
|
|
305
|
+
const listHost = new Container();
|
|
306
|
+
const queryInput = new Input();
|
|
307
|
+
let focused = true;
|
|
308
|
+
const stack: Array<{ page: PalettePage; input: string; selectedValue?: string }> = [
|
|
309
|
+
{ page: root, input: "" },
|
|
310
|
+
];
|
|
311
|
+
let selectList!: SelectList;
|
|
312
|
+
let visibleItems: PalettePage["items"] = [];
|
|
313
|
+
let modelLoading = false;
|
|
314
|
+
|
|
315
|
+
const listTheme = {
|
|
416
316
|
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
417
317
|
selectedText: (t: string) => theme.fg("accent", t),
|
|
418
318
|
description: (t: string) => theme.fg("muted", t),
|
|
419
319
|
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
420
320
|
noMatch: (t: string) => theme.fg("warning", t),
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
selectList.onSelect = (item) => {
|
|
424
|
-
const paletteItem = paletteItems.find((p) => p.value === item.value);
|
|
425
|
-
done(paletteItem ?? null);
|
|
426
321
|
};
|
|
427
|
-
selectList.onCancel = () => done(null);
|
|
428
322
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
323
|
+
function current() {
|
|
324
|
+
return stack[stack.length - 1]!;
|
|
325
|
+
}
|
|
432
326
|
|
|
433
|
-
function
|
|
327
|
+
function rebuild() {
|
|
328
|
+
const { page } = current();
|
|
329
|
+
const query = queryInput.getValue();
|
|
330
|
+
visibleItems =
|
|
331
|
+
page === root && query.trim()
|
|
332
|
+
? [
|
|
333
|
+
...root.items,
|
|
334
|
+
...paletteItems.filter((item) => item.action.type !== "model-select"),
|
|
335
|
+
]
|
|
336
|
+
: page.items;
|
|
337
|
+
const items = visibleItems.map((item) => ({
|
|
338
|
+
value: item.value,
|
|
339
|
+
label: item.label,
|
|
340
|
+
description:
|
|
341
|
+
page === root && query.trim() && !("page" in item)
|
|
342
|
+
? `${item.category} › ${item.description}`
|
|
343
|
+
: item.description,
|
|
344
|
+
}));
|
|
345
|
+
const getText = (item: SelectItem) => `${item.label} ${item.description ?? ""}`;
|
|
434
346
|
const filtered = query
|
|
435
|
-
?
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
347
|
+
? page === modelPage
|
|
348
|
+
? partitionedFuzzyFilter(
|
|
349
|
+
items.filter((item) => item.label.startsWith(STAR)),
|
|
350
|
+
items.filter((item) => !item.label.startsWith(STAR)),
|
|
351
|
+
query,
|
|
352
|
+
getText,
|
|
353
|
+
)
|
|
354
|
+
: fuzzyFilter(items, query, getText)
|
|
355
|
+
: items;
|
|
356
|
+
selectList = new SelectList(filtered, Math.min(Math.max(filtered.length, 1), 15), listTheme);
|
|
357
|
+
const restoredIndex = filtered.findIndex(
|
|
358
|
+
(item) => item.value === current().selectedValue,
|
|
359
|
+
);
|
|
360
|
+
if (restoredIndex >= 0) selectList.setSelectedIndex(restoredIndex);
|
|
361
|
+
listHost.clear();
|
|
362
|
+
listHost.addChild(selectList);
|
|
363
|
+
selectList.onSelect = (selected) => {
|
|
364
|
+
current().selectedValue = selected.value;
|
|
365
|
+
const item = visibleItems.find((candidate) => candidate.value === selected.value);
|
|
366
|
+
if (!item) return;
|
|
367
|
+
if ("page" in item) {
|
|
368
|
+
if (item.value === "models" && !modelLoading && item.page.items.length === 0) {
|
|
369
|
+
modelLoading = true;
|
|
370
|
+
try {
|
|
371
|
+
const models = ctx.modelRegistry.getAvailable();
|
|
372
|
+
const scopedIds = new Set(
|
|
373
|
+
ctx.scopedModels.map((s) => `${s.model.provider}/${s.model.id}`),
|
|
374
|
+
);
|
|
375
|
+
const decorated = models
|
|
376
|
+
.map((m) => {
|
|
377
|
+
const value = `${m.provider}/${m.id}`;
|
|
378
|
+
const scoped = scopedIds.has(value);
|
|
379
|
+
return {
|
|
380
|
+
scoped,
|
|
381
|
+
item: {
|
|
382
|
+
value,
|
|
383
|
+
label: scoped ? `${STAR}${m.name}` : m.name,
|
|
384
|
+
description: m.provider,
|
|
385
|
+
category: "Built-in",
|
|
386
|
+
action: { type: "model", provider: m.provider, modelId: m.id } as CommandAction,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
})
|
|
390
|
+
.sort((a, b) =>
|
|
391
|
+
a.scoped === b.scoped
|
|
392
|
+
? a.item.label.localeCompare(b.item.label)
|
|
393
|
+
: a.scoped
|
|
394
|
+
? -1
|
|
395
|
+
: 1,
|
|
396
|
+
);
|
|
397
|
+
item.page.items = decorated.map((d) => d.item);
|
|
398
|
+
modelLoading = false;
|
|
399
|
+
if (item.page.items.length === 0) {
|
|
400
|
+
ctx.ui.notify("No models available.", "warning");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
pushPage(item.page);
|
|
404
|
+
} catch {
|
|
405
|
+
modelLoading = false;
|
|
406
|
+
ctx.ui.notify("Cannot enumerate models.", "warning");
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
pushPage(item.page);
|
|
411
|
+
} else {
|
|
412
|
+
done(item);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
selectList.onSelectionChange = (selected) => {
|
|
416
|
+
current().selectedValue = selected.value;
|
|
417
|
+
};
|
|
418
|
+
selectList.onCancel = () => done(null);
|
|
419
|
+
if (modelLoading && page.title === "Models") {
|
|
420
|
+
listHost.clear();
|
|
421
|
+
listHost.addChild(new Text(theme.fg("muted", "Loading models…"), 1, 0));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function pushPage(page: PalettePage) {
|
|
426
|
+
current().input = queryInput.getValue();
|
|
427
|
+
stack.push({ page, input: "" });
|
|
428
|
+
queryInput.setValue("");
|
|
429
|
+
rebuild();
|
|
430
|
+
tui.requestRender();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function popPage() {
|
|
434
|
+
if (stack.length <= 1) return;
|
|
435
|
+
stack.pop();
|
|
436
|
+
queryInput.setValue(current().input);
|
|
437
|
+
rebuild();
|
|
446
438
|
tui.requestRender();
|
|
447
439
|
}
|
|
448
440
|
|
|
449
|
-
|
|
450
|
-
container.addChild(
|
|
451
|
-
container.addChild(
|
|
452
|
-
|
|
453
|
-
);
|
|
441
|
+
rebuild();
|
|
442
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
443
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(root.title)), 1, 0));
|
|
444
|
+
container.addChild(queryInput);
|
|
445
|
+
container.addChild(listHost);
|
|
446
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter open/select • backspace on empty returns • esc closes"), 1, 0));
|
|
454
447
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
455
448
|
|
|
456
449
|
return {
|
|
450
|
+
get focused() {
|
|
451
|
+
return focused;
|
|
452
|
+
},
|
|
453
|
+
set focused(value: boolean) {
|
|
454
|
+
focused = value;
|
|
455
|
+
queryInput.focused = value;
|
|
456
|
+
},
|
|
457
457
|
render(w: number) {
|
|
458
|
-
|
|
458
|
+
const lines = container.render(w);
|
|
459
|
+
const title = stack.map((frame) => frame.page.title).join(" › ");
|
|
460
|
+
lines[1] = theme.fg("accent", theme.bold(title));
|
|
461
|
+
return lines;
|
|
459
462
|
},
|
|
460
463
|
invalidate() {
|
|
461
464
|
container.invalidate();
|
|
465
|
+
queryInput.invalidate();
|
|
466
|
+
selectList.invalidate();
|
|
462
467
|
},
|
|
463
468
|
handleInput(data: string) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (query.length > 0) {
|
|
467
|
-
query = query.slice(0, -1);
|
|
468
|
-
applyQuery();
|
|
469
|
-
}
|
|
469
|
+
if (matchesKey(data, Key.escape)) {
|
|
470
|
+
done(null);
|
|
470
471
|
return;
|
|
471
472
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
query += data;
|
|
475
|
-
applyQuery();
|
|
473
|
+
if (matchesKey(data, Key.backspace) && queryInput.getValue().length === 0) {
|
|
474
|
+
popPage();
|
|
476
475
|
return;
|
|
477
476
|
}
|
|
478
|
-
|
|
479
|
-
|
|
477
|
+
if (
|
|
478
|
+
matchesKey(data, Key.up) ||
|
|
479
|
+
matchesKey(data, Key.down) ||
|
|
480
|
+
matchesKey(data, Key.enter)
|
|
481
|
+
) {
|
|
482
|
+
selectList.handleInput(data);
|
|
483
|
+
} else {
|
|
484
|
+
const before = queryInput.getValue();
|
|
485
|
+
queryInput.handleInput(data);
|
|
486
|
+
if (queryInput.getValue() !== before) {
|
|
487
|
+
current().selectedValue = undefined;
|
|
488
|
+
rebuild();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
480
491
|
tui.requestRender();
|
|
481
492
|
},
|
|
482
493
|
};
|
|
483
494
|
},
|
|
484
|
-
{ overlay: true },
|
|
495
|
+
{ overlay: true, overlayOptions: { width: "70%", maxHeight: "80%", minWidth: 50 } },
|
|
485
496
|
);
|
|
486
497
|
|
|
487
498
|
if (!result) return;
|
|
@@ -503,6 +514,10 @@ async function showCommandPalette(pi: ExtensionAPI, ctx: ExtensionContext): Prom
|
|
|
503
514
|
}
|
|
504
515
|
break;
|
|
505
516
|
}
|
|
517
|
+
case "model-select": {
|
|
518
|
+
// Kept for compatibility with callers that may construct this action.
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
506
521
|
case "restore": {
|
|
507
522
|
if (savedEditorText !== null) {
|
|
508
523
|
ctx.ui.setEditorText(savedEditorText);
|
|
@@ -519,8 +534,15 @@ async function showCommandPalette(pi: ExtensionAPI, ctx: ExtensionContext): Prom
|
|
|
519
534
|
ctx.ui.setEditorText(action.text);
|
|
520
535
|
break;
|
|
521
536
|
}
|
|
522
|
-
case "model
|
|
523
|
-
|
|
537
|
+
case "model": {
|
|
538
|
+
const model = ctx.modelRegistry.find(action.provider, action.modelId);
|
|
539
|
+
if (model) {
|
|
540
|
+
const success = await pi.setModel(model);
|
|
541
|
+
ctx.ui.notify(
|
|
542
|
+
success ? `Model: ${action.provider}/${action.modelId}` : `No API key for ${action.provider}/${action.modelId}`,
|
|
543
|
+
success ? "info" : "error",
|
|
544
|
+
);
|
|
545
|
+
}
|
|
524
546
|
break;
|
|
525
547
|
}
|
|
526
548
|
case "compact": {
|