From 1f39cc776ac9df91ace884c2f04c2ac08fca9032 Mon Sep 17 00:00:00 2001 From: lajarre Date: Thu, 5 Mar 2026 21:25:58 +0000 Subject: [PATCH] feat(coding-agent): add treeFilterMode setting for /tree default filter (#1852) Add configurable initial filter mode for the session tree navigator. Users who always switch to a specific filter (e.g. no-tools via Ctrl+T) can now set it as default in settings. Same pattern as doubleEscapeAction (#404). Filter infra from #747. --- packages/coding-agent/docs/settings.md | 1 + packages/coding-agent/src/core/settings-manager.ts | 13 +++++++++++++ .../interactive/components/settings-selector.ts | 14 ++++++++++++++ .../modes/interactive/components/tree-selector.ts | 7 +++++-- .../src/modes/interactive/interactive-mode.ts | 6 ++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/docs/settings.md b/packages/coding-agent/docs/settings.md index acf036e2..3c65dacf 100644 --- a/packages/coding-agent/docs/settings.md +++ b/packages/coding-agent/docs/settings.md @@ -42,6 +42,7 @@ Edit directly or use `/settings` for common options. | `quietStartup` | boolean | `false` | Hide startup header | | `collapseChangelog` | boolean | `false` | Show condensed changelog after updates | | `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` | +| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` | | `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) | | `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) | | `showHardwareCursor` | boolean | `false` | Show terminal cursor | diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index 7cdd1048..e9996240 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -87,6 +87,7 @@ export interface Settings { images?: ImageSettings; enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag) doubleEscapeAction?: "fork" | "tree" | "none"; // Action for double-escape with empty editor (default: "tree") + treeFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; // Default filter when opening /tree thinkingBudgets?: ThinkingBudgetsSettings; // Custom token budgets for thinking levels editorPaddingX?: number; // Horizontal padding for input editor (default: 0) autocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5) @@ -866,6 +867,18 @@ export class SettingsManager { this.save(); } + getTreeFilterMode(): "default" | "no-tools" | "user-only" | "labeled-only" | "all" { + const mode = this.settings.treeFilterMode; + const valid = ["default", "no-tools", "user-only", "labeled-only", "all"]; + return mode && valid.includes(mode) ? mode : "default"; + } + + setTreeFilterMode(mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all"): void { + this.globalSettings.treeFilterMode = mode; + this.markModified("treeFilterMode"); + this.save(); + } + getShowHardwareCursor(): boolean { return this.settings.showHardwareCursor ?? process.env.PI_HARDWARE_CURSOR === "1"; } diff --git a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts index f5304f62..07a4bd22 100644 --- a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts @@ -38,6 +38,7 @@ export interface SettingsConfig { hideThinkingBlock: boolean; collapseChangelog: boolean; doubleEscapeAction: "fork" | "tree" | "none"; + treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; showHardwareCursor: boolean; editorPaddingX: number; autocompleteMaxVisible: number; @@ -60,6 +61,7 @@ export interface SettingsCallbacks { onHideThinkingBlockChange: (hidden: boolean) => void; onCollapseChangelogChange: (collapsed: boolean) => void; onDoubleEscapeActionChange: (action: "fork" | "tree" | "none") => void; + onTreeFilterModeChange: (mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all") => void; onShowHardwareCursorChange: (enabled: boolean) => void; onEditorPaddingXChange: (padding: number) => void; onAutocompleteMaxVisibleChange: (maxVisible: number) => void; @@ -200,6 +202,13 @@ export class SettingsSelectorComponent extends Container { currentValue: config.doubleEscapeAction, values: ["tree", "fork", "none"], }, + { + id: "tree-filter-mode", + label: "Tree filter mode", + description: "Default filter when opening /tree", + currentValue: config.treeFilterMode, + values: ["default", "no-tools", "user-only", "labeled-only", "all"], + }, { id: "thinking", label: "Thinking level", @@ -379,6 +388,11 @@ export class SettingsSelectorComponent extends Container { case "double-escape-action": callbacks.onDoubleEscapeActionChange(newValue as "fork" | "tree"); break; + case "tree-filter-mode": + callbacks.onTreeFilterModeChange( + newValue as "default" | "no-tools" | "user-only" | "labeled-only" | "all", + ); + break; case "show-hardware-cursor": callbacks.onShowHardwareCursorChange(newValue === "true"); break; diff --git a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts index 4f049239..74be0f44 100644 --- a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts @@ -37,7 +37,7 @@ interface FlatNode { } /** Filter mode for tree display */ -type FilterMode = "default" | "no-tools" | "user-only" | "labeled-only" | "all"; +export type FilterMode = "default" | "no-tools" | "user-only" | "labeled-only" | "all"; /** * Tree list component with selection and ASCII art visualization @@ -70,9 +70,11 @@ class TreeList implements Component { currentLeafId: string | null, maxVisibleLines: number, initialSelectedId?: string, + initialFilterMode?: FilterMode, ) { this.currentLeafId = currentLeafId; this.maxVisibleLines = maxVisibleLines; + this.filterMode = initialFilterMode ?? "default"; this.multipleRoots = tree.length > 1; this.flatNodes = this.flattenTree(tree); this.buildActivePath(); @@ -1001,13 +1003,14 @@ export class TreeSelectorComponent extends Container implements Focusable { onCancel: () => void, onLabelChange?: (entryId: string, label: string | undefined) => void, initialSelectedId?: string, + initialFilterMode?: FilterMode, ) { super(); this.onLabelChangeCallback = onLabelChange; const maxVisibleLines = Math.max(5, Math.floor(terminalHeight / 2)); - this.treeList = new TreeList(tree, currentLeafId, maxVisibleLines, initialSelectedId); + this.treeList = new TreeList(tree, currentLeafId, maxVisibleLines, initialSelectedId, initialFilterMode); this.treeList.onSelect = onSelect; this.treeList.onCancel = onCancel; this.treeList.onLabelEdit = (entryId, currentLabel) => this.showLabelInput(entryId, currentLabel); diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 50e7197d..65d3e84d 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -3045,6 +3045,7 @@ export class InteractiveMode { hideThinkingBlock: this.hideThinkingBlock, collapseChangelog: this.settingsManager.getCollapseChangelog(), doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(), + treeFilterMode: this.settingsManager.getTreeFilterMode(), showHardwareCursor: this.settingsManager.getShowHardwareCursor(), editorPaddingX: this.settingsManager.getEditorPaddingX(), autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(), @@ -3124,6 +3125,9 @@ export class InteractiveMode { onDoubleEscapeActionChange: (action) => { this.settingsManager.setDoubleEscapeAction(action); }, + onTreeFilterModeChange: (mode) => { + this.settingsManager.setTreeFilterMode(mode); + }, onShowHardwareCursorChange: (enabled) => { this.settingsManager.setShowHardwareCursor(enabled); this.ui.setShowHardwareCursor(enabled); @@ -3413,6 +3417,7 @@ export class InteractiveMode { private showTreeSelector(initialSelectedId?: string): void { const tree = this.sessionManager.getTree(); const realLeafId = this.sessionManager.getLeafId(); + const initialFilterMode = this.settingsManager.getTreeFilterMode(); if (tree.length === 0) { this.showStatus("No entries in session"); @@ -3531,6 +3536,7 @@ export class InteractiveMode { this.ui.requestRender(); }, initialSelectedId, + initialFilterMode, ); return { component: selector, focus: selector }; });