@gjsify/readline 0.3.21 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +2 -2
- package/lib/esm/internal/stream-types.js +0 -0
- package/lib/esm/promises.js +1 -1
- package/lib/types/index.d.ts +2 -1
- package/lib/types/internal/stream-types.d.ts +48 -0
- package/package.json +45 -42
- package/src/index.spec.ts +0 -932
- package/src/index.ts +0 -544
- package/src/promises.spec.ts +0 -122
- package/src/promises.ts +0 -35
- package/src/test.mts +0 -4
- package/tsconfig.json +0 -29
- package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts
DELETED
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
// Reference: Node.js lib/readline.js
|
|
2
|
-
// Reimplemented for GJS without Node.js primordials.
|
|
3
|
-
|
|
4
|
-
import { EventEmitter } from 'node:events';
|
|
5
|
-
import { StringDecoder } from 'node:string_decoder';
|
|
6
|
-
import type { Readable, Writable } from 'node:stream';
|
|
7
|
-
|
|
8
|
-
export interface InterfaceOptions {
|
|
9
|
-
input?: Readable;
|
|
10
|
-
output?: Writable;
|
|
11
|
-
prompt?: string;
|
|
12
|
-
terminal?: boolean;
|
|
13
|
-
historySize?: number;
|
|
14
|
-
completer?: (line: string, callback: (err: Error | null, result: [string[], string]) => void) => void;
|
|
15
|
-
crlfDelay?: number;
|
|
16
|
-
removeHistoryDuplicates?: boolean;
|
|
17
|
-
escapeCodeTimeout?: number;
|
|
18
|
-
tabSize?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const LINE_END = /\r\n|\r|\n/;
|
|
22
|
-
|
|
23
|
-
export class Interface extends EventEmitter {
|
|
24
|
-
terminal: boolean;
|
|
25
|
-
line = '';
|
|
26
|
-
cursor = 0;
|
|
27
|
-
|
|
28
|
-
private _input: Readable | null;
|
|
29
|
-
private _output: Writable | null;
|
|
30
|
-
|
|
31
|
-
get input(): Readable | null { return this._input; }
|
|
32
|
-
get output(): Writable | null { return this._output; }
|
|
33
|
-
|
|
34
|
-
private _prompt: string;
|
|
35
|
-
private _closed = false;
|
|
36
|
-
private _paused = false;
|
|
37
|
-
history: string[];
|
|
38
|
-
private _historySize: number;
|
|
39
|
-
private _crlfDelay: number;
|
|
40
|
-
private _lineBuffer = '';
|
|
41
|
-
private _questionCallback: ((answer: string) => void) | null = null;
|
|
42
|
-
// Per-listener refs so close() removes only our listeners, not the keypress
|
|
43
|
-
// parser's 'data' listener — which must survive across sequential prompts.
|
|
44
|
-
private _boundOnData: ((chunk: Buffer | string) => void) | null = null;
|
|
45
|
-
private _boundOnEnd: (() => void) | null = null;
|
|
46
|
-
private _boundOnError: ((err: Error) => void) | null = null;
|
|
47
|
-
private _boundOnKeypress: ((str: string | undefined, key: Key) => void) | null = null;
|
|
48
|
-
|
|
49
|
-
constructor(input?: Readable | InterfaceOptions, output?: Writable) {
|
|
50
|
-
super();
|
|
51
|
-
|
|
52
|
-
let opts: InterfaceOptions;
|
|
53
|
-
if (input && typeof input === 'object' && !('read' in input && typeof input.read === 'function')) {
|
|
54
|
-
opts = input as InterfaceOptions;
|
|
55
|
-
} else {
|
|
56
|
-
opts = { input: input as Readable, output };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
this._input = opts.input || null;
|
|
60
|
-
this._output = opts.output || null;
|
|
61
|
-
this._prompt = opts.prompt || '> ';
|
|
62
|
-
this.terminal = opts.terminal ?? (this._output !== null);
|
|
63
|
-
this._historySize = opts.historySize ?? 30;
|
|
64
|
-
this.history = [];
|
|
65
|
-
this._crlfDelay = opts.crlfDelay ?? 100;
|
|
66
|
-
|
|
67
|
-
if (this._input) {
|
|
68
|
-
this._boundOnData = (chunk: Buffer | string) => this._onData(chunk);
|
|
69
|
-
this._boundOnEnd = () => this._onEnd();
|
|
70
|
-
this._boundOnError = (err: Error) => this.emit('error', err);
|
|
71
|
-
this._input.on('data', this._boundOnData);
|
|
72
|
-
this._input.on('end', this._boundOnEnd);
|
|
73
|
-
this._input.on('error', this._boundOnError);
|
|
74
|
-
|
|
75
|
-
if ('setEncoding' in this._input && typeof this._input.setEncoding === 'function') {
|
|
76
|
-
this._input.setEncoding('utf8');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (this.terminal) {
|
|
80
|
-
emitKeypressEvents(this._input as Readable & Record<symbol, unknown>, this as any);
|
|
81
|
-
if ('setRawMode' in this._input && typeof (this._input as any).setRawMode === 'function') {
|
|
82
|
-
if (!(this._input as any).isRaw) (this._input as any).setRawMode(true);
|
|
83
|
-
}
|
|
84
|
-
if ('resume' in this._input && typeof (this._input as any).resume === 'function') {
|
|
85
|
-
(this._input as any).resume();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
this._boundOnKeypress = (str: string | undefined, key: Key) => {
|
|
89
|
-
if (!key) return;
|
|
90
|
-
if (key.name === 'backspace' || key.name === 'delete') {
|
|
91
|
-
if (this.cursor > 0) {
|
|
92
|
-
this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor);
|
|
93
|
-
this.cursor--;
|
|
94
|
-
}
|
|
95
|
-
} else if (
|
|
96
|
-
str && str.length === 1 && !key.ctrl && !key.meta &&
|
|
97
|
-
key.name !== 'return' && key.name !== 'enter' && key.name !== 'escape'
|
|
98
|
-
) {
|
|
99
|
-
this.line = this.line.slice(0, this.cursor) + str + this.line.slice(this.cursor);
|
|
100
|
-
this.cursor++;
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
this._input.on('keypress', this._boundOnKeypress as (...args: unknown[]) => void);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private _onData(chunk: Buffer | string): void {
|
|
109
|
-
if (this._closed || this._paused) return;
|
|
110
|
-
const str = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
111
|
-
this._lineBuffer += str;
|
|
112
|
-
let m: RegExpExecArray | null;
|
|
113
|
-
while ((m = LINE_END.exec(this._lineBuffer)) !== null) {
|
|
114
|
-
const line = this._lineBuffer.substring(0, m.index);
|
|
115
|
-
this._lineBuffer = this._lineBuffer.substring(m.index + m[0].length);
|
|
116
|
-
this._onLine(line);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private _onLine(line: string): void {
|
|
121
|
-
if (line.length > 0 && this._historySize > 0) {
|
|
122
|
-
if (this.history.length === 0 || this.history[0] !== line) {
|
|
123
|
-
this.history.unshift(line);
|
|
124
|
-
if (this.history.length > this._historySize) this.history.pop();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (this._questionCallback) {
|
|
128
|
-
const cb = this._questionCallback;
|
|
129
|
-
this._questionCallback = null;
|
|
130
|
-
cb(line);
|
|
131
|
-
}
|
|
132
|
-
this.emit('line', line);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private _onEnd(): void {
|
|
136
|
-
if (this._lineBuffer.length > 0) {
|
|
137
|
-
this._onLine(this._lineBuffer);
|
|
138
|
-
this._lineBuffer = '';
|
|
139
|
-
}
|
|
140
|
-
this.close();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
setPrompt(prompt: string): void {
|
|
144
|
-
this._prompt = prompt;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
getPrompt(): string {
|
|
148
|
-
return this._prompt;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
prompt(_preserveCursor?: boolean): void {
|
|
152
|
-
if (this._closed) throw new Error('readline was closed');
|
|
153
|
-
this._output?.write(this._prompt);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
question(query: string, callback: (answer: string) => void): void;
|
|
157
|
-
question(query: string, options: Record<string, unknown>, callback: (answer: string) => void): void;
|
|
158
|
-
question(
|
|
159
|
-
query: string,
|
|
160
|
-
optionsOrCallback: Record<string, unknown> | ((answer: string) => void),
|
|
161
|
-
callback?: (answer: string) => void,
|
|
162
|
-
): void {
|
|
163
|
-
if (this._closed) throw new Error('readline was closed');
|
|
164
|
-
const cb = typeof optionsOrCallback === 'function' ? optionsOrCallback : callback!;
|
|
165
|
-
this._questionCallback = cb;
|
|
166
|
-
this._output?.write(query);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
clearLine(_dir: number, callback?: () => void): boolean {
|
|
170
|
-
this.line = '';
|
|
171
|
-
this.cursor = 0;
|
|
172
|
-
if (callback) callback();
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
write(data: string | Buffer | null, key?: { ctrl?: boolean; meta?: boolean; shift?: boolean; name?: string }): void {
|
|
177
|
-
if (this._closed) return;
|
|
178
|
-
if (key) {
|
|
179
|
-
if (this._input) (this._input as any).emit('keypress', data ?? '', key);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (data !== null && data !== undefined) {
|
|
183
|
-
const str = typeof data === 'string' ? data : data.toString('utf8');
|
|
184
|
-
if (str) {
|
|
185
|
-
this.line = this.line.slice(0, this.cursor) + str + this.line.slice(this.cursor);
|
|
186
|
-
this.cursor += str.length;
|
|
187
|
-
}
|
|
188
|
-
this._output?.write(data);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
close(): void {
|
|
193
|
-
if (this._closed) return;
|
|
194
|
-
this._closed = true;
|
|
195
|
-
|
|
196
|
-
if (this._input) {
|
|
197
|
-
// Remove only our own listeners — removeAllListeners would also strip the
|
|
198
|
-
// keypress parser's 'data' listener. The _KEYPRESS_DECODER Symbol idempotency
|
|
199
|
-
// guard would then prevent emitKeypressEvents from re-installing for the next
|
|
200
|
-
// prompt, silencing all keypress events.
|
|
201
|
-
if (this._boundOnData) this._input.removeListener('data', this._boundOnData);
|
|
202
|
-
if (this._boundOnEnd) this._input.removeListener('end', this._boundOnEnd);
|
|
203
|
-
if (this._boundOnError) this._input.removeListener('error', this._boundOnError);
|
|
204
|
-
if (this._boundOnKeypress) this._input.removeListener('keypress', this._boundOnKeypress as (...args: unknown[]) => void);
|
|
205
|
-
this._boundOnData = null;
|
|
206
|
-
this._boundOnEnd = null;
|
|
207
|
-
this._boundOnError = null;
|
|
208
|
-
this._boundOnKeypress = null;
|
|
209
|
-
|
|
210
|
-
if (this.terminal && (this._input as any).isRaw &&
|
|
211
|
-
'setRawMode' in this._input && typeof (this._input as any).setRawMode === 'function') {
|
|
212
|
-
(this._input as any).setRawMode(false);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Pause stdin so ProcessReadStream releases the GLib main loop via
|
|
216
|
-
// quitMainLoop(). Mirrors Node.js readline: close() pauses the stream
|
|
217
|
-
// so the event loop can drain and the process exits when no more prompts follow.
|
|
218
|
-
if ('pause' in this._input && typeof (this._input as any).pause === 'function') {
|
|
219
|
-
(this._input as any).pause();
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
this.emit('close');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
pause(): this {
|
|
227
|
-
if (this._closed) return this;
|
|
228
|
-
this._paused = true;
|
|
229
|
-
if (this._input && 'pause' in this._input && typeof this._input.pause === 'function') {
|
|
230
|
-
this._input.pause();
|
|
231
|
-
}
|
|
232
|
-
this.emit('pause');
|
|
233
|
-
return this;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
resume(): this {
|
|
237
|
-
if (this._closed) return this;
|
|
238
|
-
this._paused = false;
|
|
239
|
-
if (this._input && 'resume' in this._input && typeof this._input.resume === 'function') {
|
|
240
|
-
this._input.resume();
|
|
241
|
-
}
|
|
242
|
-
this.emit('resume');
|
|
243
|
-
return this;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
getCursorPos(): { rows: number; cols: number } {
|
|
247
|
-
const columns = (this._output as any)?.columns ?? 80;
|
|
248
|
-
const len = this._prompt.length + this.cursor;
|
|
249
|
-
return { rows: Math.floor(len / columns), cols: len % columns };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
[Symbol.asyncIterator](): AsyncIterableIterator<string> {
|
|
253
|
-
const lines: string[] = [];
|
|
254
|
-
let resolve: ((value: IteratorResult<string, undefined>) => void) | null = null;
|
|
255
|
-
let done = false;
|
|
256
|
-
|
|
257
|
-
const onLine = (line: string) => {
|
|
258
|
-
if (resolve) {
|
|
259
|
-
const r = resolve;
|
|
260
|
-
resolve = null;
|
|
261
|
-
r({ value: line, done: false });
|
|
262
|
-
} else {
|
|
263
|
-
lines.push(line);
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
const onClose = () => {
|
|
267
|
-
done = true;
|
|
268
|
-
if (resolve) {
|
|
269
|
-
const r = resolve;
|
|
270
|
-
resolve = null;
|
|
271
|
-
r({ value: undefined, done: true });
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
this.on('line', onLine);
|
|
276
|
-
this.on('close', onClose);
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
next: (): Promise<IteratorResult<string, undefined>> => {
|
|
280
|
-
if (lines.length > 0) return Promise.resolve({ value: lines.shift()!, done: false });
|
|
281
|
-
if (done) return Promise.resolve({ value: undefined, done: true });
|
|
282
|
-
return new Promise<IteratorResult<string, undefined>>((r) => { resolve = r; });
|
|
283
|
-
},
|
|
284
|
-
return: (): Promise<IteratorResult<string, undefined>> => {
|
|
285
|
-
done = true;
|
|
286
|
-
this.removeListener('line', onLine);
|
|
287
|
-
this.removeListener('close', onClose);
|
|
288
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
289
|
-
},
|
|
290
|
-
[Symbol.asyncIterator]() { return this; },
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export function createInterface(
|
|
296
|
-
input?: Readable | InterfaceOptions,
|
|
297
|
-
output?: Writable,
|
|
298
|
-
completer?: InterfaceOptions['completer'],
|
|
299
|
-
terminal?: boolean,
|
|
300
|
-
): Interface {
|
|
301
|
-
if (typeof input === 'object' && input !== null && !('read' in input && typeof input.read === 'function')) {
|
|
302
|
-
return new Interface(input);
|
|
303
|
-
}
|
|
304
|
-
return new Interface({ input: input as Readable, output, completer, terminal });
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export function clearLine(stream: Writable, dir: number, callback?: () => void): boolean {
|
|
308
|
-
if (!stream || typeof stream.write !== 'function') {
|
|
309
|
-
if (callback) callback();
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
const code = dir < 0 ? '\x1b[1K' : dir > 0 ? '\x1b[0K' : '\x1b[2K';
|
|
313
|
-
return stream.write(code, callback);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export function clearScreenDown(stream: Writable, callback?: () => void): boolean {
|
|
317
|
-
if (!stream || typeof stream.write !== 'function') {
|
|
318
|
-
if (callback) callback();
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
return stream.write('\x1b[0J', callback);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export function cursorTo(
|
|
325
|
-
stream: Writable,
|
|
326
|
-
x: number,
|
|
327
|
-
y?: number | (() => void),
|
|
328
|
-
callback?: () => void,
|
|
329
|
-
): boolean {
|
|
330
|
-
if (!stream || typeof stream.write !== 'function') {
|
|
331
|
-
if (typeof y === 'function') y();
|
|
332
|
-
else if (callback) callback();
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
if (typeof y === 'function') { callback = y; y = undefined; }
|
|
336
|
-
const code = typeof y === 'number' ? `\x1b[${y + 1};${x + 1}H` : `\x1b[${x + 1}G`;
|
|
337
|
-
return stream.write(code, callback);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export function moveCursor(
|
|
341
|
-
stream: Writable,
|
|
342
|
-
dx: number,
|
|
343
|
-
dy: number,
|
|
344
|
-
callback?: () => void,
|
|
345
|
-
): boolean {
|
|
346
|
-
if (!stream || typeof stream.write !== 'function') {
|
|
347
|
-
if (callback) callback();
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
let code = '';
|
|
351
|
-
if (dx > 0) code += `\x1b[${dx}C`;
|
|
352
|
-
else if (dx < 0) code += `\x1b[${-dx}D`;
|
|
353
|
-
if (dy > 0) code += `\x1b[${dy}B`;
|
|
354
|
-
else if (dy < 0) code += `\x1b[${-dy}A`;
|
|
355
|
-
if (code) return stream.write(code, callback);
|
|
356
|
-
if (callback) callback();
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// ── Keypress parser ───────────────────────────────────────────────────────────
|
|
361
|
-
// Ported from refs/node/lib/internal/readline/utils.js (emitKeys generator)
|
|
362
|
-
// Original: Node.js contributors, MIT.
|
|
363
|
-
// Rewritten for TypeScript / GJS without Node.js primordials.
|
|
364
|
-
|
|
365
|
-
export interface Key {
|
|
366
|
-
sequence: string;
|
|
367
|
-
name: string | undefined;
|
|
368
|
-
ctrl: boolean;
|
|
369
|
-
meta: boolean;
|
|
370
|
-
shift: boolean;
|
|
371
|
-
code?: string;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const ESCAPE_CODE_TIMEOUT = 500;
|
|
375
|
-
|
|
376
|
-
function* emitKeys(stream: { emit(event: string, ...args: unknown[]): boolean }): Generator<void, void, string> {
|
|
377
|
-
while (true) {
|
|
378
|
-
let ch: string = yield;
|
|
379
|
-
let s = ch;
|
|
380
|
-
let escaped = false;
|
|
381
|
-
const key: Key = { sequence: '', name: undefined, ctrl: false, meta: false, shift: false };
|
|
382
|
-
|
|
383
|
-
if (ch === '\x1b') {
|
|
384
|
-
escaped = true;
|
|
385
|
-
s += (ch = yield);
|
|
386
|
-
if (ch === '\x1b') s += (ch = yield);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (escaped && (ch === 'O' || ch === '[')) {
|
|
390
|
-
let code = ch;
|
|
391
|
-
let modifier = 0;
|
|
392
|
-
|
|
393
|
-
if (ch === 'O') {
|
|
394
|
-
s += (ch = yield);
|
|
395
|
-
if (ch >= '0' && ch <= '9') { modifier = ch.charCodeAt(0) - 1; s += (ch = yield); }
|
|
396
|
-
code += ch;
|
|
397
|
-
} else if (ch === '[') {
|
|
398
|
-
s += (ch = yield);
|
|
399
|
-
if (ch === '[') { code += ch; s += (ch = yield); }
|
|
400
|
-
const cmdStart = s.length - 1;
|
|
401
|
-
if (ch >= '0' && ch <= '9') {
|
|
402
|
-
s += (ch = yield);
|
|
403
|
-
if (ch >= '0' && ch <= '9') { s += (ch = yield); if (ch >= '0' && ch <= '9') s += (ch = yield); }
|
|
404
|
-
}
|
|
405
|
-
if (ch === ';') { s += (ch = yield); if (ch >= '0' && ch <= '9') s += (yield); }
|
|
406
|
-
const cmd = s.slice(cmdStart);
|
|
407
|
-
let match: RegExpExecArray | null;
|
|
408
|
-
if ((match = /^(?:(\d\d?)(?:;(\d))?([~^$])|(\d{3}~))$/.exec(cmd))) {
|
|
409
|
-
if (match[4]) { code += match[4]; } else { code += match[1] + match[3]; modifier = (parseInt(match[2] ?? '1', 10) || 1) - 1; }
|
|
410
|
-
} else if ((match = /^((\d;)?(\d))?([A-Za-z])$/.exec(cmd))) {
|
|
411
|
-
code += match[4]; modifier = (parseInt(match[3] ?? '1', 10) || 1) - 1;
|
|
412
|
-
} else { code += cmd; }
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
key.ctrl = !!(modifier & 4); key.meta = !!(modifier & 10); key.shift = !!(modifier & 1); key.code = code;
|
|
416
|
-
|
|
417
|
-
switch (code) {
|
|
418
|
-
case '[P': case 'OP': case '[11~': case '[[A': key.name = 'f1'; break;
|
|
419
|
-
case '[Q': case 'OQ': case '[12~': case '[[B': key.name = 'f2'; break;
|
|
420
|
-
case '[R': case 'OR': case '[13~': case '[[C': key.name = 'f3'; break;
|
|
421
|
-
case '[S': case 'OS': case '[14~': case '[[D': key.name = 'f4'; break;
|
|
422
|
-
case '[15~': case '[[E': key.name = 'f5'; break;
|
|
423
|
-
case '[17~': key.name = 'f6'; break; case '[18~': key.name = 'f7'; break;
|
|
424
|
-
case '[19~': key.name = 'f8'; break; case '[20~': key.name = 'f9'; break;
|
|
425
|
-
case '[21~': key.name = 'f10'; break; case '[23~': key.name = 'f11'; break;
|
|
426
|
-
case '[24~': key.name = 'f12'; break;
|
|
427
|
-
case '[200~': key.name = 'paste-start'; break; case '[201~': key.name = 'paste-end'; break;
|
|
428
|
-
case '[A': case 'OA': key.name = 'up'; break;
|
|
429
|
-
case '[B': case 'OB': key.name = 'down'; break;
|
|
430
|
-
case '[C': case 'OC': key.name = 'right'; break;
|
|
431
|
-
case '[D': case 'OD': key.name = 'left'; break;
|
|
432
|
-
case '[E': case 'OE': key.name = 'clear'; break;
|
|
433
|
-
case '[F': case 'OF': key.name = 'end'; break;
|
|
434
|
-
case '[H': case 'OH': key.name = 'home'; break;
|
|
435
|
-
case '[1~': key.name = 'home'; break; case '[2~': key.name = 'insert'; break;
|
|
436
|
-
case '[3~': key.name = 'delete'; break; case '[4~': key.name = 'end'; break;
|
|
437
|
-
case '[5~': case '[[5~': key.name = 'pageup'; break;
|
|
438
|
-
case '[6~': case '[[6~': key.name = 'pagedown'; break;
|
|
439
|
-
case '[7~': key.name = 'home'; break; case '[8~': key.name = 'end'; break;
|
|
440
|
-
case '[a': key.name = 'up'; key.shift = true; break;
|
|
441
|
-
case '[b': key.name = 'down'; key.shift = true; break;
|
|
442
|
-
case '[c': key.name = 'right'; key.shift = true; break;
|
|
443
|
-
case '[d': key.name = 'left'; key.shift = true; break;
|
|
444
|
-
case '[2$': key.name = 'insert'; key.shift = true; break;
|
|
445
|
-
case '[3$': key.name = 'delete'; key.shift = true; break;
|
|
446
|
-
case '[5$': key.name = 'pageup'; key.shift = true; break;
|
|
447
|
-
case '[6$': key.name = 'pagedown'; key.shift = true; break;
|
|
448
|
-
case '[7$': key.name = 'home'; key.shift = true; break;
|
|
449
|
-
case '[8$': key.name = 'end'; key.shift = true; break;
|
|
450
|
-
case 'Oa': key.name = 'up'; key.ctrl = true; break;
|
|
451
|
-
case 'Ob': key.name = 'down'; key.ctrl = true; break;
|
|
452
|
-
case 'Oc': key.name = 'right'; key.ctrl = true; break;
|
|
453
|
-
case 'Od': key.name = 'left'; key.ctrl = true; break;
|
|
454
|
-
case '[2^': key.name = 'insert'; key.ctrl = true; break;
|
|
455
|
-
case '[3^': key.name = 'delete'; key.ctrl = true; break;
|
|
456
|
-
case '[5^': key.name = 'pageup'; key.ctrl = true; break;
|
|
457
|
-
case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
|
|
458
|
-
case '[7^': key.name = 'home'; key.ctrl = true; break;
|
|
459
|
-
case '[8^': key.name = 'end'; key.ctrl = true; break;
|
|
460
|
-
case '[Z': key.name = 'tab'; key.shift = true; break;
|
|
461
|
-
default: key.name = 'undefined'; break;
|
|
462
|
-
}
|
|
463
|
-
} else if (ch === '\r') {
|
|
464
|
-
key.name = 'return'; key.meta = escaped;
|
|
465
|
-
} else if (ch === '\n') {
|
|
466
|
-
key.name = 'enter'; key.meta = escaped;
|
|
467
|
-
} else if (ch === '\t') {
|
|
468
|
-
key.name = 'tab'; key.meta = escaped;
|
|
469
|
-
} else if (ch === '\b' || ch === '\x7f') {
|
|
470
|
-
key.name = 'backspace'; key.meta = escaped;
|
|
471
|
-
} else if (ch === '\x1b') {
|
|
472
|
-
key.name = 'escape'; key.meta = escaped;
|
|
473
|
-
} else if (ch === ' ') {
|
|
474
|
-
key.name = 'space'; key.meta = escaped;
|
|
475
|
-
} else if (!escaped && ch <= '\x1a') {
|
|
476
|
-
key.name = String.fromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
|
477
|
-
key.ctrl = true;
|
|
478
|
-
} else if (/^[0-9A-Za-z]$/.test(ch)) {
|
|
479
|
-
key.name = ch.toLowerCase(); key.shift = /^[A-Z]$/.test(ch); key.meta = escaped;
|
|
480
|
-
} else if (escaped) {
|
|
481
|
-
key.name = ch.length ? undefined : 'escape'; key.meta = true;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
key.sequence = s;
|
|
485
|
-
if (s.length !== 0 && (key.name !== undefined || escaped)) {
|
|
486
|
-
stream.emit('keypress', escaped ? undefined : s, key);
|
|
487
|
-
} else if (s.length === 1) {
|
|
488
|
-
stream.emit('keypress', s, key);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const _KEYPRESS_DECODER = Symbol('keypress-decoder');
|
|
494
|
-
const _ESCAPE_DECODER = Symbol('escape-decoder');
|
|
495
|
-
|
|
496
|
-
// Idempotent — calling twice on the same stream is a no-op.
|
|
497
|
-
// Ported from refs/node/lib/internal/readline/emitKeypressEvents.js.
|
|
498
|
-
export function emitKeypressEvents(stream: Readable & Record<symbol, unknown>, iface: { escapeCodeTimeout?: number } = {}): void {
|
|
499
|
-
if ((stream as any)[_KEYPRESS_DECODER]) return;
|
|
500
|
-
|
|
501
|
-
(stream as any)[_KEYPRESS_DECODER] = new StringDecoder('utf8');
|
|
502
|
-
(stream as any)[_ESCAPE_DECODER] = emitKeys(stream as any);
|
|
503
|
-
(stream as any)[_ESCAPE_DECODER].next();
|
|
504
|
-
|
|
505
|
-
const escTimeout = iface.escapeCodeTimeout ?? ESCAPE_CODE_TIMEOUT;
|
|
506
|
-
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
507
|
-
const triggerEscape = () => (stream as any)[_ESCAPE_DECODER].next('');
|
|
508
|
-
|
|
509
|
-
function onData(input: Buffer | string): void {
|
|
510
|
-
if ((stream as any).listenerCount('keypress') > 0) {
|
|
511
|
-
const str: string = (stream as any)[_KEYPRESS_DECODER].write(
|
|
512
|
-
typeof input === 'string' ? Buffer.from(input) : input,
|
|
513
|
-
);
|
|
514
|
-
if (str) {
|
|
515
|
-
clearTimeout(timeoutId);
|
|
516
|
-
for (const ch of str) {
|
|
517
|
-
try {
|
|
518
|
-
(stream as any)[_ESCAPE_DECODER].next(ch);
|
|
519
|
-
if (ch === '\x1b') timeoutId = setTimeout(triggerEscape, escTimeout);
|
|
520
|
-
} catch {
|
|
521
|
-
(stream as any)[_ESCAPE_DECODER] = emitKeys(stream as any);
|
|
522
|
-
(stream as any)[_ESCAPE_DECODER].next();
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
} else {
|
|
527
|
-
(stream as any).removeListener('data', onData);
|
|
528
|
-
delete (stream as any)[_KEYPRESS_DECODER];
|
|
529
|
-
delete (stream as any)[_ESCAPE_DECODER];
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
(stream as any).on('data', onData);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
export default {
|
|
537
|
-
Interface,
|
|
538
|
-
createInterface,
|
|
539
|
-
clearLine,
|
|
540
|
-
clearScreenDown,
|
|
541
|
-
cursorTo,
|
|
542
|
-
moveCursor,
|
|
543
|
-
emitKeypressEvents,
|
|
544
|
-
};
|
package/src/promises.spec.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// Tests for readline/promises module
|
|
2
|
-
// Reference: Node.js lib/readline/promises.js
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
5
|
-
import { createInterface, Interface } from 'node:readline/promises';
|
|
6
|
-
import { Readable, Writable, PassThrough } from 'node:stream';
|
|
7
|
-
|
|
8
|
-
export default async () => {
|
|
9
|
-
await describe('readline/promises', async () => {
|
|
10
|
-
await it('should export createInterface function', async () => {
|
|
11
|
-
expect(typeof createInterface).toBe('function');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
await it('should export Interface class', async () => {
|
|
15
|
-
expect(Interface).toBeDefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
await it('should create an interface with input stream', async () => {
|
|
19
|
-
const input = new PassThrough();
|
|
20
|
-
const rl = createInterface({ input });
|
|
21
|
-
expect(rl).toBeDefined();
|
|
22
|
-
expect(rl instanceof Interface).toBe(true);
|
|
23
|
-
rl.close();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
await it('should create an interface with input and output', async () => {
|
|
27
|
-
const input = new PassThrough();
|
|
28
|
-
const output = new PassThrough();
|
|
29
|
-
const rl = createInterface({ input, output });
|
|
30
|
-
expect(rl).toBeDefined();
|
|
31
|
-
rl.close();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
await it('should read lines via async iterator', async () => {
|
|
35
|
-
const input = new PassThrough();
|
|
36
|
-
const rl = createInterface({ input });
|
|
37
|
-
|
|
38
|
-
const lines: string[] = [];
|
|
39
|
-
const done = (async () => {
|
|
40
|
-
for await (const line of rl) {
|
|
41
|
-
lines.push(line);
|
|
42
|
-
}
|
|
43
|
-
})();
|
|
44
|
-
|
|
45
|
-
input.write('hello\n');
|
|
46
|
-
input.write('world\n');
|
|
47
|
-
input.end();
|
|
48
|
-
|
|
49
|
-
await done;
|
|
50
|
-
expect(lines.length).toBe(2);
|
|
51
|
-
expect(lines[0]).toBe('hello');
|
|
52
|
-
expect(lines[1]).toBe('world');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
await it('question should return a promise', async () => {
|
|
56
|
-
const input = new PassThrough();
|
|
57
|
-
const output = new PassThrough();
|
|
58
|
-
const rl = createInterface({ input, output });
|
|
59
|
-
|
|
60
|
-
// Schedule the answer
|
|
61
|
-
setTimeout(() => {
|
|
62
|
-
input.write('my answer\n');
|
|
63
|
-
}, 10);
|
|
64
|
-
|
|
65
|
-
const answer = await rl.question('What? ');
|
|
66
|
-
expect(answer).toBe('my answer');
|
|
67
|
-
rl.close();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await it('should handle multiple questions sequentially', async () => {
|
|
71
|
-
const input = new PassThrough();
|
|
72
|
-
const output = new PassThrough();
|
|
73
|
-
const rl = createInterface({ input, output });
|
|
74
|
-
|
|
75
|
-
setTimeout(() => {
|
|
76
|
-
input.write('first\n');
|
|
77
|
-
}, 10);
|
|
78
|
-
|
|
79
|
-
const answer1 = await rl.question('Q1? ');
|
|
80
|
-
expect(answer1).toBe('first');
|
|
81
|
-
|
|
82
|
-
setTimeout(() => {
|
|
83
|
-
input.write('second\n');
|
|
84
|
-
}, 10);
|
|
85
|
-
|
|
86
|
-
const answer2 = await rl.question('Q2? ');
|
|
87
|
-
expect(answer2).toBe('second');
|
|
88
|
-
|
|
89
|
-
rl.close();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
await it('should close properly', async () => {
|
|
93
|
-
const input = new PassThrough();
|
|
94
|
-
const rl = createInterface({ input });
|
|
95
|
-
|
|
96
|
-
let closed = false;
|
|
97
|
-
rl.on('close', () => { closed = true; });
|
|
98
|
-
rl.close();
|
|
99
|
-
|
|
100
|
-
// Give event loop a tick
|
|
101
|
-
await new Promise<void>((resolve) => setTimeout(resolve, 10));
|
|
102
|
-
expect(closed).toBe(true);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
await it('should emit line events', async () => {
|
|
106
|
-
const input = new PassThrough();
|
|
107
|
-
const rl = createInterface({ input });
|
|
108
|
-
|
|
109
|
-
const lines: string[] = [];
|
|
110
|
-
rl.on('line', (line: string) => lines.push(line));
|
|
111
|
-
|
|
112
|
-
input.write('line1\nline2\n');
|
|
113
|
-
input.end();
|
|
114
|
-
|
|
115
|
-
// Wait for processing
|
|
116
|
-
await new Promise<void>((resolve) => rl.on('close', resolve));
|
|
117
|
-
expect(lines.length).toBe(2);
|
|
118
|
-
expect(lines[0]).toBe('line1');
|
|
119
|
-
expect(lines[1]).toBe('line2');
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
};
|
package/src/promises.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// readline/promises — Promise-based readline API
|
|
2
|
-
// Reference: Node.js lib/readline/promises.js
|
|
3
|
-
|
|
4
|
-
import { Interface as BaseInterface, createInterface as baseCreateInterface } from './index.js';
|
|
5
|
-
import type { InterfaceOptions } from './index.js';
|
|
6
|
-
import type { Readable, Writable } from 'node:stream';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Promise-based readline Interface.
|
|
10
|
-
*/
|
|
11
|
-
export class Interface extends BaseInterface {
|
|
12
|
-
/** Ask a question and return the answer as a Promise. */
|
|
13
|
-
question(query: string, options?: any): Promise<string> {
|
|
14
|
-
return new Promise<string>((resolve) => {
|
|
15
|
-
super.question(query, resolve);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a promise-based readline Interface.
|
|
22
|
-
*/
|
|
23
|
-
export function createInterface(input?: Readable | InterfaceOptions, output?: Writable): Interface {
|
|
24
|
-
if (typeof input === 'object' && input !== null && !('read' in input && typeof (input as any).read === 'function')) {
|
|
25
|
-
const opts = input as InterfaceOptions;
|
|
26
|
-
const rl = new Interface(opts);
|
|
27
|
-
return rl;
|
|
28
|
-
}
|
|
29
|
-
return new Interface({ input: input as Readable, output });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default {
|
|
33
|
-
Interface,
|
|
34
|
-
createInterface,
|
|
35
|
-
};
|