From e4df5d14b5a7d47512cdbd798c4712e796e12e81 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 30 Dec 2025 01:40:13 +0100 Subject: [PATCH] Tree selector improvements: active line highlight and tool filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add inverse background highlight for selected/active line - Add 'no-tools' filter mode to hide tool results - Add isShiftCtrlO to cycle filters backwards - Filter order: default → no-tools → user-only → labeled-only → all --- .../interactive/components/tree-selector.ts | 23 +++++++++++++++---- packages/tui/src/index.ts | 1 + packages/tui/src/keys.ts | 8 +++++++ 3 files changed, 28 insertions(+), 4 deletions(-) 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 15945373..fc213933 100644 --- a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts @@ -11,6 +11,7 @@ import { isCtrlO, isEnter, isEscape, + isShiftCtrlO, Spacer, Text, TruncatedText, @@ -42,7 +43,7 @@ interface FlatNode { } /** Filter mode for tree display */ -type FilterMode = "default" | "user-only" | "labeled-only" | "all"; +type FilterMode = "default" | "no-tools" | "user-only" | "labeled-only" | "all"; /** * Tree list component with selection and ASCII art visualization @@ -265,6 +266,9 @@ class TreeList implements Component { passesFilter = (entry.type === "message" && entry.message.role === "user") || (entry.type === "custom_message" && entry.display); + } else if (this.filterMode === "no-tools") { + // Hide tool results + passesFilter = !(entry.type === "message" && entry.message.role === "toolResult"); } else if (this.filterMode === "labeled-only") { passesFilter = flatNode.node.label !== undefined; } else if (this.filterMode !== "all") { @@ -373,6 +377,8 @@ class TreeList implements Component { private getFilterLabel(): string { switch (this.filterMode) { + case "no-tools": + return " [no-tools]"; case "user-only": return " [user]"; case "labeled-only": @@ -456,7 +462,10 @@ class TreeList implements Component { const label = flatNode.node.label ? theme.fg("warning", `[${flatNode.node.label}] `) : ""; const content = this.getEntryDisplayText(flatNode.node, isSelected); - const line = cursor + theme.fg("dim", prefix) + pathMarker + label + content; + let line = cursor + theme.fg("dim", prefix) + pathMarker + label + content; + if (isSelected) { + line = theme.inverse(line); + } lines.push(truncateToWidth(line, width)); } @@ -663,9 +672,15 @@ class TreeList implements Component { } } else if (isCtrlC(keyData)) { this.onCancel?.(); + } else if (isShiftCtrlO(keyData)) { + // 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.applyFilter(); } else if (isCtrlO(keyData)) { - // Cycle filter: default → user-only → labeled-only → all → default - const modes: FilterMode[] = ["default", "user-only", "labeled-only", "all"]; + // 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); this.filterMode = modes[(currentIndex + 1) % modes.length]; this.applyFilter(); diff --git a/packages/tui/src/index.ts b/packages/tui/src/index.ts index 2c741eb8..4d4d895c 100644 --- a/packages/tui/src/index.ts +++ b/packages/tui/src/index.ts @@ -51,6 +51,7 @@ export { isEscape, isHome, isShiftCtrlD, + isShiftCtrlO, isShiftCtrlP, isShiftEnter, isShiftTab, diff --git a/packages/tui/src/keys.ts b/packages/tui/src/keys.ts index 12965fec..da8cf699 100644 --- a/packages/tui/src/keys.ts +++ b/packages/tui/src/keys.ts @@ -304,6 +304,14 @@ export function isCtrlO(data: string): boolean { return data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl); } +/** + * Check if input matches Shift+Ctrl+O (Kitty protocol only). + * Ignores lock key bits. + */ +export function isShiftCtrlO(data: string): boolean { + return matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl); +} + /** * Check if input matches Ctrl+P (raw byte or Kitty protocol). * Ignores lock key bits.