@gjsify/readline 0.2.0 → 0.3.0

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/index.js CHANGED
@@ -1,10 +1,18 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import { StringDecoder } from "node:string_decoder";
3
+ const LINE_END = /\r\n|\r|\n/;
2
4
  class Interface extends EventEmitter {
3
5
  terminal;
4
6
  line = "";
5
7
  cursor = 0;
6
8
  _input;
7
9
  _output;
10
+ get input() {
11
+ return this._input;
12
+ }
13
+ get output() {
14
+ return this._output;
15
+ }
8
16
  _prompt;
9
17
  _closed = false;
10
18
  _paused = false;
@@ -13,6 +21,12 @@ class Interface extends EventEmitter {
13
21
  _crlfDelay;
14
22
  _lineBuffer = "";
15
23
  _questionCallback = null;
24
+ // Per-listener refs so close() removes only our listeners, not the keypress
25
+ // parser's 'data' listener — which must survive across sequential prompts.
26
+ _boundOnData = null;
27
+ _boundOnEnd = null;
28
+ _boundOnError = null;
29
+ _boundOnKeypress = null;
16
30
  constructor(input, output) {
17
31
  super();
18
32
  let opts;
@@ -29,21 +43,45 @@ class Interface extends EventEmitter {
29
43
  this.history = [];
30
44
  this._crlfDelay = opts.crlfDelay ?? 100;
31
45
  if (this._input) {
32
- this._input.on("data", (chunk) => this._onData(chunk));
33
- this._input.on("end", () => this._onEnd());
34
- this._input.on("error", (err) => this.emit("error", err));
46
+ this._boundOnData = (chunk) => this._onData(chunk);
47
+ this._boundOnEnd = () => this._onEnd();
48
+ this._boundOnError = (err) => this.emit("error", err);
49
+ this._input.on("data", this._boundOnData);
50
+ this._input.on("end", this._boundOnEnd);
51
+ this._input.on("error", this._boundOnError);
35
52
  if ("setEncoding" in this._input && typeof this._input.setEncoding === "function") {
36
53
  this._input.setEncoding("utf8");
37
54
  }
55
+ if (this.terminal) {
56
+ emitKeypressEvents(this._input, this);
57
+ if ("setRawMode" in this._input && typeof this._input.setRawMode === "function") {
58
+ if (!this._input.isRaw) this._input.setRawMode(true);
59
+ }
60
+ if ("resume" in this._input && typeof this._input.resume === "function") {
61
+ this._input.resume();
62
+ }
63
+ this._boundOnKeypress = (str, key) => {
64
+ if (!key) return;
65
+ if (key.name === "backspace" || key.name === "delete") {
66
+ if (this.cursor > 0) {
67
+ this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor);
68
+ this.cursor--;
69
+ }
70
+ } else if (str && str.length === 1 && !key.ctrl && !key.meta && key.name !== "return" && key.name !== "enter" && key.name !== "escape") {
71
+ this.line = this.line.slice(0, this.cursor) + str + this.line.slice(this.cursor);
72
+ this.cursor++;
73
+ }
74
+ };
75
+ this._input.on("keypress", this._boundOnKeypress);
76
+ }
38
77
  }
39
78
  }
40
79
  _onData(chunk) {
41
80
  if (this._closed || this._paused) return;
42
81
  const str = typeof chunk === "string" ? chunk : chunk.toString("utf8");
43
82
  this._lineBuffer += str;
44
- const lineEnd = /\r\n|\r|\n/;
45
83
  let m;
46
- while ((m = lineEnd.exec(this._lineBuffer)) !== null) {
84
+ while ((m = LINE_END.exec(this._lineBuffer)) !== null) {
47
85
  const line = this._lineBuffer.substring(0, m.index);
48
86
  this._lineBuffer = this._lineBuffer.substring(m.index + m[0].length);
49
87
  this._onLine(line);
@@ -53,9 +91,7 @@ class Interface extends EventEmitter {
53
91
  if (line.length > 0 && this._historySize > 0) {
54
92
  if (this.history.length === 0 || this.history[0] !== line) {
55
93
  this.history.unshift(line);
56
- if (this.history.length > this._historySize) {
57
- this.history.pop();
58
- }
94
+ if (this.history.length > this._historySize) this.history.pop();
59
95
  }
60
96
  }
61
97
  if (this._questionCallback) {
@@ -72,53 +108,64 @@ class Interface extends EventEmitter {
72
108
  }
73
109
  this.close();
74
110
  }
75
- /** Set the prompt string. */
76
111
  setPrompt(prompt) {
77
112
  this._prompt = prompt;
78
113
  }
79
- /** Get the current prompt string. */
80
114
  getPrompt() {
81
115
  return this._prompt;
82
116
  }
83
- /** Write the prompt to the output stream. */
84
- prompt(preserveCursor) {
85
- if (this._closed) {
86
- throw new Error("readline was closed");
87
- }
88
- if (this._output) {
89
- this._output.write(this._prompt);
90
- }
117
+ prompt(_preserveCursor) {
118
+ if (this._closed) throw new Error("readline was closed");
119
+ this._output?.write(this._prompt);
91
120
  }
92
121
  question(query, optionsOrCallback, callback) {
93
- if (this._closed) {
94
- throw new Error("readline was closed");
95
- }
122
+ if (this._closed) throw new Error("readline was closed");
96
123
  const cb = typeof optionsOrCallback === "function" ? optionsOrCallback : callback;
97
124
  this._questionCallback = cb;
98
- if (this._output) {
99
- this._output.write(query);
100
- }
125
+ this._output?.write(query);
126
+ }
127
+ clearLine(_dir, callback) {
128
+ this.line = "";
129
+ this.cursor = 0;
130
+ if (callback) callback();
131
+ return true;
101
132
  }
102
- /** Write data to the output stream. */
103
133
  write(data, key) {
104
134
  if (this._closed) return;
135
+ if (key) {
136
+ if (this._input) this._input.emit("keypress", data ?? "", key);
137
+ return;
138
+ }
105
139
  if (data !== null && data !== void 0) {
106
- if (this._output) {
107
- this._output.write(data);
140
+ const str = typeof data === "string" ? data : data.toString("utf8");
141
+ if (str) {
142
+ this.line = this.line.slice(0, this.cursor) + str + this.line.slice(this.cursor);
143
+ this.cursor += str.length;
108
144
  }
145
+ this._output?.write(data);
109
146
  }
110
147
  }
111
- /** Close the interface. */
112
148
  close() {
113
149
  if (this._closed) return;
114
150
  this._closed = true;
115
151
  if (this._input) {
116
- this._input.removeAllListeners("data");
117
- this._input.removeAllListeners("end");
152
+ if (this._boundOnData) this._input.removeListener("data", this._boundOnData);
153
+ if (this._boundOnEnd) this._input.removeListener("end", this._boundOnEnd);
154
+ if (this._boundOnError) this._input.removeListener("error", this._boundOnError);
155
+ if (this._boundOnKeypress) this._input.removeListener("keypress", this._boundOnKeypress);
156
+ this._boundOnData = null;
157
+ this._boundOnEnd = null;
158
+ this._boundOnError = null;
159
+ this._boundOnKeypress = null;
160
+ if (this.terminal && this._input.isRaw && "setRawMode" in this._input && typeof this._input.setRawMode === "function") {
161
+ this._input.setRawMode(false);
162
+ }
163
+ if ("pause" in this._input && typeof this._input.pause === "function") {
164
+ this._input.pause();
165
+ }
118
166
  }
119
167
  this.emit("close");
120
168
  }
121
- /** Pause the input stream. */
122
169
  pause() {
123
170
  if (this._closed) return this;
124
171
  this._paused = true;
@@ -128,7 +175,6 @@ class Interface extends EventEmitter {
128
175
  this.emit("pause");
129
176
  return this;
130
177
  }
131
- /** Resume the input stream. */
132
178
  resume() {
133
179
  if (this._closed) return this;
134
180
  this._paused = false;
@@ -138,15 +184,16 @@ class Interface extends EventEmitter {
138
184
  this.emit("resume");
139
185
  return this;
140
186
  }
141
- /** Get the current line content. */
142
187
  getCursorPos() {
143
- return { rows: 0, cols: this.cursor };
188
+ const columns = this._output?.columns ?? 80;
189
+ const len = this._prompt.length + this.cursor;
190
+ return { rows: Math.floor(len / columns), cols: len % columns };
144
191
  }
145
192
  [Symbol.asyncIterator]() {
146
193
  const lines = [];
147
194
  let resolve = null;
148
195
  let done = false;
149
- this.on("line", (line) => {
196
+ const onLine = (line) => {
150
197
  if (resolve) {
151
198
  const r = resolve;
152
199
  resolve = null;
@@ -154,29 +201,29 @@ class Interface extends EventEmitter {
154
201
  } else {
155
202
  lines.push(line);
156
203
  }
157
- });
158
- this.on("close", () => {
204
+ };
205
+ const onClose = () => {
159
206
  done = true;
160
207
  if (resolve) {
161
208
  const r = resolve;
162
209
  resolve = null;
163
210
  r({ value: void 0, done: true });
164
211
  }
165
- });
212
+ };
213
+ this.on("line", onLine);
214
+ this.on("close", onClose);
166
215
  return {
167
- next() {
168
- if (lines.length > 0) {
169
- return Promise.resolve({ value: lines.shift(), done: false });
170
- }
171
- if (done) {
172
- return Promise.resolve({ value: void 0, done: true });
173
- }
216
+ next: () => {
217
+ if (lines.length > 0) return Promise.resolve({ value: lines.shift(), done: false });
218
+ if (done) return Promise.resolve({ value: void 0, done: true });
174
219
  return new Promise((r) => {
175
220
  resolve = r;
176
221
  });
177
222
  },
178
- return() {
223
+ return: () => {
179
224
  done = true;
225
+ this.removeListener("line", onLine);
226
+ this.removeListener("close", onClose);
180
227
  return Promise.resolve({ value: void 0, done: true });
181
228
  },
182
229
  [Symbol.asyncIterator]() {
@@ -229,13 +276,339 @@ function moveCursor(stream, dx, dy, callback) {
229
276
  else if (dx < 0) code += `\x1B[${-dx}D`;
230
277
  if (dy > 0) code += `\x1B[${dy}B`;
231
278
  else if (dy < 0) code += `\x1B[${-dy}A`;
232
- if (code) {
233
- return stream.write(code, callback);
234
- }
279
+ if (code) return stream.write(code, callback);
235
280
  if (callback) callback();
236
281
  return true;
237
282
  }
238
- function emitKeypressEvents(_stream, _interface) {
283
+ const ESCAPE_CODE_TIMEOUT = 500;
284
+ function* emitKeys(stream) {
285
+ while (true) {
286
+ let ch = yield;
287
+ let s = ch;
288
+ let escaped = false;
289
+ const key = { sequence: "", name: void 0, ctrl: false, meta: false, shift: false };
290
+ if (ch === "\x1B") {
291
+ escaped = true;
292
+ s += ch = yield;
293
+ if (ch === "\x1B") s += ch = yield;
294
+ }
295
+ if (escaped && (ch === "O" || ch === "[")) {
296
+ let code = ch;
297
+ let modifier = 0;
298
+ if (ch === "O") {
299
+ s += ch = yield;
300
+ if (ch >= "0" && ch <= "9") {
301
+ modifier = ch.charCodeAt(0) - 1;
302
+ s += ch = yield;
303
+ }
304
+ code += ch;
305
+ } else if (ch === "[") {
306
+ s += ch = yield;
307
+ if (ch === "[") {
308
+ code += ch;
309
+ s += ch = yield;
310
+ }
311
+ const cmdStart = s.length - 1;
312
+ if (ch >= "0" && ch <= "9") {
313
+ s += ch = yield;
314
+ if (ch >= "0" && ch <= "9") {
315
+ s += ch = yield;
316
+ if (ch >= "0" && ch <= "9") s += ch = yield;
317
+ }
318
+ }
319
+ if (ch === ";") {
320
+ s += ch = yield;
321
+ if (ch >= "0" && ch <= "9") s += yield;
322
+ }
323
+ const cmd = s.slice(cmdStart);
324
+ let match;
325
+ if (match = /^(?:(\d\d?)(?:;(\d))?([~^$])|(\d{3}~))$/.exec(cmd)) {
326
+ if (match[4]) {
327
+ code += match[4];
328
+ } else {
329
+ code += match[1] + match[3];
330
+ modifier = (parseInt(match[2] ?? "1", 10) || 1) - 1;
331
+ }
332
+ } else if (match = /^((\d;)?(\d))?([A-Za-z])$/.exec(cmd)) {
333
+ code += match[4];
334
+ modifier = (parseInt(match[3] ?? "1", 10) || 1) - 1;
335
+ } else {
336
+ code += cmd;
337
+ }
338
+ }
339
+ key.ctrl = !!(modifier & 4);
340
+ key.meta = !!(modifier & 10);
341
+ key.shift = !!(modifier & 1);
342
+ key.code = code;
343
+ switch (code) {
344
+ case "[P":
345
+ case "OP":
346
+ case "[11~":
347
+ case "[[A":
348
+ key.name = "f1";
349
+ break;
350
+ case "[Q":
351
+ case "OQ":
352
+ case "[12~":
353
+ case "[[B":
354
+ key.name = "f2";
355
+ break;
356
+ case "[R":
357
+ case "OR":
358
+ case "[13~":
359
+ case "[[C":
360
+ key.name = "f3";
361
+ break;
362
+ case "[S":
363
+ case "OS":
364
+ case "[14~":
365
+ case "[[D":
366
+ key.name = "f4";
367
+ break;
368
+ case "[15~":
369
+ case "[[E":
370
+ key.name = "f5";
371
+ break;
372
+ case "[17~":
373
+ key.name = "f6";
374
+ break;
375
+ case "[18~":
376
+ key.name = "f7";
377
+ break;
378
+ case "[19~":
379
+ key.name = "f8";
380
+ break;
381
+ case "[20~":
382
+ key.name = "f9";
383
+ break;
384
+ case "[21~":
385
+ key.name = "f10";
386
+ break;
387
+ case "[23~":
388
+ key.name = "f11";
389
+ break;
390
+ case "[24~":
391
+ key.name = "f12";
392
+ break;
393
+ case "[200~":
394
+ key.name = "paste-start";
395
+ break;
396
+ case "[201~":
397
+ key.name = "paste-end";
398
+ break;
399
+ case "[A":
400
+ case "OA":
401
+ key.name = "up";
402
+ break;
403
+ case "[B":
404
+ case "OB":
405
+ key.name = "down";
406
+ break;
407
+ case "[C":
408
+ case "OC":
409
+ key.name = "right";
410
+ break;
411
+ case "[D":
412
+ case "OD":
413
+ key.name = "left";
414
+ break;
415
+ case "[E":
416
+ case "OE":
417
+ key.name = "clear";
418
+ break;
419
+ case "[F":
420
+ case "OF":
421
+ key.name = "end";
422
+ break;
423
+ case "[H":
424
+ case "OH":
425
+ key.name = "home";
426
+ break;
427
+ case "[1~":
428
+ key.name = "home";
429
+ break;
430
+ case "[2~":
431
+ key.name = "insert";
432
+ break;
433
+ case "[3~":
434
+ key.name = "delete";
435
+ break;
436
+ case "[4~":
437
+ key.name = "end";
438
+ break;
439
+ case "[5~":
440
+ case "[[5~":
441
+ key.name = "pageup";
442
+ break;
443
+ case "[6~":
444
+ case "[[6~":
445
+ key.name = "pagedown";
446
+ break;
447
+ case "[7~":
448
+ key.name = "home";
449
+ break;
450
+ case "[8~":
451
+ key.name = "end";
452
+ break;
453
+ case "[a":
454
+ key.name = "up";
455
+ key.shift = true;
456
+ break;
457
+ case "[b":
458
+ key.name = "down";
459
+ key.shift = true;
460
+ break;
461
+ case "[c":
462
+ key.name = "right";
463
+ key.shift = true;
464
+ break;
465
+ case "[d":
466
+ key.name = "left";
467
+ key.shift = true;
468
+ break;
469
+ case "[2$":
470
+ key.name = "insert";
471
+ key.shift = true;
472
+ break;
473
+ case "[3$":
474
+ key.name = "delete";
475
+ key.shift = true;
476
+ break;
477
+ case "[5$":
478
+ key.name = "pageup";
479
+ key.shift = true;
480
+ break;
481
+ case "[6$":
482
+ key.name = "pagedown";
483
+ key.shift = true;
484
+ break;
485
+ case "[7$":
486
+ key.name = "home";
487
+ key.shift = true;
488
+ break;
489
+ case "[8$":
490
+ key.name = "end";
491
+ key.shift = true;
492
+ break;
493
+ case "Oa":
494
+ key.name = "up";
495
+ key.ctrl = true;
496
+ break;
497
+ case "Ob":
498
+ key.name = "down";
499
+ key.ctrl = true;
500
+ break;
501
+ case "Oc":
502
+ key.name = "right";
503
+ key.ctrl = true;
504
+ break;
505
+ case "Od":
506
+ key.name = "left";
507
+ key.ctrl = true;
508
+ break;
509
+ case "[2^":
510
+ key.name = "insert";
511
+ key.ctrl = true;
512
+ break;
513
+ case "[3^":
514
+ key.name = "delete";
515
+ key.ctrl = true;
516
+ break;
517
+ case "[5^":
518
+ key.name = "pageup";
519
+ key.ctrl = true;
520
+ break;
521
+ case "[6^":
522
+ key.name = "pagedown";
523
+ key.ctrl = true;
524
+ break;
525
+ case "[7^":
526
+ key.name = "home";
527
+ key.ctrl = true;
528
+ break;
529
+ case "[8^":
530
+ key.name = "end";
531
+ key.ctrl = true;
532
+ break;
533
+ case "[Z":
534
+ key.name = "tab";
535
+ key.shift = true;
536
+ break;
537
+ default:
538
+ key.name = "undefined";
539
+ break;
540
+ }
541
+ } else if (ch === "\r") {
542
+ key.name = "return";
543
+ key.meta = escaped;
544
+ } else if (ch === "\n") {
545
+ key.name = "enter";
546
+ key.meta = escaped;
547
+ } else if (ch === " ") {
548
+ key.name = "tab";
549
+ key.meta = escaped;
550
+ } else if (ch === "\b" || ch === "\x7F") {
551
+ key.name = "backspace";
552
+ key.meta = escaped;
553
+ } else if (ch === "\x1B") {
554
+ key.name = "escape";
555
+ key.meta = escaped;
556
+ } else if (ch === " ") {
557
+ key.name = "space";
558
+ key.meta = escaped;
559
+ } else if (!escaped && ch <= "") {
560
+ key.name = String.fromCharCode(ch.charCodeAt(0) + "a".charCodeAt(0) - 1);
561
+ key.ctrl = true;
562
+ } else if (/^[0-9A-Za-z]$/.test(ch)) {
563
+ key.name = ch.toLowerCase();
564
+ key.shift = /^[A-Z]$/.test(ch);
565
+ key.meta = escaped;
566
+ } else if (escaped) {
567
+ key.name = ch.length ? void 0 : "escape";
568
+ key.meta = true;
569
+ }
570
+ key.sequence = s;
571
+ if (s.length !== 0 && (key.name !== void 0 || escaped)) {
572
+ stream.emit("keypress", escaped ? void 0 : s, key);
573
+ } else if (s.length === 1) {
574
+ stream.emit("keypress", s, key);
575
+ }
576
+ }
577
+ }
578
+ const _KEYPRESS_DECODER = /* @__PURE__ */ Symbol("keypress-decoder");
579
+ const _ESCAPE_DECODER = /* @__PURE__ */ Symbol("escape-decoder");
580
+ function emitKeypressEvents(stream, iface = {}) {
581
+ if (stream[_KEYPRESS_DECODER]) return;
582
+ stream[_KEYPRESS_DECODER] = new StringDecoder("utf8");
583
+ stream[_ESCAPE_DECODER] = emitKeys(stream);
584
+ stream[_ESCAPE_DECODER].next();
585
+ const escTimeout = iface.escapeCodeTimeout ?? ESCAPE_CODE_TIMEOUT;
586
+ let timeoutId;
587
+ const triggerEscape = () => stream[_ESCAPE_DECODER].next("");
588
+ function onData(input) {
589
+ if (stream.listenerCount("keypress") > 0) {
590
+ const str = stream[_KEYPRESS_DECODER].write(
591
+ typeof input === "string" ? Buffer.from(input) : input
592
+ );
593
+ if (str) {
594
+ clearTimeout(timeoutId);
595
+ for (const ch of str) {
596
+ try {
597
+ stream[_ESCAPE_DECODER].next(ch);
598
+ if (ch === "\x1B") timeoutId = setTimeout(triggerEscape, escTimeout);
599
+ } catch {
600
+ stream[_ESCAPE_DECODER] = emitKeys(stream);
601
+ stream[_ESCAPE_DECODER].next();
602
+ }
603
+ }
604
+ }
605
+ } else {
606
+ stream.removeListener("data", onData);
607
+ delete stream[_KEYPRESS_DECODER];
608
+ delete stream[_ESCAPE_DECODER];
609
+ }
610
+ }
611
+ stream.on("data", onData);
239
612
  }
240
613
  var index_default = {
241
614
  Interface,
@@ -12,15 +12,14 @@ export interface InterfaceOptions {
12
12
  escapeCodeTimeout?: number;
13
13
  tabSize?: number;
14
14
  }
15
- /**
16
- * readline.Interface — reads lines from a Readable stream.
17
- */
18
15
  export declare class Interface extends EventEmitter {
19
16
  terminal: boolean;
20
17
  line: string;
21
18
  cursor: number;
22
19
  private _input;
23
20
  private _output;
21
+ get input(): Readable | null;
22
+ get output(): Writable | null;
24
23
  private _prompt;
25
24
  private _closed;
26
25
  private _paused;
@@ -29,67 +28,51 @@ export declare class Interface extends EventEmitter {
29
28
  private _crlfDelay;
30
29
  private _lineBuffer;
31
30
  private _questionCallback;
31
+ private _boundOnData;
32
+ private _boundOnEnd;
33
+ private _boundOnError;
34
+ private _boundOnKeypress;
32
35
  constructor(input?: Readable | InterfaceOptions, output?: Writable);
33
36
  private _onData;
34
37
  private _onLine;
35
38
  private _onEnd;
36
- /** Set the prompt string. */
37
39
  setPrompt(prompt: string): void;
38
- /** Get the current prompt string. */
39
40
  getPrompt(): string;
40
- /** Write the prompt to the output stream. */
41
- prompt(preserveCursor?: boolean): void;
42
- /**
43
- * Display the query and wait for user input.
44
- */
41
+ prompt(_preserveCursor?: boolean): void;
45
42
  question(query: string, callback: (answer: string) => void): void;
46
43
  question(query: string, options: Record<string, unknown>, callback: (answer: string) => void): void;
47
- /** Write data to the output stream. */
44
+ clearLine(_dir: number, callback?: () => void): boolean;
48
45
  write(data: string | Buffer | null, key?: {
49
46
  ctrl?: boolean;
50
47
  meta?: boolean;
51
48
  shift?: boolean;
52
49
  name?: string;
53
50
  }): void;
54
- /** Close the interface. */
55
51
  close(): void;
56
- /** Pause the input stream. */
57
52
  pause(): this;
58
- /** Resume the input stream. */
59
53
  resume(): this;
60
- /** Get the current line content. */
61
54
  getCursorPos(): {
62
55
  rows: number;
63
56
  cols: number;
64
57
  };
65
58
  [Symbol.asyncIterator](): AsyncIterableIterator<string>;
66
59
  }
67
- /**
68
- * Create a readline Interface.
69
- */
70
60
  export declare function createInterface(input?: Readable | InterfaceOptions, output?: Writable, completer?: InterfaceOptions['completer'], terminal?: boolean): Interface;
71
- /**
72
- * Clear the current line of a TTY stream.
73
- * dir: -1 = to the left, 1 = to the right, 0 = entire line
74
- */
75
61
  export declare function clearLine(stream: Writable, dir: number, callback?: () => void): boolean;
76
- /**
77
- * Clear from cursor to end of screen.
78
- */
79
62
  export declare function clearScreenDown(stream: Writable, callback?: () => void): boolean;
80
- /**
81
- * Move cursor to the specified position.
82
- */
83
63
  export declare function cursorTo(stream: Writable, x: number, y?: number | (() => void), callback?: () => void): boolean;
84
- /**
85
- * Move cursor relative to its current position.
86
- */
87
64
  export declare function moveCursor(stream: Writable, dx: number, dy: number, callback?: () => void): boolean;
88
- /**
89
- * Enable keypress events on a stream (no-op in GJS — full implementation
90
- * requires raw terminal mode which depends on the input stream type).
91
- */
92
- export declare function emitKeypressEvents(_stream: Readable, _interface?: Interface): void;
65
+ export interface Key {
66
+ sequence: string;
67
+ name: string | undefined;
68
+ ctrl: boolean;
69
+ meta: boolean;
70
+ shift: boolean;
71
+ code?: string;
72
+ }
73
+ export declare function emitKeypressEvents(stream: Readable & Record<symbol, unknown>, iface?: {
74
+ escapeCodeTimeout?: number;
75
+ }): void;
93
76
  declare const _default: {
94
77
  Interface: typeof Interface;
95
78
  createInterface: typeof createInterface;