Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fbc5805

Browse filesBrowse files
princejwesleyevanlucas
authored andcommitted
readline: keypress trigger for escape character
Fixes: #7379 PR-URL: #7382 Reviewed-By: jasnell - James M Snell <jasnell@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io>
1 parent 5d37b49 commit fbc5805
Copy full SHA for fbc5805

File tree

Expand file treeCollapse file tree

3 files changed

+79
-1
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+79
-1
lines changed
Open diff view settings
Collapse file

‎lib/internal/readline.js‎

Copy file name to clipboardExpand all lines: lib/internal/readline.js
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,11 +376,15 @@ function* emitKeys(stream) {
376376
key.name = ch.toLowerCase();
377377
key.shift = /^[A-Z]$/.test(ch);
378378
key.meta = escaped;
379+
} else if (escaped) {
380+
// Escape sequence timeout
381+
key.name = ch.length ? undefined : 'escape';
382+
key.meta = true;
379383
}
380384

381385
key.sequence = s;
382386

383-
if (key.name !== undefined) {
387+
if (s.length !== 0 && (key.name !== undefined || escaped)) {
384388
/* Named character or sequence */
385389
stream.emit('keypress', escaped ? undefined : s, key);
386390
} else if (s.length === 1) {
Collapse file

‎lib/readline.js‎

Copy file name to clipboardExpand all lines: lib/readline.js
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,9 @@ exports.Interface = Interface;
926926
const KEYPRESS_DECODER = Symbol('keypress-decoder');
927927
const ESCAPE_DECODER = Symbol('escape-decoder');
928928

929+
// GNU readline library - keyseq-timeout is 500ms (default)
930+
const ESCAPE_CODE_TIMEOUT = 500;
931+
929932
function emitKeypressEvents(stream, iface) {
930933
if (stream[KEYPRESS_DECODER]) return;
931934
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
@@ -934,17 +937,26 @@ function emitKeypressEvents(stream, iface) {
934937
stream[ESCAPE_DECODER] = emitKeys(stream);
935938
stream[ESCAPE_DECODER].next();
936939

940+
const escapeCodeTimeout = () => stream[ESCAPE_DECODER].next('');
941+
let timeoutId;
942+
937943
function onData(b) {
938944
if (stream.listenerCount('keypress') > 0) {
939945
var r = stream[KEYPRESS_DECODER].write(b);
940946
if (r) {
947+
clearTimeout(timeoutId);
948+
941949
for (var i = 0; i < r.length; i++) {
942950
if (r[i] === '\t' && typeof r[i + 1] === 'string' && iface) {
943951
iface.isCompletionEnabled = false;
944952
}
945953

946954
try {
947955
stream[ESCAPE_DECODER].next(r[i]);
956+
// Escape letter at the tail position
957+
if (r[i] === '\x1b' && i + 1 === r.length) {
958+
timeoutId = setTimeout(escapeCodeTimeout, ESCAPE_CODE_TIMEOUT);
959+
}
948960
} catch (err) {
949961
// if the generator throws (it could happen in the `keypress`
950962
// event), we need to restart it.
Collapse file

‎test/parallel/test-readline-keys.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-readline-keys.js
+62Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,49 @@ function addTest(sequences, expectedKeys) {
4444
assert.deepStrictEqual(keys, expectedKeys);
4545
}
4646

47+
// Simulate key interval test cases
48+
// Returns a function that takes `next` test case and returns a thunk
49+
// that can be called to run tests in sequence
50+
// e.g.
51+
// addKeyIntervalTest(..)
52+
// (addKeyIntervalTest(..)
53+
// (addKeyIntervalTest(..)(noop)))()
54+
// where noop is a terminal function(() => {}).
55+
56+
const addKeyIntervalTest = (sequences, expectedKeys, interval = 550,
57+
assertDelay = 550) => {
58+
return (next) => () => {
59+
60+
if (!Array.isArray(sequences)) {
61+
sequences = [ sequences ];
62+
}
63+
64+
if (!Array.isArray(expectedKeys)) {
65+
expectedKeys = [ expectedKeys ];
66+
}
67+
68+
expectedKeys = expectedKeys.map(function(k) {
69+
return k ? extend({ ctrl: false, meta: false, shift: false }, k) : k;
70+
});
71+
72+
const keys = [];
73+
fi.on('keypress', (s, k) => keys.push(k));
74+
75+
const emitKeys = ([head, ...tail]) => {
76+
if (head) {
77+
fi.write(head);
78+
setTimeout(() => emitKeys(tail), interval);
79+
} else {
80+
setTimeout(() => {
81+
next();
82+
assert.deepStrictEqual(keys, expectedKeys);
83+
}, assertDelay);
84+
}
85+
};
86+
emitKeys(sequences);
87+
};
88+
};
89+
4790
// regular alphanumerics
4891
addTest('io.JS', [
4992
{ name: 'i', sequence: 'i' },
@@ -149,3 +192,22 @@ addTest('\x1b[31ma\x1b[39ma', [
149192
{ name: 'undefined', sequence: '\x1b[39m', code: '[39m' },
150193
{ name: 'a', sequence: 'a' },
151194
]);
195+
196+
// Reduce array of addKeyIntervalTest(..) right to left
197+
// with () => {} as initial function
198+
const runKeyIntervalTests = [
199+
// escape character
200+
addKeyIntervalTest('\x1b', [
201+
{ name: 'escape', sequence: '\x1b', meta: true }
202+
]),
203+
// chain of escape characters
204+
addKeyIntervalTest('\x1b\x1b\x1b\x1b'.split(''), [
205+
{ name: 'escape', sequence: '\x1b', meta: true },
206+
{ name: 'escape', sequence: '\x1b', meta: true },
207+
{ name: 'escape', sequence: '\x1b', meta: true },
208+
{ name: 'escape', sequence: '\x1b', meta: true }
209+
])
210+
].reverse().reduce((acc, fn) => fn(acc), () => {});
211+
212+
// run key interval tests one after another
213+
runKeyIntervalTests();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.