diff --git a/src/frontends/tui/minimal.ts b/src/frontends/tui/minimal.ts index eba565e..c24fc87 100644 --- a/src/frontends/tui/minimal.ts +++ b/src/frontends/tui/minimal.ts @@ -163,8 +163,18 @@ export class MinimalTui { // Listen for line changes to show hints this.keypressHandler = (char: string, key: readline.Key) => { - if (this.activePromptCancel && this.isEscapeKey(char, key)) { - this.activePromptCancel(); + if (this.isEscapeKey(char, key)) { + if (this.activePromptCancel) { + this.activePromptCancel(); + return; + } + if (this.rl) { + try { + this.rl.write(null, { ctrl: true, name: 'u' }); + } catch { + // ignore + } + } return; } @@ -520,6 +530,30 @@ export class MinimalTui { stdoutMuted?: boolean; _writeToOutput?: (s: string) => void; }; + const stdin = process.stdin as NodeJS.ReadStream & { + isRaw?: boolean; + setRawMode?: (mode: boolean) => void; + }; + const wasRaw = Boolean(stdin.isRaw); + let enabledRawForPrompt = false; + let dataListener: ((chunk: Buffer) => void) | null = null; + let settled = false; + + if (stdin.isTTY && stdin.setRawMode && !wasRaw) { + stdin.setRawMode(true); + enabledRawForPrompt = true; + } + + const cleanup = () => { + if (dataListener) { + stdin.removeListener('data', dataListener); + dataListener = null; + } + if (enabledRawForPrompt && stdin.setRawMode) { + stdin.setRawMode(false); + } + }; + rlAny.stdoutMuted = true; rlAny._writeToOutput = (s: string) => { if (!rlAny.stdoutMuted) { @@ -532,8 +566,40 @@ export class MinimalTui { process.stdout.write('*'); } }; - const answer = await new Promise((resolve) => rl.question(question, resolve)); + const answer = await new Promise((resolve) => { + dataListener = (chunk: Buffer) => { + if (settled) { + return; + } + if (chunk.length === 1 && chunk[0] === 0x1b) { + settled = true; + rl.close(); + resolve(''); + } + }; + stdin.on('data', dataListener); + + const onClose = () => { + if (settled) { + return; + } + settled = true; + resolve(''); + }; + if (typeof rl.once === 'function') { + rl.once('close', onClose); + } + + rl.question(question, (value) => { + if (settled) { + return; + } + settled = true; + resolve(value); + }); + }); rlAny.stdoutMuted = false; + cleanup(); rl.close(); process.stdout.write('\n'); return answer.trim();