stream[KEYPRESS_DECODER] = new StringDecoder('utf8'); stream[ESCAPE_DECODER] = emitKeys(stream); stream[ESCAPE_DECODER].next(); const triggerEscape = () => stream[ESCAPE_DECODER].next(''); const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface; let timeoutId; function onData(input) { if (stream.listenerCount('keypress') > 0) { const string = stream[KEYPRESS_DECODER].write(input); if (string) { clearTimeout(timeoutId); // This supports characters of length 2. iface[kSawKeyPress] = charLengthAt(string, 0) === string.length; iface.isCompletionEnabled = false; let length = 0; for (const character of new SafeStringIterator(string)) { length += character.length; if (length === string.length) { iface.isCompletionEnabled = true; } try { stream[ESCAPE_DECODER].next(character); // Escape letter at the tail position if (length === string.length && character === kEscape) { timeoutId = setTimeout(triggerEscape, escapeCodeTimeout); } } catch (err) { // If the generator throws (it could happen in the `keypress` // event), we need to restart it. stream[ESCAPE_DECODER] = emitKeys(stream); stream[ESCAPE_DECODER].next(); throw err; } } } } else { // Nobody's watching anyway stream.removeListener('data', onData); stream.on('newListener', onNewListener); } } function onNewListener(event) { if (event === 'keypress') { stream.on('data', onData); stream.removeListener('newListener', onNewListener); } } if (stream.listenerCount('keypress') > 0) { stream.on('data', onData); } else { stream.on('newListener', onNewListener); } } module.exports = emitKeypressEvents;