fix: use configurable expandTools keybinding instead of hardcoded ctrl+o (#717)

- Add expandTools to EditorAction in pi-tui so components can access it
- Update bash-execution, compaction-summary-message, branch-summary-message,
  and tool-execution to use getEditorKeybindings().getKeys('expandTools')
- Pass expandTools config to setEditorKeybindings in KeybindingsManager.create()
- Style keybinding with 'dim' color, description with 'muted' (matches startup hints)
This commit is contained in:
Danila Poyarkov 2026-01-14 12:27:22 +03:00 committed by GitHub
parent 30a126f2bd
commit 7f2d2f106e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 17 deletions

View file

@ -111,9 +111,10 @@ export class KeybindingsManager {
const manager = new KeybindingsManager(config); const manager = new KeybindingsManager(config);
// Set up editor keybindings globally // Set up editor keybindings globally
// Include both editor actions and expandTools (shared between app and editor)
const editorConfig: EditorKeybindingsConfig = {}; const editorConfig: EditorKeybindingsConfig = {};
for (const [action, keys] of Object.entries(config)) { for (const [action, keys] of Object.entries(config)) {
if (!isAppAction(action)) { if (!isAppAction(action) || action === "expandTools") {
editorConfig[action as EditorAction] = keys; editorConfig[action as EditorAction] = keys;
} }
} }

View file

@ -2,7 +2,7 @@
* Component for displaying bash command execution with streaming output. * Component for displaying bash command execution with streaming output.
*/ */
import { Container, Loader, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; import { Container, getEditorKeybindings, Loader, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
import stripAnsi from "strip-ansi"; import stripAnsi from "strip-ansi";
import { import {
DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES,
@ -166,10 +166,15 @@ export class BashExecutionComponent extends Container {
// Show how many lines are hidden (collapsed preview) // Show how many lines are hidden (collapsed preview)
if (hiddenLineCount > 0) { if (hiddenLineCount > 0) {
const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
if (this.expanded) { if (this.expanded) {
statusParts.push(theme.fg("dim", "(ctrl+o to collapse)")); statusParts.push(`(${theme.fg("dim", expandKey)}${theme.fg("muted", " to collapse")})`);
} else { } else {
statusParts.push(theme.fg("dim", `... ${hiddenLineCount} more lines (ctrl+o to expand)`)); statusParts.push(
theme.fg("muted", `... ${hiddenLineCount} more lines (`) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)"),
);
} }
} }

View file

@ -1,4 +1,4 @@
import { Box, Markdown, Spacer, Text } from "@mariozechner/pi-tui"; import { Box, getEditorKeybindings, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
import type { BranchSummaryMessage } from "../../../core/messages.js"; import type { BranchSummaryMessage } from "../../../core/messages.js";
import { getMarkdownTheme, theme } from "../theme/theme.js"; import { getMarkdownTheme, theme } from "../theme/theme.js";
@ -41,7 +41,16 @@ export class BranchSummaryMessageComponent extends Box {
}), }),
); );
} else { } else {
this.addChild(new Text(theme.fg("customMessageText", "Branch summary (ctrl+o to expand)"), 0, 0)); const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
this.addChild(
new Text(
theme.fg("customMessageText", "Branch summary (") +
theme.fg("dim", expandKey) +
theme.fg("customMessageText", " to expand)"),
0,
0,
),
);
} }
} }
} }

View file

