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:
Marek Pazik
2026-04-20 20:35:30 +08:00
committed by GitHub
parent d554409b1f
commit d4e2e563ae
7 changed files with 163 additions and 25 deletions

View File

@@ -4,7 +4,6 @@ import {
type Focusable,
getKeybindings,
Input,
matchesKey,
Spacer,
Text,
TruncatedText,
@@ -938,39 +937,39 @@ class TreeList implements Component {
} else {
this.onCancel?.();
}
} else if (matchesKey(keyData, "ctrl+d")) {
} else if (kb.matches(keyData, "app.tree.filter.default")) {
// Direct filter: default
this.filterMode = "default";
this.foldedNodes.clear();
this.applyFilter();
} else if (matchesKey(keyData, "ctrl+t")) {
} else if (kb.matches(keyData, "app.tree.filter.noTools")) {
// Toggle filter: no-tools ↔ default
this.filterMode = this.filterMode === "no-tools" ? "default" : "no-tools";
this.foldedNodes.clear();
this.applyFilter();
} else if (matchesKey(keyData, "ctrl+u")) {
} else if (kb.matches(keyData, "app.tree.filter.userOnly")) {
// Toggle filter: user-only ↔ default
this.filterMode = this.filterMode === "user-only" ? "default" : "user-only";
this.foldedNodes.clear();
this.applyFilter();
} else if (matchesKey(keyData, "ctrl+l")) {
} else if (kb.matches(keyData, "app.tree.filter.labeledOnly")) {
// Toggle filter: labeled-only ↔ default
this.filterMode = this.filterMode === "labeled-only" ? "default" : "labeled-only";
this.foldedNodes.clear();
this.applyFilter();
} else if (matchesKey(keyData, "ctrl+a")) {
} else if (kb.matches(keyData, "app.tree.filter.all")) {
// Toggle filter: all ↔ default
this.filterMode = this.filterMode === "all" ? "default" : "all";
this.foldedNodes.clear();
this.applyFilter();
} else if (matchesKey(keyData, "shift+ctrl+o")) {
} else if (kb.matches(keyData, "app.tree.filter.cycleBackward")) {
// Cycle filter backwards
const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
const currentIndex = modes.indexOf(this.filterMode);
this.filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
this.foldedNodes.clear();
this.applyFilter();
} else if (matchesKey(keyData, "ctrl+o")) {
} else if (kb.matches(keyData, "app.tree.filter.cycleForward")) {
// Cycle filter forwards: default → no-tools → user-only → labeled-only → all → default
const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
const currentIndex = modes.indexOf(this.filterMode);
@@ -1178,11 +1177,19 @@ export class TreeSelectorComponent extends Container implements Focusable {
this.addChild(new Spacer(1));
this.addChild(new DynamicBorder());
this.addChild(new Text(theme.bold(" Session Tree"), 1, 0));
const filterKeys = [
keyText("app.tree.filter.default"),
keyText("app.tree.filter.noTools"),
keyText("app.tree.filter.userOnly"),
keyText("app.tree.filter.labeledOnly"),
keyText("app.tree.filter.all"),
].join("/");
const cycleKeys = `${keyText("app.tree.filter.cycleForward")}/${keyText("app.tree.filter.cycleBackward")}`;
this.addChild(
new TruncatedText(
theme.fg(
"muted",
` ↑/↓: move. ←/→: page. ^←/^→ or Alt+←/Alt+→: fold/branch. ${keyText("app.tree.editLabel")}: label. ^D/^T/^U/^L/^A: filters (^O/⇧^O cycle). ${keyText("app.tree.toggleLabelTimestamp")}: label time`,
` ↑/↓: move. ←/→: page. ^←/^→ or Alt+←/Alt+→: fold/branch. ${keyText("app.tree.editLabel")}: label. ${filterKeys}: filters (${cycleKeys} cycle). ${keyText("app.tree.toggleLabelTimestamp")}: label time`,
),
0,
0,