perf(terminal): reuse ANSI truncation scanner

Reuse one module-level ANSI/OSC scanner during visible-width truncation and reset scanner state between calls. Keeps styled, plain, and OSC-8 truncation behavior covered by regression tests.
This commit is contained in:
Anson_H
2026-06-13 06:54:53 -07:00
committed by GitHub
parent 7994880864
commit 205ab8d4bd
2 changed files with 12 additions and 2 deletions

View File

@@ -60,4 +60,13 @@ describe("terminal ansi helpers", () => {
expect(truncateToVisibleWidth("表文", 1)).toBe("");
expect(visibleWidth(truncateToVisibleWidth("表文", 1))).toBe(0);
});
it("reuses the ANSI scanner across truncation calls", () => {
expect(truncateToVisibleWidth("\u001B[31mabc\u001B[0m", 2)).toBe("\u001B[31mab\u001B[0m");
expect(truncateToVisibleWidth("plain", 3)).toBe("pla");
expect(
truncateToVisibleWidth("\u001B]8;;https://openclaw.ai\u001B\\link\u001B]8;;\u001B\\", 2),
).toBe("\u001B]8;;https://openclaw.ai\u001B\\li\u001B]8;;\u001B\\");
expect(truncateToVisibleWidth("\u001B[32mxy\u001B[0m", 1)).toBe("\u001B[32mx\u001B[0m");
});
});

View File

@@ -6,6 +6,7 @@ const ANSI_OSC_PATTERN = "\\x1b\\][^\\x07\\x1b]*(?:\\x1b\\\\|\\x07)";
const ANSI_CSI_REGEX = new RegExp(ANSI_CSI_PATTERN, "g");
const ANSI_OSC_REGEX = new RegExp(ANSI_OSC_PATTERN, "g");
const ANSI_SEQUENCE_REGEX = new RegExp(`${ANSI_OSC_PATTERN}|${ANSI_CSI_PATTERN}`, "g");
const graphemeSegmenter =
typeof Intl !== "undefined" && "Segmenter" in Intl
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
@@ -134,7 +135,7 @@ export function truncateToVisibleWidth(input: string, maxWidth: number): string
if (visibleWidth(input) <= maxWidth) {
return input;
}
const ansi = new RegExp(`${ANSI_OSC_PATTERN}|${ANSI_CSI_PATTERN}`, "g");
ANSI_SEQUENCE_REGEX.lastIndex = 0;
let out = "";
let used = 0;
let pos = 0;
@@ -157,7 +158,7 @@ export function truncateToVisibleWidth(input: string, maxWidth: number): string
}
};
let match: RegExpExecArray | null;
while ((match = ansi.exec(input)) !== null) {
while ((match = ANSI_SEQUENCE_REGEX.exec(input)) !== null) {
appendVisible(input.slice(pos, match.index));
out += match[0];
pos = match.index + match[0].length;