diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 82b4bbe5c0..6f5fc714a8 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -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) { diff --git a/webview-ui/src/components/chat/CommandExecution.tsx b/webview-ui/src/components/chat/CommandExecution.tsx index 8e648f26cc..79b0306674 100644 --- a/webview-ui/src/components/chat/CommandExecution.tsx +++ b/webview-ui/src/components/chat/CommandExecution.tsx @@ -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(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 (
- +
{status?.status === "started" && ( @@ -116,7 +111,7 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
Exited ({status.exitCode})
)} - {lines.length > 0 && ( + {output.length > 0 && (
-
- {lines.length > 0 && ( - {lines[i]}} - followOutput="auto" - /> - )} -
+
) } -type LineProps = HTMLAttributes - -const Line = ({ className, ...props }: LineProps) => { - return ( -
- ) -} - CommandExecution.displayName = "CommandExecution" + +const OutputContainer = ({ isExpanded, output }: { isExpanded: boolean; output: string }) => ( +
+ {output.length > 0 && } +
+) + +const MemoizedOutputContainer = memo(OutputContainer)