fix: webview terminal output processing error (#3028)

This commit is contained in:
KJ7LNW 2025-05-11 17:51:22 -07:00 committed by GitHub
parent 4f727142ae
commit 7d75536741
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 50 deletions

View file

@ -149,9 +149,12 @@ export async function executeCommand(
const terminalProvider = terminalShellIntegrationDisabled ? "execa" : "vscode"
const clineProvider = await cline.providerRef.deref()
let accumulatedOutput = ""
const callbacks: RooTerminalCallbacks = {
onLine: async (output: string, process: RooTerminalProcess) => {
const status: CommandExecutionStatus = { executionId, status: "output", output }
onLine: async (lines: string, process: RooTerminalProcess) => {
accumulatedOutput += lines
const compressedOutput = Terminal.compressTerminalOutput(accumulatedOutput, terminalOutputLineLimit)
const status: CommandExecutionStatus = { executionId, status: "output", output: compressedOutput }
clineProvider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
if (runInBackground) {

View file

@ -1,6 +1,5 @@
import { HTMLAttributes, useCallback, useEffect, useMemo, useState } from "react"
import { useCallback, useState, memo } from "react"
import { useEvent } from "react-use"
import { Virtuoso } from "react-virtuoso"
import { ChevronDown, Skull } from "lucide-react"
import { CommandExecutionStatus, commandExecutionStatusSchema } from "@roo/schemas"
@ -19,6 +18,17 @@ interface CommandExecutionProps {
text?: string
}
const parseCommandAndOutput = (text: string) => {
const index = text.indexOf(COMMAND_OUTPUT_STRING)
if (index === -1) {
return { command: text, output: "" }
}
return {
command: text.slice(0, index),
output: text.slice(index + COMMAND_OUTPUT_STRING.length),
}
}
export const CommandExecution = ({ executionId, text }: CommandExecutionProps) => {
const { terminalShellIntegrationDisabled = false } = useExtensionState()
@ -27,13 +37,11 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
const [isExpanded, setIsExpanded] = useState(terminalShellIntegrationDisabled)
const [status, setStatus] = useState<CommandExecutionStatus | null>(null)
const [output, setOutput] = useState("")
const [command, setCommand] = useState(text)
const lines = useMemo(
() => [`$ ${command}`, ...output.split("\n").filter((line) => line.trim() !== "")],
[output, command],
)
const { command: initialCommand, output: initialOutput } = text
? parseCommandAndOutput(text)
: { command: "", output: "" }
const [output, setOutput] = useState(initialOutput)
const [command, setCommand] = useState(initialCommand)
const onMessage = useCallback(
(event: MessageEvent) => {
@ -55,7 +63,7 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
setStatus(data)
break
case "output":
setOutput((output) => output + data.output)
setOutput(data.output)
break
case "fallback":
setIsExpanded(true)
@ -72,22 +80,9 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
useEvent("message", onMessage)
useEffect(() => {
if (!status && text) {
const index = text.indexOf(COMMAND_OUTPUT_STRING)
if (index === -1) {
setCommand(text)
} else {
setCommand(text.slice(0, index))
setOutput(text.slice(index + COMMAND_OUTPUT_STRING.length))
}
}
}, [status, text])
return (
<div className="w-full bg-vscode-editor-background border border-vscode-border rounded-xs p-2">
<CodeBlock source={command} language="shell" />
<CodeBlock source={text ? parseCommandAndOutput(text).command : command} language="shell" />
<div className="flex flex-row items-center justify-between gap-2 px-1">
<div className="flex flex-row items-center gap-1">
{status?.status === "started" && (
@ -116,7 +111,7 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
<div className="whitespace-nowrap">Exited ({status.exitCode})</div>
</div>
)}
{lines.length > 0 && (
{output.length > 0 && (
<Button variant="ghost" size="icon" onClick={() => setIsExpanded(!isExpanded)}>
<ChevronDown
className={cn("size-4 transition-transform duration-300", {
@ -127,31 +122,21 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
)}
</div>
</div>
<div
className={cn("mt-1 pt-1 border-t border-border/25", { hidden: !isExpanded })}
style={{ height: Math.min((lines.length + 1) * 16, 200) }}>
{lines.length > 0 && (
<Virtuoso
className="h-full"
totalCount={lines.length}
itemContent={(i) => <Line className="text-sm">{lines[i]}</Line>}
followOutput="auto"
/>
)}
</div>
<MemoizedOutputContainer isExpanded={isExpanded} output={output} />
</div>
)
}
type LineProps = HTMLAttributes<HTMLDivElement>
const Line = ({ className, ...props }: LineProps) => {
return (
<div
className={cn("font-mono text-vscode-editor-foreground whitespace-pre-wrap break-words", className)}
{...props}
/>
)
}
CommandExecution.displayName = "CommandExecution"
const OutputContainer = ({ isExpanded, output }: { isExpanded: boolean; output: string }) => (
<div
className={cn("mt-1 pt-1 border-t border-border/25 overflow-hidden transition-[max-height] duration-300", {
"max-h-0": !isExpanded,
"max-h-[100%]": isExpanded,
})}>
{output.length > 0 && <CodeBlock source={output} language="log" />}
</div>
)
const MemoizedOutputContainer = memo(OutputContainer)