'use strict'; const { SafeStringIterator, Symbol, } = primordials; const { charLengthAt, CSI, emitKeys, } = require('internal/readline/utils'); const { clearTimeout, setTimeout } = require('timers'); const { kEscape, } = CSI; const { StringDecoder } = require('string_decoder'); 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; /** * accepts a readable Stream instance and makes it emit "keypress" events */ function emitKeypressEvents(stream, iface = {}) { if (stream[KEYPRESS_DECODER]) return; 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._sawKeyPress = 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;