Skip to content

Commit 10147f1

Browse files
aduh95targos
authored andcommitted
readline: move utilities to internal modules
PR-URL: #38466 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent e3b75cb commit 10147f1

File tree

6 files changed

+243
-196
lines changed

6 files changed

+243
-196
lines changed

lib/internal/console/constructor.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,10 @@ const consoleMethods = {
429429
if (this._stdout.isTTY && process.env.TERM !== 'dumb') {
430430
// The require is here intentionally to avoid readline being
431431
// required too early when console is first loaded.
432-
const { cursorTo, clearScreenDown } = require('readline');
432+
const {
433+
cursorTo,
434+
clearScreenDown,
435+
} = require('internal/readline/callbacks');
433436
cursorTo(this._stdout, 0, 0);
434437
clearScreenDown(this._stdout);
435438
}

lib/internal/readline/callbacks.js

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
'use strict';
2+
3+
const {
4+
NumberIsNaN,
5+
} = primordials;
6+
7+
const {
8+
codes: {
9+
ERR_INVALID_ARG_VALUE,
10+
ERR_INVALID_CURSOR_POS,
11+
},
12+
} = require('internal/errors');
13+
14+
const {
15+
validateCallback,
16+
} = require('internal/validators');
17+
const {
18+
CSI,
19+
} = require('internal/readline/utils');
20+
21+
const {
22+
kClearLine,
23+
kClearScreenDown,
24+
kClearToLineBeginning,
25+
kClearToLineEnd,
26+
} = CSI;
27+
28+
29+
/**
30+
* moves the cursor to the x and y coordinate on the given stream
31+
*/
32+
33+
function cursorTo(stream, x, y, callback) {
34+
if (callback !== undefined) {
35+
validateCallback(callback);
36+
}
37+
38+
if (typeof y === 'function') {
39+
callback = y;
40+
y = undefined;
41+
}
42+
43+
if (NumberIsNaN(x)) throw new ERR_INVALID_ARG_VALUE('x', x);
44+
if (NumberIsNaN(y)) throw new ERR_INVALID_ARG_VALUE('y', y);
45+
46+
if (stream == null || (typeof x !== 'number' && typeof y !== 'number')) {
47+
if (typeof callback === 'function') process.nextTick(callback, null);
48+
return true;
49+
}
50+
51+
if (typeof x !== 'number') throw new ERR_INVALID_CURSOR_POS();
52+
53+
const data = typeof y !== 'number' ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`;
54+
return stream.write(data, callback);
55+
}
56+
57+
/**
58+
* moves the cursor relative to its current location
59+
*/
60+
61+
function moveCursor(stream, dx, dy, callback) {
62+
if (callback !== undefined) {
63+
validateCallback(callback);
64+
}
65+
66+
if (stream == null || !(dx || dy)) {
67+
if (typeof callback === 'function') process.nextTick(callback, null);
68+
return true;
69+
}
70+
71+
let data = '';
72+
73+
if (dx < 0) {
74+
data += CSI`${-dx}D`;
75+
} else if (dx > 0) {
76+
data += CSI`${dx}C`;
77+
}
78+
79+
if (dy < 0) {
80+
data += CSI`${-dy}A`;
81+
} else if (dy > 0) {
82+
data += CSI`${dy}B`;
83+
}
84+
85+
return stream.write(data, callback);
86+
}
87+
88+
/**
89+
* clears the current line the cursor is on:
90+
* -1 for left of the cursor
91+
* +1 for right of the cursor
92+
* 0 for the entire line
93+
*/
94+
95+
function clearLine(stream, dir, callback) {
96+
if (callback !== undefined) {
97+
validateCallback(callback);
98+
}
99+
100+
if (stream === null || stream === undefined) {
101+
if (typeof callback === 'function') process.nextTick(callback, null);
102+
return true;
103+
}
104+
105+
const type =
106+
dir < 0 ? kClearToLineBeginning : dir > 0 ? kClearToLineEnd : kClearLine;
107+
return stream.write(type, callback);
108+
}
109+
110+
/**
111+
* clears the screen from the current position of the cursor down
112+
*/
113+
114+
function clearScreenDown(stream, callback) {
115+
if (callback !== undefined) {
116+
validateCallback(callback);
117+
}
118+
119+
if (stream === null || stream === undefined) {
120+
if (typeof callback === 'function') process.nextTick(callback, null);
121+
return true;
122+
}
123+
124+
return stream.write(kClearScreenDown, callback);
125+
}
126+
127+
module.exports = {
128+
clearLine,
129+
clearScreenDown,
130+
cursorTo,
131+
moveCursor,
132+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'use strict';
2+
3+
const {
4+
SafeStringIterator,
5+
Symbol,
6+
} = primordials;
7+
8+
const {
9+
charLengthAt,
10+
CSI,
11+
emitKeys,
12+
} = require('internal/readline/utils');
13+
14+
const { clearTimeout, setTimeout } = require('timers');
15+
const {
16+
kEscape,
17+
} = CSI;
18+
19+
const { StringDecoder } = require('string_decoder');
20+
21+
const KEYPRESS_DECODER = Symbol('keypress-decoder');
22+
const ESCAPE_DECODER = Symbol('escape-decoder');
23+
24+
// GNU readline library - keyseq-timeout is 500ms (default)
25+
const ESCAPE_CODE_TIMEOUT = 500;
26+
27+
/**
28+
* accepts a readable Stream instance and makes it emit "keypress" events
29+
*/
30+
31+
function emitKeypressEvents(stream, iface = {}) {
32+
if (stream[KEYPRESS_DECODER]) return;
33+
34+
stream[KEYPRESS_DECODER] = new StringDecoder('utf8');
35+
36+
stream[ESCAPE_DECODER] = emitKeys(stream);
37+
stream[ESCAPE_DECODER].next();
38+
39+
const triggerEscape = () => stream[ESCAPE_DECODER].next('');
40+
const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface;
41+
let timeoutId;
42+
43+
function onData(input) {
44+
if (stream.listenerCount('keypress') > 0) {
45+
const string = stream[KEYPRESS_DECODER].write(input);
46+
if (string) {
47+
clearTimeout(timeoutId);
48+
49+
// This supports characters of length 2.
50+
iface._sawKeyPress = charLengthAt(string, 0) === string.length;
51+
iface.isCompletionEnabled = false;
52+
53+
let length = 0;
54+
for (const character of new SafeStringIterator(string)) {
55+
length += character.length;
56+
if (length === string.length) {
57+
iface.isCompletionEnabled = true;
58+
}
59+
60+
try {
61+
stream[ESCAPE_DECODER].next(character);
62+
// Escape letter at the tail position
63+
if (length === string.length && character === kEscape) {
64+
timeoutId = setTimeout(triggerEscape, escapeCodeTimeout);
65+
}
66+
} catch (err) {
67+
// If the generator throws (it could happen in the `keypress`
68+
// event), we need to restart it.
69+
stream[ESCAPE_DECODER] = emitKeys(stream);
70+
stream[ESCAPE_DECODER].next();
71+
throw err;
72+
}
73+
}
74+
}
75+
} else {
76+
// Nobody's watching anyway
77+
stream.removeListener('data', onData);
78+
stream.on('newListener', onNewListener);
79+
}
80+
}
81+
82+
function onNewListener(event) {
83+
if (event === 'keypress') {
84+
stream.on('data', onData);
85+
stream.removeListener('newListener', onNewListener);
86+
}
87+
}
88+
89+
if (stream.listenerCount('keypress') > 0) {
90+
stream.on('data', onData);
91+
} else {
92+
stream.on('newListener', onNewListener);
93+
}
94+
}
95+
96+
module.exports = emitKeypressEvents;

lib/internal/repl/utils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const {
4141
clearScreenDown,
4242
cursorTo,
4343
moveCursor,
44-
} = require('readline');
44+
} = require('internal/readline/callbacks');
4545

4646
const {
4747
commonPrefix,

0 commit comments

Comments
 (0)