All files / lib/internal/readline emitKeypressEvents.js

94.79% Statements 91/96
88.89% Branches 16/18
100% Functions 4/4
94.79% Lines 91/96

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 972x 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 158x 158x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 654x 654x 654x 650x 650x 650x 650x 650x 650x 650x 650x 10049x 10049x 650x 650x 10049x 10049x 10049x 10049x 10049x 10x 10x 10049x 1x 1x 1x 1x 1x 1x 10049x 649x 654x         654x 157x 157x 314x 157x 157x 157x 314x 157x 158x   158x 157x 157x 158x 2x 2x  
'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;