diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 63204d62b6f..7f7caf551f6 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -77,7 +77,7 @@ import { useConfig } from "../../config" import { useClipboard } from "../../context/clipboard" import { nextThinkingMode, reasoningSummary, type ThinkingMode } from "../../context/thinking" import { getScrollAcceleration } from "../../util/scroll" -import { collapseToolOutput } from "../../util/collapse-tool-output" +import { collapseShellOutput, collapseToolOutput } from "../../util/collapse-tool-output" import { Keymap, type KeymapCommand } from "../../context/keymap" import { usePathFormatter } from "../../context/path-format" import { useLocation } from "../../context/location" @@ -2968,14 +2968,9 @@ function ShellDisplay(props: { const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6)) const prefix = createMemo(() => (workdir() && workdir() !== "." ? `cd ${workdir()} && ` : "")) const input = createMemo(() => (props.command ? `${isRunning() ? "" : "$ "}${prefix()}${props.command}` : "")) - const content = createMemo(() => [input(), output()].filter(Boolean).join("\n\n")) - const collapsed = createMemo(() => collapseToolOutput(content(), maxLines, maxChars())) - const limited = createMemo(() => { - if (expanded() || !collapsed().overflow) return content() - return collapsed().output - }) - const limitedInput = createMemo(() => limited().slice(0, input().length)) - const limitedOutput = createMemo(() => limited().slice(Math.min(limited().length, input().length + 2))) + const collapsed = createMemo(() => collapseShellOutput(input(), output(), maxLines, maxChars())) + const limitedInput = createMemo(() => (expanded() ? input() : collapsed().input)) + const limitedOutput = createMemo(() => (expanded() ? output() : collapsed().output)) const expandable = createMemo(() => Boolean(props.shellID) || collapsed().overflow) const toggle = () => { const next = !expanded() diff --git a/packages/tui/src/util/collapse-tool-output.ts b/packages/tui/src/util/collapse-tool-output.ts index 7e2a534cd50..759be3f1862 100644 --- a/packages/tui/src/util/collapse-tool-output.ts +++ b/packages/tui/src/util/collapse-tool-output.ts @@ -19,3 +19,31 @@ export function collapseToolOutput(output: string, maxLines: number, maxChars: n return { output: preview, overflow: true } } + +export function collapseShellOutput(input: string, output: string, maxLines: number, maxChars: number) { + const content = [input, output].filter(Boolean).join("\n\n") + const collapsed = collapseToolOutput(content, maxLines, maxChars) + if (!collapsed.overflow) return { input, output, overflow: false } + if (!input) return { input, output: collapseTail(output, maxLines, maxChars), overflow: true } + if (!output) return { input: collapsed.output, output, overflow: true } + + const reserved = Math.max(1, Math.floor(maxChars / maxLines)) + const command = collapseToolOutput( + input, + Math.max(1, maxLines - 2), + Math.max(1, maxChars - reserved - 2), + ).output + const lines = Math.max(1, maxLines - command.split("\n").length - 1) + const chars = Math.max(1, maxChars - Array.from(command).length - 2) + return { input: command, output: collapseTail(output, lines, chars), overflow: true } +} + +function collapseTail(output: string, maxLines: number, maxChars: number) { + const lines = output.split("\n") + if (lines.length <= maxLines && Array.from(output).length <= maxChars) return output + + const preview = lines.slice(-maxLines).join("\n") + const visible = Array.from(preview) + if (visible.length < maxChars) return `…${preview}` + return `…${visible.slice(-Math.max(0, maxChars - 1)).join("")}` +}