readline: keypress trigger for escape character
Fixes: https://github.com/nodejs/node/issues/7379 PR-URL: https://github.com/nodejs/node/pull/7382 Reviewed-By: jasnell - James M Snell <jasnell@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io>
This commit is contained in:
parent
b417087ca7
commit
4b883a3fb4
@ -376,11 +376,15 @@ function* emitKeys(stream) {
|
||||
key.name = ch.toLowerCase();
|
||||
key.shift = /^[A-Z]$/.test(ch);
|
||||
key.meta = escaped;
|
||||
} else if (escaped) {
|
||||
// Escape sequence timeout
|
||||
key.name = ch.length ? undefined : 'escape';
|
||||
key.meta = true;
|
||||
}
|
||||
|
||||
key.sequence = s;
|
||||
|
||||
if (key.name !== undefined) {
|
||||
if (s.length !== 0 && (key.name !== undefined || escaped)) {
|
||||
/* Named character or sequence */
|
||||
stream.emit('keypress', escaped ? undefined : s, key);
|
||||
} else if (s.length === 1) {
|
||||
|
@ -927,6 +927,9 @@ exports.Interface = Interface;
|
||||
const KEYPRESS_DECODER = Symbol('keypress-decoder');
|
||||
const ESCAPE_DECODER = Symbol('escape-decoder');
|
||||
|
||||
// GNU readline library - keyseq-timeout is 500ms (default)
|
||||
const ESCAPE_CODE_TIMEOUT = 500;
|
||||
|
||||
function emitKeypressEvents(stream, iface) {
|
||||
if (stream[KEYPRESS_DECODER]) return;
|
||||
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
|
||||
@ -935,10 +938,15 @@ function emitKeypressEvents(stream, iface) {
|
||||
stream[ESCAPE_DECODER] = emitKeys(stream);
|
||||
stream[ESCAPE_DECODER].next();
|
||||
|
||||
const escapeCodeTimeout = () => stream[ESCAPE_DECODER].next('');
|
||||
let timeoutId;
|
||||
|
||||
function onData(b) {
|
||||
if (stream.listenerCount('keypress') > 0) {
|
||||
var r = stream[KEYPRESS_DECODER].write(b);
|
||||
if (r) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
for (var i = 0; i < r.length; i++) {
|
||||
if (r[i] === '\t' && typeof r[i + 1] === 'string' && iface) {
|
||||
iface.isCompletionEnabled = false;
|
||||
@ -946,6 +954,10 @@ function emitKeypressEvents(stream, iface) {
|
||||
|
||||
try {
|
||||
stream[ESCAPE_DECODER].next(r[i]);
|
||||
// Escape letter at the tail position
|
||||
if (r[i] === '\x1b' && i + 1 === r.length) {
|
||||
timeoutId = setTimeout(escapeCodeTimeout, ESCAPE_CODE_TIMEOUT);
|
||||
}
|
||||
} catch (err) {
|
||||
// if the generator throws (it could happen in the `keypress`
|
||||
// event), we need to restart it.
|
||||
|
@ -44,6 +44,49 @@ function addTest(sequences, expectedKeys) {
|
||||
assert.deepStrictEqual(keys, expectedKeys);
|
||||
}
|
||||
|
||||
// Simulate key interval test cases
|
||||
// Returns a function that takes `next` test case and returns a thunk
|
||||
// that can be called to run tests in sequence
|
||||
// e.g.
|
||||
// addKeyIntervalTest(..)
|
||||
// (addKeyIntervalTest(..)
|
||||
// (addKeyIntervalTest(..)(noop)))()
|
||||
// where noop is a terminal function(() => {}).
|
||||
|
||||
const addKeyIntervalTest = (sequences, expectedKeys, interval = 550,
|
||||
assertDelay = 550) => {
|
||||
return (next) => () => {
|
||||
|
||||
if (!Array.isArray(sequences)) {
|
||||
sequences = [ sequences ];
|
||||
}
|
||||
|
||||
if (!Array.isArray(expectedKeys)) {
|
||||
expectedKeys = [ expectedKeys ];
|
||||
}
|
||||
|
||||
expectedKeys = expectedKeys.map(function(k) {
|
||||
return k ? extend({ ctrl: false, meta: false, shift: false }, k) : k;
|
||||
});
|
||||
|
||||
const keys = [];
|
||||
fi.on('keypress', (s, k) => keys.push(k));
|
||||
|
||||
const emitKeys = ([head, ...tail]) => {
|
||||
if (head) {
|
||||
fi.write(head);
|
||||
setTimeout(() => emitKeys(tail), interval);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
next();
|
||||
assert.deepStrictEqual(keys, expectedKeys);
|
||||
}, assertDelay);
|
||||
}
|
||||
};
|
||||
emitKeys(sequences);
|
||||
};
|
||||
};
|
||||
|
||||
// regular alphanumerics
|
||||
addTest('io.JS', [
|
||||
{ name: 'i', sequence: 'i' },
|
||||
@ -149,3 +192,22 @@ addTest('\x1b[31ma\x1b[39ma', [
|
||||
{ name: 'undefined', sequence: '\x1b[39m', code: '[39m' },
|
||||
{ name: 'a', sequence: 'a' },
|
||||
]);
|
||||
|
||||
// Reduce array of addKeyIntervalTest(..) right to left
|
||||
// with () => {} as initial function
|
||||
const runKeyIntervalTests = [
|
||||
// escape character
|
||||
addKeyIntervalTest('\x1b', [
|
||||
{ name: 'escape', sequence: '\x1b', meta: true }
|
||||
]),
|
||||
// chain of escape characters
|
||||
addKeyIntervalTest('\x1b\x1b\x1b\x1b'.split(''), [
|
||||
{ name: 'escape', sequence: '\x1b', meta: true },
|
||||
{ name: 'escape', sequence: '\x1b', meta: true },
|
||||
{ name: 'escape', sequence: '\x1b', meta: true },
|
||||
{ name: 'escape', sequence: '\x1b', meta: true }
|
||||
])
|
||||
].reverse().reduce((acc, fn) => fn(acc), () => {});
|
||||
|
||||
// run key interval tests one after another
|
||||
runKeyIntervalTests();
|
||||
|
Loading…
x
Reference in New Issue
Block a user