readline: implements keypress buffering
There was an underlying assumption in readline.emitKeypressEvents (and by extension emitKey) that the given stream (usually process.stdin) would emit 'data' once per keypress, which is not always the case. This commit buffers the input stream and ensures a 'keypress' event is triggered for every keypress (including escape codes). Signed-off-by: Fedor Indutny <fedor@indutny.com>
This commit is contained in:
parent
6569812531
commit
4a90f51bfe
@ -893,7 +893,7 @@ function emitKeypressEvents(stream) {
|
||||
function onData(b) {
|
||||
if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
|
||||
var r = stream._keypressDecoder.write(b);
|
||||
if (r) emitKey(stream, r);
|
||||
if (r) emitKeys(stream, r);
|
||||
} else {
|
||||
// Nobody's watching anyway
|
||||
stream.removeListener('data', onData);
|
||||
@ -947,11 +947,17 @@ exports.emitKeypressEvents = emitKeypressEvents;
|
||||
// Regexes used for ansi escape code splitting
|
||||
var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
|
||||
var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$');
|
||||
var functionKeyCodeReAnywhere =
|
||||
/(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
|
||||
var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
|
||||
'(\\d+)(?:;(\\d+))?([~^$])',
|
||||
'(?:M([@ #!a`])(.)(.))', // mouse
|
||||
'(?:1;)?(\\d+)?([a-zA-Z])'
|
||||
].join('|') + ')');
|
||||
var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source);
|
||||
var escapeCodeReAnywhere = new RegExp([
|
||||
functionKeyCodeReAnywhere.source, metaKeyCodeReAnywhere.source, /\x1b./.source
|
||||
].join('|'));
|
||||
|
||||
function emitKey(stream, s) {
|
||||
function emitKeys(stream, s) {
|
||||
var ch,
|
||||
key = {
|
||||
name: undefined,
|
||||
@ -970,6 +976,16 @@ function emitKey(stream, s) {
|
||||
}
|
||||
}
|
||||
|
||||
var buffer = [];
|
||||
var match;
|
||||
while (match = escapeCodeReAnywhere.exec(s)) {
|
||||
buffer = buffer.concat(s.slice(0, match.index).split(''));
|
||||
buffer.push(match[0]);
|
||||
s = s.slice(match.index + match[0].length);
|
||||
}
|
||||
buffer = buffer.concat(s.split(''));
|
||||
|
||||
buffer.forEach(function(s) {
|
||||
key.sequence = s;
|
||||
|
||||
if (s === '\r') {
|
||||
@ -1025,8 +1041,8 @@ function emitKey(stream, s) {
|
||||
// reassemble the key code leaving out leading \x1b's,
|
||||
// the modifier key bitflag and any meaningless "1;" sequence
|
||||
var code = (parts[1] || '') + (parts[2] || '') +
|
||||
(parts[4] || '') + (parts[6] || ''),
|
||||
modifier = (parts[3] || parts[5] || 1) - 1;
|
||||
(parts[4] || '') + (parts[9] || ''),
|
||||
modifier = (parts[3] || parts[8] || 1) - 1;
|
||||
|
||||
// Parse the key modifier
|
||||
key.ctrl = !!(modifier & 4);
|
||||
@ -1131,13 +1147,6 @@ function emitKey(stream, s) {
|
||||
default: key.name = 'undefined'; break;
|
||||
|
||||
}
|
||||
} else if (s.length > 1 && s[0] !== '\x1b') {
|
||||
// Got a longer-than-one string of characters.
|
||||
// Probably a paste, since it wasn't a control sequence.
|
||||
Array.prototype.forEach.call(s, function(c) {
|
||||
emitKey(stream, c);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't emit a key if no name was found
|
||||
@ -1152,6 +1161,7 @@ function emitKey(stream, s) {
|
||||
if (key || ch) {
|
||||
stream.emit('keypress', ch, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -214,6 +214,30 @@ function isWarned(emitter) {
|
||||
assert.equal(callCount, 1);
|
||||
rli.close();
|
||||
|
||||
// keypress
|
||||
[
|
||||
['a'],
|
||||
['\x1b'],
|
||||
['\x1b[31m'],
|
||||
['\x1b[31m', '\x1b[39m'],
|
||||
['\x1b[31m', 'a', '\x1b[39m', 'a']
|
||||
].forEach(function (keypresses) {
|
||||
fi = new FakeInput();
|
||||
callCount = 0;
|
||||
var remainingKeypresses = keypresses.slice();
|
||||
function keypressListener (ch, key) {
|
||||
callCount++;
|
||||
assert.equal(key.sequence, remainingKeypresses.shift());
|
||||
};
|
||||
readline.emitKeypressEvents(fi);
|
||||
fi.on('keypress', keypressListener);
|
||||
fi.emit('data', keypresses.join(''));
|
||||
assert.equal(callCount, keypresses.length);
|
||||
assert.equal(remainingKeypresses.length, 0);
|
||||
fi.removeListener('keypress', keypressListener);
|
||||
fi.emit('data', ''); // removes listener
|
||||
});
|
||||
|
||||
if (terminal) {
|
||||
// question
|
||||
fi = new FakeInput();
|
||||
|
Loading…
x
Reference in New Issue
Block a user