Fix terminal DSR replies
This commit is contained in:
@@ -999,6 +999,33 @@ process.stdin.on("data", (chunk) => {
|
||||
process.stdout.write("\\x1b[c");
|
||||
`;
|
||||
|
||||
const DSR_HELPER_SCRIPT = `process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
const mode = process.argv[2] || "cursor";
|
||||
const query = mode === "private-cursor" ? "\\x1b[?6n" : mode === "status" ? "\\x1b[5n" : "\\x1b[6n";
|
||||
const pattern = mode === "private-cursor" ? /\\x1b\\[\\?(\\d+);(\\d+)R/ : mode === "status" ? /\\x1b\\[0n/ : /\\x1b\\[(\\d+);(\\d+)R/;
|
||||
let buf = "";
|
||||
const timer = setTimeout(() => {
|
||||
process.stdout.write("DSR_TIMEOUT\\n");
|
||||
process.exit(2);
|
||||
}, 2500);
|
||||
process.stdin.on("data", (chunk) => {
|
||||
buf += chunk.toString("binary");
|
||||
const match = buf.match(pattern);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
if (mode === "status") {
|
||||
process.stdout.write("DSR_OK:status\\n");
|
||||
} else {
|
||||
process.stdout.write("DSR_OK:" + match[1] + ":" + match[2] + "\\n");
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
process.stdout.write(query);
|
||||
`;
|
||||
|
||||
function writeDaHelper(prefix: string): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), prefix));
|
||||
temporaryDirs.push(dir);
|
||||
@@ -1007,14 +1034,30 @@ function writeDaHelper(prefix: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
function writeDsrHelper(prefix: string): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), prefix));
|
||||
temporaryDirs.push(dir);
|
||||
const path = join(dir, "helper.cjs");
|
||||
writeFileSync(path, DSR_HELPER_SCRIPT);
|
||||
return path;
|
||||
}
|
||||
|
||||
function isDaOkLine(line: string): boolean {
|
||||
return line.startsWith("DA_OK:");
|
||||
}
|
||||
|
||||
function isDsrOkLine(line: string): boolean {
|
||||
return line.startsWith("DSR_OK:");
|
||||
}
|
||||
|
||||
function hasDaOkLine(state: ReturnType<TerminalSession["getState"]>): boolean {
|
||||
return getLines(state).some(isDaOkLine);
|
||||
}
|
||||
|
||||
function hasDsrOkLine(state: ReturnType<TerminalSession["getState"]>): boolean {
|
||||
return getLines(state).some(isDsrOkLine);
|
||||
}
|
||||
|
||||
function lastNonEmptyLineIsPrompt(state: ReturnType<TerminalSession["getState"]>): boolean {
|
||||
const last =
|
||||
getLines(state)
|
||||
@@ -1059,6 +1102,63 @@ describe("terminal protocol queries", () => {
|
||||
await waitForState(session, hasDaOkLine);
|
||||
await waitForState(session, lastNonEmptyLineIsPrompt);
|
||||
});
|
||||
|
||||
it("delivers public DSR cursor-position replies to a foreground app on stdin", async () => {
|
||||
const helperPath = writeDsrHelper("terminal-dsr-helper-");
|
||||
|
||||
const session = trackSession(
|
||||
await createTerminal({
|
||||
cwd: "/tmp",
|
||||
shell: "/bin/sh",
|
||||
env: { PS1: "$ " },
|
||||
}),
|
||||
);
|
||||
await waitForLines(session, ["$"]);
|
||||
|
||||
session.send({ type: "input", data: `${process.execPath} ${helperPath} cursor\r` });
|
||||
await waitForState(session, hasDsrOkLine);
|
||||
|
||||
const ack = getLines(session.getState()).find(isDsrOkLine) ?? "";
|
||||
expect(ack).toMatch(/^DSR_OK:\d+:\d+$/);
|
||||
});
|
||||
|
||||
it("delivers private DSR cursor-position replies to a foreground app on stdin", async () => {
|
||||
const helperPath = writeDsrHelper("terminal-dsr-private-helper-");
|
||||
|
||||
const session = trackSession(
|
||||
await createTerminal({
|
||||
cwd: "/tmp",
|
||||
shell: "/bin/sh",
|
||||
env: { PS1: "$ " },
|
||||
}),
|
||||
);
|
||||
await waitForLines(session, ["$"]);
|
||||
|
||||
session.send({ type: "input", data: `${process.execPath} ${helperPath} private-cursor\r` });
|
||||
await waitForState(session, hasDsrOkLine);
|
||||
|
||||
const ack = getLines(session.getState()).find(isDsrOkLine) ?? "";
|
||||
expect(ack).toMatch(/^DSR_OK:\d+:\d+$/);
|
||||
});
|
||||
|
||||
it("delivers DSR terminal-status replies to a foreground app on stdin", async () => {
|
||||
const helperPath = writeDsrHelper("terminal-dsr-status-helper-");
|
||||
|
||||
const session = trackSession(
|
||||
await createTerminal({
|
||||
cwd: "/tmp",
|
||||
shell: "/bin/sh",
|
||||
env: { PS1: "$ " },
|
||||
}),
|
||||
);
|
||||
await waitForLines(session, ["$"]);
|
||||
|
||||
session.send({ type: "input", data: `${process.execPath} ${helperPath} status\r` });
|
||||
await waitForState(session, hasDsrOkLine);
|
||||
|
||||
const ack = getLines(session.getState()).find(isDsrOkLine) ?? "";
|
||||
expect(ack).toBe("DSR_OK:status");
|
||||
});
|
||||
});
|
||||
|
||||
describe("stream snapshots", () => {
|
||||
|
||||
@@ -670,6 +670,29 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
||||
}
|
||||
return false;
|
||||
});
|
||||
terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
|
||||
if (params.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (params[0] === 5) {
|
||||
ptyProcess.write("\x1b[0n");
|
||||
return true;
|
||||
}
|
||||
if (params[0] === 6) {
|
||||
const buffer = terminal.buffer.active;
|
||||
ptyProcess.write(`\x1b[${buffer.cursorY + 1};${buffer.cursorX + 1}R`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
terminal.parser.registerCsiHandler({ prefix: "?", final: "n" }, (params) => {
|
||||
if (params.length !== 1 || params[0] !== 6) {
|
||||
return false;
|
||||
}
|
||||
const buffer = terminal.buffer.active;
|
||||
ptyProcess.write(`\x1b[?${buffer.cursorY + 1};${buffer.cursorX + 1}R`);
|
||||
return true;
|
||||
});
|
||||
|
||||
let disposeTitleChangeSubscription: { dispose(): void } | null = null;
|
||||
if (!lockedTitle) {
|
||||
|
||||
Reference in New Issue
Block a user