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) : [],
inputLoop: async function* inputLoop() {
for await (const chunk of Bun.stdin.stream()) {
yield readKey(chunk);
yield chunk;
}
return null;
},
getTerminalSize: async function getTerminalSize(): Promise<ITerminalSize> {
const encoder = new TextEncoder();
@ -33,28 +35,32 @@ const BunTerminalIO: ITerminal = {
// Get the first chunk from stdin
// The response is \x1b[(rows);(cols)R..
for await (const chunk of Bun.stdin.stream()) {
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,
};
const chunk = await BunTerminalIO.readStdinRaw();
if (chunk === null) {
return defaultTerminalSize;
}
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> {
const gen = BunTerminalIO.inputLoop();
const chunk = await gen.next();
return chunk.value!;
readStdin: async function (): Promise<string | null> {
const raw = await BunTerminalIO.readStdinRaw();
return readKey(raw ?? new Uint8Array(0));
},
readStdinRaw: async function (): Promise<Uint8Array | null> {
const chunk = await BunTerminalIO.inputLoop().next();
return chunk.value ?? null;
},
writeStdout: async function write(s: string): Promise<void> {
const buffer = new TextEncoder().encode(s);

View File

@ -7,6 +7,7 @@ import {
ITerminalSize,
logToFile,
Position,
readKey,
SCROLL_QUIT_TIMES,
SCROLL_VERSION,
} from './mod.ts';
@ -229,23 +230,35 @@ class Editor {
let res = '';
while (true) {
outer: while (true) {
this.setStatusMessage(`${p}${res}`);
await this.refreshScreen();
const char = await term.readStdin();
// End the prompt
if (char === KeyCommand.Enter) {
this.setStatusMessage('');
if (res.length === 0) {
return null;
for await (const chunk of term.inputLoop()) {
const char = readKey(chunk);
if (char === null) {
continue;
}
return res;
}
// End the prompt
if (char === KeyCommand.Enter) {
this.setStatusMessage('');
if (res.length === 0) {
return null;
}
// Add to the prompt result
if (!isControl(char)) {
res += char;
return res;
}
// 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 Editor from './editor.ts';
@ -37,16 +37,21 @@ export async function main() {
// Clear the screen
await editor.refreshScreen();
for await (const char of term.inputLoop()) {
// Process input
const shouldLoop = await editor.processKeyPress(char);
if (!shouldLoop) {
return 0;
while (true) {
for await (const char of term.inputLoop()) {
const parsed = readKey(char);
if (char.length === 0 || parsed.length === 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
*/
inputLoop(): AsyncGenerator<string, void>;
inputLoop(): AsyncGenerator<Uint8Array, null>;
/**
* Get the size of the terminal
*/
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
@ -147,6 +155,9 @@ let scrollRuntime: IRuntime | null = null;
* @param raw - the raw chunk of input
*/
export function readKey(raw: Uint8Array): string {
if (raw.length === 0) {
return '';
}
const parsed = decoder.decode(raw);
// Return the input if it's unambiguous

View File

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