feat(coding-agent): make scoped models and tree filter shortcuts configurable (#3343)
- Add app.models.{save,enableAll,clearAll,toggleProvider,reorderUp,reorderDown}
- Add app.tree.filter.{default,noTools,userOnly,labeledOnly,all,cycleForward,cycleBackward}
- Migrate scoped-models-selector cancel to tui.select.cancel
- Prefer reserved actions on key collision in extension shortcut conflict check
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from "@mariozechner/pi-tui";
|
||||
import { theme } from "../theme/theme.js";
|
||||
import { DynamicBorder } from "./dynamic-border.js";
|
||||
import { keyText } from "./keybinding-hints.js";
|
||||
|
||||
// EnabledIds: null = all enabled (no filter), string[] = explicit ordered list
|
||||
type EnabledIds = string[] | null;
|
||||
@@ -126,7 +127,9 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
this.addChild(new DynamicBorder());
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("accent", theme.bold("Model Configuration")), 0, 0));
|
||||
this.addChild(new Text(theme.fg("muted", "Session-only. Ctrl+S to save to settings."), 0, 0));
|
||||
this.addChild(
|
||||
new Text(theme.fg("muted", `Session-only. ${keyText("app.models.save")} to save to settings.`), 0, 0),
|
||||
);
|
||||
this.addChild(new Spacer(1));
|
||||
|
||||
// Search input
|
||||
@@ -162,7 +165,15 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
const enabledCount = this.enabledIds?.length ?? this.allIds.length;
|
||||
const allEnabled = this.enabledIds === null;
|
||||
const countText = allEnabled ? "all enabled" : `${enabledCount}/${this.allIds.length} enabled`;
|
||||
const parts = ["Enter toggle", "^A all", "^X clear", "^P provider", "Alt+↑↓ reorder", "^S save", countText];
|
||||
const parts = [
|
||||
`${keyText("tui.select.confirm")} toggle`,
|
||||
`${keyText("app.models.enableAll")} all`,
|
||||
`${keyText("app.models.clearAll")} clear`,
|
||||
`${keyText("app.models.toggleProvider")} provider`,
|
||||
`${keyText("app.models.reorderUp")}/${keyText("app.models.reorderDown")} reorder`,
|
||||
`${keyText("app.models.save")} save`,
|
||||
countText,
|
||||
];
|
||||
return this.isDirty
|
||||
? theme.fg("dim", ` ${parts.join(" · ")} `) + theme.fg("warning", "(unsaved)")
|
||||
: theme.fg("dim", ` ${parts.join(" · ")}`);
|
||||
@@ -237,12 +248,14 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt+Up/Down - Reorder enabled models
|
||||
if (matchesKey(data, Key.alt("up")) || matchesKey(data, Key.alt("down"))) {
|
||||
// Reorder enabled models
|
||||
const reorderUp = kb.matches(data, "app.models.reorderUp");
|
||||
const reorderDown = kb.matches(data, "app.models.reorderDown");
|
||||
if (reorderUp || reorderDown) {
|
||||
if (this.enabledIds === null) return;
|
||||
const item = this.filteredItems[this.selectedIndex];
|
||||
if (item && isEnabled(this.enabledIds, item.fullId)) {
|
||||
const delta = matchesKey(data, Key.alt("up")) ? -1 : 1;
|
||||
const delta = reorderUp ? -1 : 1;
|
||||
const currentIndex = this.enabledIds.indexOf(item.fullId);
|
||||
const newIndex = currentIndex + delta;
|
||||
// Only move if within bounds
|
||||
@@ -258,7 +271,7 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
}
|
||||
|
||||
// Toggle on Enter
|
||||
if (matchesKey(data, Key.enter)) {
|
||||
if (kb.matches(data, "tui.select.confirm")) {
|
||||
const item = this.filteredItems[this.selectedIndex];
|
||||
if (item) {
|
||||
this.enabledIds = toggle(this.enabledIds, item.fullId);
|
||||
@@ -269,8 +282,8 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+A - Enable all (filtered if search active, otherwise all)
|
||||
if (matchesKey(data, Key.ctrl("a"))) {
|
||||
// Enable all (filtered if search active, otherwise all)
|
||||
if (kb.matches(data, "app.models.enableAll")) {
|
||||
const targetIds = this.searchInput.getValue() ? this.filteredItems.map((i) => i.fullId) : undefined;
|
||||
this.enabledIds = enableAll(this.enabledIds, this.allIds, targetIds);
|
||||
this.isDirty = true;
|
||||
@@ -279,8 +292,8 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+X - Clear all (filtered if search active, otherwise all)
|
||||
if (matchesKey(data, Key.ctrl("x"))) {
|
||||
// Clear all (filtered if search active, otherwise all)
|
||||
if (kb.matches(data, "app.models.clearAll")) {
|
||||
const targetIds = this.searchInput.getValue() ? this.filteredItems.map((i) => i.fullId) : undefined;
|
||||
this.enabledIds = clearAll(this.enabledIds, this.allIds, targetIds);
|
||||
this.isDirty = true;
|
||||
@@ -289,8 +302,8 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+P - Toggle provider of current item
|
||||
if (matchesKey(data, Key.ctrl("p"))) {
|
||||
// Toggle provider of current item
|
||||
if (kb.matches(data, "app.models.toggleProvider")) {
|
||||
const item = this.filteredItems[this.selectedIndex];
|
||||
if (item) {
|
||||
const provider = item.model.provider;
|
||||
@@ -306,8 +319,8 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+S - Save/persist to settings
|
||||
if (matchesKey(data, Key.ctrl("s"))) {
|
||||
// Save/persist to settings
|
||||
if (kb.matches(data, "app.models.save")) {
|
||||
this.callbacks.onPersist(this.enabledIds === null ? null : [...this.enabledIds]);
|
||||
this.isDirty = false;
|
||||
this.footerText.setText(this.getFooterText());
|
||||
|
||||
Reference in New Issue
Block a user