Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions src/ui/stdioProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ type StdioProxyOptions = {
};

export class StdioProxy {
readonly #keypressInput = new PassThrough();
readonly #keypressListeners: ((...event: KeyPressEvent) => void)[] = [];
#pendingKeypressInput = "";
#keypressInput = this.#createKeypressInput();
#decoder = new StringDecoder("utf8");
readonly #onCursorPositionReport: (data: string) => void;
readonly #onWin32InputMode: (enabled: boolean) => void;
Expand All @@ -58,17 +60,16 @@ export class StdioProxy {
constructor({ onCursorPositionReport = () => {}, onWin32InputMode = () => {} }: StdioProxyOptions = {}) {
this.#onCursorPositionReport = onCursorPositionReport;
this.#onWin32InputMode = onWin32InputMode;
readline.emitKeypressEvents(this.#keypressInput as unknown as NodeJS.ReadStream);
}

onKeypress(listener: (...event: KeyPressEvent) => void): void {
this.#keypressInput.on("keypress", listener);
this.#keypressListeners.push(listener);
}

handleInput(data: Buffer | string): void {
handleInput(data: Buffer | string, forwardInput?: (data: string) => void): void {
const decoded = Buffer.isBuffer(data) ? this.#decoder.write(data) : this.#decoder.end() + data;
if (!Buffer.isBuffer(data)) this.#decoder = new StringDecoder("utf8");
this.#routeInput(this.#pendingInput + decoded);
this.#routeInput(this.#pendingInput + decoded, forwardInput);
}

handleOutput(data: string): string {
Expand All @@ -94,13 +95,36 @@ export class StdioProxy {
return pendingOutput;
}

#routeInput(input: string): void {
#createKeypressInput(): PassThrough {
const input = new PassThrough();
readline.emitKeypressEvents(input as unknown as NodeJS.ReadStream);
input.on("keypress", (...event: KeyPressEvent) => {
this.#pendingKeypressInput = this.#pendingKeypressInput.slice(event[1].sequence.length);
this.#keypressListeners.forEach((listener) => listener(...event));
});
return input;
}

#routeInput(input: string, forwardInput?: (data: string) => void): void {
this.#pendingInput = input.match(partialCursorPositionReport)?.[0] ?? "";
const completeInput = this.#pendingInput.length === 0 ? input : input.slice(0, -this.#pendingInput.length);
const keypressInput = completeInput.replace(cursorPositionReport, (response) => {
this.#onCursorPositionReport(response);
return "";
});
if (keypressInput.length !== 0) this.#keypressInput.write(keypressInput);
if (forwardInput != null) {
const pending = this.#pendingKeypressInput;
if (pending.length !== 0) {
// Forward incomplete keys in order, without leaving readline's Escape timer active on this route.
this.#keypressInput.removeAllListeners("keypress");
this.#keypressInput.destroy();
this.#pendingKeypressInput = "";
this.#keypressInput = this.#createKeypressInput();
}
if (pending.length + keypressInput.length !== 0) forwardInput(pending + keypressInput);
} else if (keypressInput.length !== 0) {
this.#pendingKeypressInput += keypressInput;
this.#keypressInput.write(keypressInput);
}
}
}
3 changes: 2 additions & 1 deletion src/ui/ui-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const render = async (program: Command, shell: Shell, underTest: boolean,
}
},
});
const handleInput = (data: Buffer | string) => stdio.handleInput(data);
const forwardInput = (data: string) => term.write(data);
const handleInput = (data: Buffer | string) => stdio.handleInput(data, term.isAlternateBuffer() ? forwardInput : undefined);
process.stdin.on("data", handleInput);
writeOutput(ansi.clearTerminal);

Expand Down
Loading