mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
feat: parse new terminal output (#3582)
This commit is contained in:
@@ -102,6 +102,23 @@ describe("Terminal", () => {
|
||||
expect(mockTerminal.write).toHaveBeenCalledWith("$ ");
|
||||
});
|
||||
|
||||
it("should display a custom symbol if output contains a custom symbol", () => {
|
||||
renderTerminal([
|
||||
{ type: "input", content: "echo Hello" },
|
||||
{
|
||||
type: "output",
|
||||
content:
|
||||
"Hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ ",
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo Hello");
|
||||
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "Hello");
|
||||
expect(mockTerminal.write).toHaveBeenCalledWith(
|
||||
"\nopenhands@659478cb008c:/workspace $ ",
|
||||
);
|
||||
});
|
||||
|
||||
// This test fails because it expects `disposeMock` to have been called before the component is unmounted.
|
||||
it.skip("should dispose the terminal on unmount", () => {
|
||||
const { unmount } = renderWithProviders(<Terminal />);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Terminal } from "@xterm/xterm";
|
||||
import React from "react";
|
||||
import { Command } from "#/state/commandSlice";
|
||||
import { sendTerminalCommand } from "#/services/terminalService";
|
||||
import { parseTerminalOutput } from "#/utils/parseTerminalOutput";
|
||||
|
||||
/*
|
||||
NOTE: Tests for this hook are indirectly covered by the tests for the XTermTerminal component.
|
||||
@@ -101,14 +102,16 @@ export const useTerminal = (commands: Command[] = []) => {
|
||||
// Start writing commands from the last command index
|
||||
for (let i = lastCommandIndex.current; i < commands.length; i += 1) {
|
||||
const command = commands[i];
|
||||
const lines = command.content.split("\n");
|
||||
const lines = parseTerminalOutput(command.content).output.split("\n");
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
terminal.current?.writeln(line);
|
||||
terminal.current?.writeln(parseTerminalOutput(line).output);
|
||||
});
|
||||
|
||||
if (command.type === "output") {
|
||||
terminal.current.write("\n$ ");
|
||||
terminal.current.write(
|
||||
`\n${parseTerminalOutput(command.content).symbol} `,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
frontend/src/utils/parseTerminalOutput.test.ts
Normal file
21
frontend/src/utils/parseTerminalOutput.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parseTerminalOutput } from "./parseTerminalOutput";
|
||||
|
||||
describe("parseTerminalOutput", () => {
|
||||
it("should parse the command, env, and symbol", () => {
|
||||
const raw =
|
||||
"web_scraper.py\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ ";
|
||||
|
||||
const parsed = parseTerminalOutput(raw);
|
||||
|
||||
expect(parsed.output).toBe("web_scraper.py");
|
||||
expect(parsed.symbol).toBe("openhands@659478cb008c:/workspace $");
|
||||
});
|
||||
|
||||
it("should return raw output if unable to parse", () => {
|
||||
const raw = "web_scraper.py";
|
||||
|
||||
const parsed = parseTerminalOutput(raw);
|
||||
expect(parsed.output).toBe("web_scraper.py");
|
||||
});
|
||||
});
|
||||
27
frontend/src/utils/parseTerminalOutput.ts
Normal file
27
frontend/src/utils/parseTerminalOutput.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Parses the raw output from the terminal into the command and symbol
|
||||
* @param raw The raw output to be displayed in the terminal
|
||||
* @returns The parsed output
|
||||
*
|
||||
* @example
|
||||
* const raw =
|
||||
* "web_scraper.py\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ ";
|
||||
*
|
||||
* const parsed = parseTerminalOutput(raw);
|
||||
*
|
||||
* console.log(parsed.output); // web_scraper.py
|
||||
* console.log(parsed.symbol); // openhands@659478cb008c:/workspace $
|
||||
*/
|
||||
export const parseTerminalOutput = (raw: string) => {
|
||||
const envRegex = /\[Python Interpreter: (.*)\]/;
|
||||
const env = raw.match(envRegex);
|
||||
let fullOutput = raw;
|
||||
if (env && env[0]) fullOutput = fullOutput.replace(`${env[0]}\n`, "");
|
||||
const [output, s] = fullOutput.split("\r\n\r\n");
|
||||
const symbol = s || "$";
|
||||
|
||||
return {
|
||||
output: output.trim(),
|
||||
symbol: symbol.trim(),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user