mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 10:16:03 +00:00
fix(tui): normalize shell progress output
This commit is contained in:
@@ -66,7 +66,7 @@ import { useConfig } from "../../config"
|
|||||||
import { useClipboard } from "../../context/clipboard"
|
import { useClipboard } from "../../context/clipboard"
|
||||||
import { nextThinkingMode, reasoningSummary, type ThinkingMode } from "../../context/thinking"
|
import { nextThinkingMode, reasoningSummary, type ThinkingMode } from "../../context/thinking"
|
||||||
import { getScrollAcceleration } from "../../util/scroll"
|
import { getScrollAcceleration } from "../../util/scroll"
|
||||||
import { collapseToolOutput } from "../../util/collapse-tool-output"
|
import { collapseToolOutput, normalizeShellOutput } from "../../util/collapse-tool-output"
|
||||||
import { usePluginRuntime } from "../../plugin/runtime"
|
import { usePluginRuntime } from "../../plugin/runtime"
|
||||||
import { PluginSlot } from "../../plugin/context"
|
import { PluginSlot } from "../../plugin/context"
|
||||||
import { Keymap, type KeymapCommand } from "../../context/keymap"
|
import { Keymap, type KeymapCommand } from "../../context/keymap"
|
||||||
@@ -1607,21 +1607,24 @@ function RevertMessage(props: {
|
|||||||
|
|
||||||
function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "shell" }> }) {
|
function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "shell" }> }) {
|
||||||
const { themeV2 } = useTheme().contextual("elevated")
|
const { themeV2 } = useTheme().contextual("elevated")
|
||||||
const ctx = use()
|
|
||||||
const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
|
const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
|
||||||
const [expanded, setExpanded] = createSignal(false)
|
|
||||||
const maxLines = 10
|
|
||||||
const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6))
|
|
||||||
const collapsed = createMemo(() => collapseToolOutput(output(), maxLines, maxChars()))
|
|
||||||
const limited = createMemo(() => (expanded() ? output() : collapsed().output))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockTool onClick={collapsed().overflow ? () => setExpanded((value) => !value) : undefined}>
|
<box
|
||||||
|
border={["left"]}
|
||||||
|
paddingTop={1}
|
||||||
|
paddingBottom={1}
|
||||||
|
paddingLeft={2}
|
||||||
|
gap={1}
|
||||||
|
backgroundColor={themeV2.background()}
|
||||||
|
customBorderChars={SplitBorder.customBorderChars}
|
||||||
|
borderColor={themeV2.background()}
|
||||||
|
>
|
||||||
<text fg={themeV2.text()}>$ {props.message.command}</text>
|
<text fg={themeV2.text()}>$ {props.message.command}</text>
|
||||||
<Show when={output()}>
|
<Show when={output()}>
|
||||||
<text fg={themeV2.text.subdued()}>{limited()}</text>
|
<text fg={themeV2.text.subdued()}>{output()}</text>
|
||||||
</Show>
|
</Show>
|
||||||
</BlockTool>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2445,7 +2448,7 @@ function Shell(props: ToolProps) {
|
|||||||
limit: 1024 * 1024,
|
limit: 1024 * 1024,
|
||||||
location: location ? { directory: location.directory, workspace: location.workspaceID } : undefined,
|
location: location ? { directory: location.directory, workspace: location.workspaceID } : undefined,
|
||||||
})
|
})
|
||||||
.then((response) => setBackgroundOutput(stripAnsi(response.data.output.trim())))
|
.then((response) => setBackgroundOutput(normalizeShellOutput(response.data.output.trim())))
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
@@ -2458,7 +2461,7 @@ function Shell(props: ToolProps) {
|
|||||||
if (props.part.state.status === "streaming") return ""
|
if (props.part.state.status === "streaming") return ""
|
||||||
if (shellID()) return expanded() ? backgroundOutput() : ""
|
if (shellID()) return expanded() ? backgroundOutput() : ""
|
||||||
const content = props.part.state.content[0]
|
const content = props.part.state.content[0]
|
||||||
return stripAnsi(content?.type === "text" ? content.text.trim() : "")
|
return normalizeShellOutput(content?.type === "text" ? content.text.trim() : "")
|
||||||
})
|
})
|
||||||
const maxLines = 10
|
const maxLines = 10
|
||||||
const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6))
|
const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6))
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import stripAnsi from "strip-ansi"
|
||||||
|
|
||||||
export function collapseToolOutput(output: string, maxLines: number, maxChars: number) {
|
export function collapseToolOutput(output: string, maxLines: number, maxChars: number) {
|
||||||
const lines = output.split("\n")
|
const lines = output.split("\n")
|
||||||
if (lines.length <= maxLines && Array.from(output).length <= maxChars) {
|
if (lines.length <= maxLines && Array.from(output).length <= maxChars) {
|
||||||
@@ -19,3 +21,7 @@ export function collapseToolOutput(output: string, maxLines: number, maxChars: n
|
|||||||
|
|
||||||
return { output: preview, overflow: true }
|
return { output: preview, overflow: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeShellOutput(output: string) {
|
||||||
|
return stripAnsi(output).replace(/\r\n?/g, "\n")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import { collapseToolOutput } from "../../../src/util/collapse-tool-output"
|
import { collapseToolOutput, normalizeShellOutput } from "../../../src/util/collapse-tool-output"
|
||||||
|
|
||||||
test("limits command input and output to the same line budget", () => {
|
test("limits command input and output to the same line budget", () => {
|
||||||
const command = Array.from({ length: 8 }, (_, index) => `command ${index + 1}`).join("\n")
|
const command = Array.from({ length: 8 }, (_, index) => `command ${index + 1}`).join("\n")
|
||||||
@@ -12,3 +12,13 @@ test("limits command input and output to the same line budget", () => {
|
|||||||
expect(collapsed.output).toContain("command 8\n\noutput 1…")
|
expect(collapsed.output).toContain("command 8\n\noutput 1…")
|
||||||
expect(collapsed.output).not.toContain("output 2")
|
expect(collapsed.output).not.toContain("output 2")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("normalizes carriage-return shell progress before collapsing", () => {
|
||||||
|
const output = Array.from({ length: 30 }, (_, index) => `progress ${index + 1}`).join("\r")
|
||||||
|
const collapsed = collapseToolOutput(normalizeShellOutput(output), 10, 1_000)
|
||||||
|
|
||||||
|
expect(collapsed.overflow).toBe(true)
|
||||||
|
expect(collapsed.output.split("\n")).toHaveLength(10)
|
||||||
|
expect(collapsed.output).toContain("progress 10…")
|
||||||
|
expect(collapsed.output).not.toContain("progress 11")
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user