| 'use strict'; |
| |
| const { |
| SafeStringIterator, |
| Symbol, |
| } = primordials; |
| |
| const { |
| charLengthAt, |
| CSI, |
| emitKeys, |
| } = require('internal/readline/utils'); |
| const { |
| kSawKeyPress, |
| } = require('internal/readline/interface'); |
| |
| 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[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; |