1'use strict'; 2 3const { 4 SafeStringIterator, 5 Symbol, 6} = primordials; 7 8const { 9 charLengthAt, 10 CSI, 11 emitKeys, 12} = require('internal/readline/utils'); 13const { 14 kSawKeyPress, 15} = require('internal/readline/interface'); 16 17const { clearTimeout, setTimeout } = require('timers'); 18const { 19 kEscape, 20} = CSI; 21 22const { StringDecoder } = require('string_decoder'); 23 24const KEYPRESS_DECODER = Symbol('keypress-decoder'); 25const ESCAPE_DECODER = Symbol('escape-decoder'); 26 27// GNU readline library - keyseq-timeout is 500ms (default) 28const ESCAPE_CODE_TIMEOUT = 500; 29 30/** 31 * accepts a readable Stream instance and makes it emit "keypress" events 32 */ 33 34function emitKeypressEvents(stream, iface = {}) { 35 if (stream[KEYPRESS_DECODER]) return; 36 37 stream[KEYPRESS_DECODER] = new StringDecoder('utf8'); 38 39 stream[ESCAPE_DECODER] = emitKeys(stream); 40 stream[ESCAPE_DECODER].next(); 41 42 const triggerEscape = () => stream[ESCAPE_DECODER].next(''); 43 const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface; 44 let timeoutId; 45 46 function onData(input) { 47 if (stream.listenerCount('keypress') > 0) { 48 const string = stream[KEYPRESS_DECODER].write(input); 49 if (string) { 50 clearTimeout(timeoutId); 51 52 // This supports characters of length 2. 53 iface[kSawKeyPress] = charLengthAt(string, 0) === string.length; 54 iface.isCompletionEnabled = false; 55 56 let length = 0; 57 for (const character of new SafeStringIterator(string)) { 58 length += character.length; 59 if (length === string.length) { 60 iface.isCompletionEnabled = true; 61 } 62 63 try { 64 stream[ESCAPE_DECODER].next(character); 65 // Escape letter at the tail position 66 if (length === string.length && character === kEscape) { 67 timeoutId = setTimeout(triggerEscape, escapeCodeTimeout); 68 } 69 } catch (err) { 70 // If the generator throws (it could happen in the `keypress` 71 // event), we need to restart it. 72 stream[ESCAPE_DECODER] = emitKeys(stream); 73 stream[ESCAPE_DECODER].next(); 74 throw err; 75 } 76 } 77 } 78 } else { 79 // Nobody's watching anyway 80 stream.removeListener('data', onData); 81 stream.on('newListener', onNewListener); 82 } 83 } 84 85 function onNewListener(event) { 86 if (event === 'keypress') { 87 stream.on('data', onData); 88 stream.removeListener('newListener', onNewListener); 89 } 90 } 91 92 if (stream.listenerCount('keypress') > 0) { 93 stream.on('data', onData); 94 } else { 95 stream.on('newListener', onNewListener); 96 } 97} 98 99module.exports = emitKeypressEvents; 100