feat(terminal): implement clearTerminalAll function for enhanced terminal management (#579)

- Added the `clearTerminalAll` function to clear the terminal's output while preserving cursor position and handling shell redraws.
- Updated the `XTerminal` component to utilize `clearTerminalAll` for resetting the terminal state.
- Introduced tests to verify the functionality of `clearTerminalAll`, ensuring it clears scrollback without altering the CMD cursor row and requests redraws for shells that handle Ctrl+L.
This commit is contained in:
Kang committed 2026-09-26 22:24:12 +08:00
1 parent 2ce1972ea3
commit 42364ea0b5
3 files changed
+70 -1

No files matched your search

+4 -1
View File
@@ -134,6 +134,7 @@ import {
shouldBlockXTerminalData,
} from "./xterminalKeyboardInput";
import {
clearTerminalAll,
markTerminalUserInput,
registerTerminalUserInputMarker,
} from "@/lib/terminalControlInput";
@@ -1752,7 +1753,9 @@ export default function XTerminal({
if (appLockedRef.current) return;
lineTimestampsRef.current = new Map();
gutterLineOffsetRef.current = 0;
terminal.reset();
clearTerminalAll(terminal, {
shellRedraw: sessionTypeRef.current !== "Local" || !isWindows,
});
focusTerminal();
requestGutterRefresh();
};
+51
View File
@@ -1,6 +1,7 @@
import type { Terminal } from "@xterm/xterm";
import { describe, expect, it, vi } from "vitest";
import {
clearTerminalAll,
registerTerminalUserInputMarker,
sendTerminalClearInput,
} from "./terminalControlInput";
@@ -24,4 +25,54 @@ describe("terminal control input origin", () => {
expect(terminal.focus).toHaveBeenCalledOnce();
unregister();
});
it("clears scrollback without sending Ctrl+L or changing the CMD cursor row", async () => {
const canvas = vi
.spyOn(HTMLCanvasElement.prototype, "getContext")
.mockReturnValue({
measureText: () => ({ width: 10 }),
} as unknown as CanvasRenderingContext2D);
const { Terminal: XTerminal } = await import("@xterm/xterm");
const terminal = new XTerminal({ cols: 20, rows: 3 });
await new Promise<void>((resolve) =>
terminal.write(
"first line\r\nold line\r\nsecond line\r\nprompt> abcdef\x1b[?1h",
resolve,
),
);
const input = vi.fn();
const disposable = terminal.onData(input);
expect(terminal.buffer.active.baseY).toBeGreaterThan(0);
clearTerminalAll(terminal);
await new Promise<void>((resolve) => terminal.write("", resolve));
expect(terminal.buffer.active.length).toBe(3);
expect(terminal.buffer.active.baseY).toBe(0);
expect(terminal.buffer.active.cursorY).toBe(2);
expect(terminal.buffer.active.cursorX).toBe(14);
expect(terminal.buffer.active.getLine(0)?.translateToString(true)).toBe("");
expect(terminal.buffer.active.getLine(1)?.translateToString(true)).toBe("");
expect(terminal.buffer.active.getLine(2)?.translateToString(true)).toBe("prompt> abcdef");
expect(input).not.toHaveBeenCalled();
expect(terminal.modes.applicationCursorKeysMode).toBe(true);
disposable.dispose();
terminal.dispose();
canvas.mockRestore();
});
it("requests a redraw for shells that handle Ctrl+L", async () => {
const { Terminal: XTerminal } = await import("@xterm/xterm");
const terminal = new XTerminal({ cols: 20, rows: 3 });
await new Promise<void>((resolve) => terminal.write("prompt> ", resolve));
const input = vi.fn();
const disposable = terminal.onData(input);
clearTerminalAll(terminal, { shellRedraw: true });
expect(input).toHaveBeenCalledWith("\x0c");
disposable.dispose();
terminal.dispose();
});
});
+15
View File
@@ -28,3 +28,18 @@ export function sendTerminalClearInput(
terminal.focus();
}
}
export function clearTerminalAll(
terminal: Terminal,
options: { shellRedraw?: boolean } = {},
): void {
const { cursorX, cursorY } = terminal.buffer.active;
terminal.clearSelection();
terminal.clear();
if (options.shellRedraw) {
sendTerminalClearInput(terminal);
} else if (cursorY > 0) {
// Keep ConPTY's viewport cursor coordinates while discarding old output.
terminal.write(`\x1b[1;1H\x1b[${cursorY}L\x1b[${cursorY + 1};${cursorX + 1}H`);
}
}