All files / lib/internal/readline emitKeypressEvents.js

100% Statements 99/99
100% Branches 19/19
100% Functions 4/4
100% Lines 99/99

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 1002x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 250x 250x 249x 249x 249x 249x 249x 249x 249x 249x 249x 249x 249x 827x 826x 826x 820x 820x 820x 820x 820x 820x 820x 820x 11196x 11196x 820x 820x 11196x 11196x 11196x 11196x 11196x 10x 10x 11196x 2x 2x 2x 2x 2x 2x 11196x 818x 827x 1x 1x 1x 1x 827x 249x 249x 496x 248x 248x 248x 496x 249x 250x 2x 250x 247x 247x 250x 2x 2x  
'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;