feat: parse new terminal output (#3582)

This commit is contained in:
sp.wack
2024-08-26 13:09:43 +03:00
committed by GitHub
parent c444aa801a
commit eaf8e5c8a7
4 changed files with 71 additions and 3 deletions

View File

@@ -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 />);

View File

@@ -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} `,
);
}
}

View 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");
});
});

View 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(),
};
};