index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. /**
  2. * Readline API façade to fix some issues
  3. * @Note: May look a bit like Monkey patching... if you know a better way let me know.
  4. */
  5. "use strict";
  6. var readline = require("readline");
  7. var MuteStream = require("mute-stream");
  8. var codePointAt = require("code-point-at");
  9. var isFullwidthCodePoint = require("is-fullwidth-code-point");
  10. var Interface = module.exports = {};
  11. /**
  12. * Create a readline interface
  13. * @param {Object} opt Readline option hash
  14. * @return {readline} the new readline interface
  15. */
  16. Interface.createInterface = function( opt ) {
  17. opt || (opt = {});
  18. var filteredOpt = opt;
  19. // Default `input` to stdin
  20. filteredOpt.input = opt.input || process.stdin;
  21. // Add mute capabilities to the output
  22. var ms = new MuteStream();
  23. ms.pipe( opt.output || process.stdout );
  24. filteredOpt.output = ms;
  25. // Create the readline
  26. var rl = readline.createInterface( filteredOpt );
  27. // Fix bug with refreshLine
  28. var _refreshLine = rl._refreshLine;
  29. rl._refreshLine = function() {
  30. _refreshLine.call(rl);
  31. var line = this._prompt + this.line;
  32. var cursorPos = this._getCursorPos();
  33. readline.moveCursor(this.output, -line.length, 0);
  34. readline.moveCursor(this.output, cursorPos.cols, 0);
  35. };
  36. // Returns current cursor's position and line
  37. rl._getCursorPos = function() {
  38. var columns = this.columns;
  39. var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
  40. var dispPos = this._getDisplayPos(strBeforeCursor);
  41. var cols = dispPos.cols;
  42. var rows = dispPos.rows;
  43. // If the cursor is on a full-width character which steps over the line,
  44. // move the cursor to the beginning of the next line.
  45. if (cols + 1 === columns &&
  46. this.cursor < this.line.length &&
  47. isFullwidthCodePoint(codePointAt(this.line, this.cursor))) {
  48. rows++;
  49. cols = 0;
  50. }
  51. return {cols: cols, rows: rows};
  52. };
  53. // Returns the last character's display position of the given string
  54. rl._getDisplayPos = function(str) {
  55. var offset = 0;
  56. var col = this.columns;
  57. var row = 0;
  58. var code;
  59. str = stripVTControlCharacters(str);
  60. for (var i = 0, len = str.length; i < len; i++) {
  61. code = codePointAt(str, i);
  62. if (code >= 0x10000) { // surrogates
  63. i++;
  64. }
  65. if (code === 0x0a) { // new line \n
  66. offset = 0;
  67. row += 1;
  68. continue;
  69. }
  70. if (isFullwidthCodePoint(code)) {
  71. if ((offset + 1) % col === 0) {
  72. offset++;
  73. }
  74. offset += 2;
  75. } else {
  76. offset++;
  77. }
  78. }
  79. var cols = offset % col;
  80. var rows = row + (offset - cols) / col;
  81. return {cols: cols, rows: rows};
  82. };
  83. // Prevent arrows from breaking the question line
  84. var origWrite = rl._ttyWrite;
  85. rl._ttyWrite = function( s, key ) {
  86. key || (key = {});
  87. if ( key.name === "up" ) return;
  88. if ( key.name === "down" ) return;
  89. origWrite.apply( this, arguments );
  90. };
  91. return rl;
  92. };
  93. // Regexes used for ansi escape code splitting
  94. var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
  95. var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
  96. '(\\d+)(?:;(\\d+))?([~^$])',
  97. '(?:M([@ #!a`])(.)(.))', // mouse
  98. '(?:1;)?(\\d+)?([a-zA-Z])'
  99. ].join('|') + ')');
  100. /**
  101. * Tries to remove all VT control characters. Use to estimate displayed
  102. * string width. May be buggy due to not running a real state machine
  103. */
  104. function stripVTControlCharacters (str) {
  105. str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), '');
  106. return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), '');
  107. }