Fix prompt in deno, but break in bun :(

This commit is contained in:
Timothy Warren 2023-11-27 15:05:48 -05:00
parent 4d54d4bf8a
commit 8ee17f4eef
5 changed files with 94 additions and 56 deletions

View File

@ -16,8 +16,10 @@ const BunTerminalIO: ITerminal = {
argv: (Bun.argv.length > 2) ? Bun.argv.slice(2) : [], argv: (Bun.argv.length > 2) ? Bun.argv.slice(2) : [],
inputLoop: async function* inputLoop() { inputLoop: async function* inputLoop() {
for await (const chunk of Bun.stdin.stream()) { for await (const chunk of Bun.stdin.stream()) {
yield readKey(chunk); yield chunk;
} }
return null;
}, },
getTerminalSize: async function getTerminalSize(): Promise<ITerminalSize> { getTerminalSize: async function getTerminalSize(): Promise<ITerminalSize> {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
@ -33,28 +35,32 @@ const BunTerminalIO: ITerminal = {
// Get the first chunk from stdin // Get the first chunk from stdin
// The response is \x1b[(rows);(cols)R.. // The response is \x1b[(rows);(cols)R..
for await (const chunk of Bun.stdin.stream()) { const chunk = await BunTerminalIO.readStdinRaw();
const rawCode = (new TextDecoder()).decode(chunk); if (chunk === null) {
const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1'); return defaultTerminalSize;
const [srows, scols] = res.split(';');
const rows = parseInt(srows, 10) ?? 24;
const cols = parseInt(scols, 10) ?? 80;
// Clear the screen
await write(Ansi.ClearScreen + Ansi.ResetCursor);
return {
rows,
cols,
};
} }
return defaultTerminalSize; const rawCode = (new TextDecoder()).decode(chunk);
const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1');
const [srows, scols] = res.split(';');
const rows = parseInt(srows, 10) ?? 24;
const cols = parseInt(scols, 10) ?? 80;
// Clear the screen
await write(Ansi.ClearScreen + Ansi.ResetCursor);
return {
rows,
cols,
};
}, },
readStdin: async function (): Promise<string> { readStdin: async function (): Promise<string | null> {
const gen = BunTerminalIO.inputLoop(); const raw = await BunTerminalIO.readStdinRaw();
const chunk = await gen.next(); return readKey(raw ?? new Uint8Array(0));
return chunk.value!; },
readStdinRaw: async function (): Promise<Uint8Array | null> {
const chunk = await BunTerminalIO.inputLoop().next();
return chunk.value ?? null;
}, },
writeStdout: async function write(s: string): Promise<void> { writeStdout: async function write(s: string): Promise<void> {
const buffer = new TextEncoder().encode(s); const buffer = new TextEncoder().encode(s);

View File

@ -7,6 +7,7 @@ import {
ITerminalSize, ITerminalSize,
logToFile, logToFile,
Position, Position,
readKey,
SCROLL_QUIT_TIMES, SCROLL_QUIT_TIMES,
SCROLL_VERSION, SCROLL_VERSION,
} from './mod.ts'; } from './mod.ts';
@ -229,23 +230,35 @@ class Editor {
let res = ''; let res = '';
while (true) { outer: while (true) {
this.setStatusMessage(`${p}${res}`); this.setStatusMessage(`${p}${res}`);
await this.refreshScreen(); await this.refreshScreen();
const char = await term.readStdin(); for await (const chunk of term.inputLoop()) {
// End the prompt const char = readKey(chunk);
if (char === KeyCommand.Enter) { if (char === null) {
this.setStatusMessage(''); continue;
if (res.length === 0) {
return null;
} }
return res; // End the prompt
} if (char === KeyCommand.Enter) {
this.setStatusMessage('');
if (res.length === 0) {
return null;
}
// Add to the prompt result return res;
if (!isControl(char)) { }
res += char;
// Allow backspacing
if (char === KeyCommand.Backspace || char === KeyCommand.Delete) {
res = truncate(res, res.length - 1);
continue outer;
}
// Add to the prompt result
if (!isControl(char!)) {
res += char;
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { getRuntime } from './runtime.ts'; import { getRuntime, readKey } from './runtime.ts';
import { getTermios } from './termios.ts'; import { getTermios } from './termios.ts';
import Editor from './editor.ts'; import Editor from './editor.ts';
@ -37,16 +37,21 @@ export async function main() {
// Clear the screen // Clear the screen
await editor.refreshScreen(); await editor.refreshScreen();
for await (const char of term.inputLoop()) { while (true) {
// Process input for await (const char of term.inputLoop()) {
const shouldLoop = await editor.processKeyPress(char); const parsed = readKey(char);
if (!shouldLoop) { if (char.length === 0 || parsed.length === 0) {
return 0; continue;
}
// Process input
const shouldLoop = await editor.processKeyPress(parsed);
if (!shouldLoop) {
return;
}
// Render output
await editor.refreshScreen();
} }
// Render output
await editor.refreshScreen();
} }
return -1;
} }

View File

@ -64,14 +64,22 @@ export interface ITerminal {
/** /**
* The generator function returning chunks of input from the stdin stream * The generator function returning chunks of input from the stdin stream
*/ */
inputLoop(): AsyncGenerator<string, void>; inputLoop(): AsyncGenerator<Uint8Array, null>;
/** /**
* Get the size of the terminal * Get the size of the terminal
*/ */
getTerminalSize(): Promise<ITerminalSize>; getTerminalSize(): Promise<ITerminalSize>;
readStdin(): Promise<string>; /**
* Get the current chunk of input, if it exists
*/
readStdin(): Promise<string | null>;
/**
* Get the raw chunk of input
*/
readStdinRaw(): Promise<Uint8Array | null>;
/** /**
* Pipe a string to stdout * Pipe a string to stdout
@ -147,6 +155,9 @@ let scrollRuntime: IRuntime | null = null;
* @param raw - the raw chunk of input * @param raw - the raw chunk of input
*/ */
export function readKey(raw: Uint8Array): string { export function readKey(raw: Uint8Array): string {
if (raw.length === 0) {
return '';
}
const parsed = decoder.decode(raw); const parsed = decoder.decode(raw);
// Return the input if it's unambiguous // Return the input if it's unambiguous

View File

@ -2,13 +2,10 @@ import { ITerminal, ITerminalSize, readKey } from '../common/runtime.ts';
const DenoTerminalIO: ITerminal = { const DenoTerminalIO: ITerminal = {
argv: Deno.args, argv: Deno.args,
/** inputLoop: async function* (): AsyncGenerator<Uint8Array, null> {
* Wrap the runtime-specific hook into stdin yield await DenoTerminalIO.readStdinRaw() ?? new Uint8Array(0);
*/
inputLoop: async function* inputLoop() { return null;
for await (const chunk of Deno.stdin.readable) {
yield readKey(chunk);
}
}, },
getTerminalSize: function getSize(): Promise<ITerminalSize> { getTerminalSize: function getSize(): Promise<ITerminalSize> {
const size: { rows: number; columns: number } = Deno.consoleSize(); const size: { rows: number; columns: number } = Deno.consoleSize();
@ -18,10 +15,16 @@ const DenoTerminalIO: ITerminal = {
cols: size.columns, cols: size.columns,
}); });
}, },
readStdin: async function (): Promise<string> { readStdin: async function (): Promise<string | null> {
const gen = DenoTerminalIO.inputLoop(); const raw = await DenoTerminalIO.readStdinRaw();
const chunk = await gen.next(); return readKey(raw ?? new Uint8Array(0));
return chunk.value!; },
readStdinRaw: async function (): Promise<Uint8Array | null> {
const reader = Deno.stdin.readable.getReader();
const chunk = await reader.read();
const res = chunk?.value;
reader.releaseLock();
return res ?? null;
}, },
writeStdout: async function write(s: string): Promise<void> { writeStdout: async function write(s: string): Promise<void> {
const buffer: Uint8Array = new TextEncoder().encode(s); const buffer: Uint8Array = new TextEncoder().encode(s);