@ -1,4 +1,4 @@
import { Box, Markdown, Spacer, Text } from "@mariozechner/pi-tui"; import { Box, getEditorKeybindings, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
import type { CompactionSummaryMessage } from "../../../core/messages.js"; import type { CompactionSummaryMessage } from "../../../core/messages.js";
import { getMarkdownTheme, theme } from "../theme/theme.js"; import { getMarkdownTheme, theme } from "../theme/theme.js";
@ -42,8 +42,15 @@ export class CompactionSummaryMessageComponent extends Box {
}), }),
); );
} else { } else {
const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
this.addChild( this.addChild(
new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (ctrl+o to expand)`), 0, 0), new Text(
theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
theme.fg("dim", expandKey) +
theme.fg("customMessageText", " to expand)"),
0,
0,
),
); );
} }
} }

View file

@ -3,6 +3,7 @@ import {
Box, Box,
Container, Container,
getCapabilities, getCapabilities,
getEditorKeybindings,
getImageDimensions, getImageDimensions,
Image, Image,
imageFallback, imageFallback,
@ -374,9 +375,15 @@ export class ToolExecutionComponent extends Container {
cachedSkipped = result.skippedCount; cachedSkipped = result.skippedCount;
cachedWidth = width; cachedWidth = width;
} }
return cachedSkipped && cachedSkipped > 0 if (cachedSkipped && cachedSkipped > 0) {
? ["", theme.fg("toolOutput", `... (${cachedSkipped} earlier lines)`), ...cachedLines] const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
: cachedLines; const hint =
theme.fg("muted", `... (${cachedSkipped} earlier lines, `) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)");
return ["", hint, ...cachedLines];
}
return cachedLines;
}, },
invalidate: () => { invalidate: () => {
cachedWidth = undefined; cachedWidth = undefined;
@ -469,7 +476,11 @@ export class ToolExecutionComponent extends Container {
.map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line)))) .map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
.join("\n"); .join("\n");
if (remaining > 0) { if (remaining > 0) {
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`); const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
text +=
theme.fg("muted", `\n... (${remaining} more lines, `) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)");
} }
const truncation = this.result.details?.truncation; const truncation = this.result.details?.truncation;
@ -526,7 +537,11 @@ export class ToolExecutionComponent extends Container {
.map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line)))) .map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
.join("\n"); .join("\n");
if (remaining > 0) { if (remaining > 0) {
text += theme.fg("toolOutput", `\n... (${remaining} more lines, ${totalLines} total)`); const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
text +=
theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total, `) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)");
} }
} }
} else if (this.toolName === "edit") { } else if (this.toolName === "edit") {
@ -584,7 +599,11 @@ export class ToolExecutionComponent extends Container {
text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`; text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
if (remaining > 0) { if (remaining > 0) {
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`); const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
text +=
theme.fg("muted", `\n... (${remaining} more lines, `) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)");
} }
} }
@ -625,7 +644,11 @@ export class ToolExecutionComponent extends Container {
text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`; text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
if (remaining > 0) { if (remaining > 0) {
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`); const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
text +=
theme.fg("muted", `\n... (${remaining} more lines, `) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)");
} }
} }
@ -670,7 +693,11 @@ export class ToolExecutionComponent extends Container {
text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`; text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
if (remaining > 0) { if (remaining > 0) {
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`); const expandKey = getEditorKeybindings().getKeys("expandTools")[0]!;
text +=
theme.fg("muted", `\n... (${remaining} more lines, `) +
theme.fg("dim", expandKey) +
theme.fg("muted", " to expand)");
} }
} }

View file

@ -31,7 +31,9 @@ export type EditorAction =
| "selectConfirm" | "selectConfirm"
| "selectCancel" | "selectCancel"
// Clipboard // Clipboard
| "copy"; | "copy"
// Tool output
| "expandTools";
// Re-export KeyId from keys.ts // Re-export KeyId from keys.ts
export type { KeyId }; export type { KeyId };
@ -75,6 +77,8 @@ export const DEFAULT_EDITOR_KEYBINDINGS: Required<EditorKeybindingsConfig> = {
selectCancel: ["escape", "ctrl+c"], selectCancel: ["escape", "ctrl+c"],
// Clipboard // Clipboard
copy: "ctrl+c", copy: "ctrl+c",
// Tool output
expandTools: "ctrl+o",
}; };
/** /**