您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

244 行
7.0KB

  1. 'use strict'
  2. var fs = require('fs');
  3. var stripAnsi = require('strip-ansi');
  4. var term = 13; // carriage return
  5. /**
  6. * create -- sync function for reading user input from stdin
  7. * @param {Object} config {
  8. * sigint: {Boolean} exit on ^C
  9. * autocomplete: {StringArray} function({String})
  10. * history: {String} a history control object (see `prompt-sync-history`)
  11. * }
  12. * @returns {Function} prompt function
  13. */
  14. // for ANSI escape codes reference see https://en.wikipedia.org/wiki/ANSI_escape_code
  15. function create(config) {
  16. config = config || {};
  17. var sigint = config.sigint;
  18. var eot = config.eot;
  19. var autocomplete = config.autocomplete =
  20. config.autocomplete || function(){return []};
  21. var history = config.history;
  22. prompt.history = history || {save: function(){}};
  23. prompt.hide = function (ask) { return prompt(ask, {echo: ''}) };
  24. return prompt;
  25. /**
  26. * prompt -- sync function for reading user input from stdin
  27. * @param {String} ask opening question/statement to prompt for
  28. * @param {String} value initial value for the prompt
  29. * @param {Object} opts {
  30. * echo: set to a character to be echoed, default is '*'. Use '' for no echo
  31. * value: {String} initial value for the prompt
  32. * ask: {String} opening question/statement to prompt for, does not override ask param
  33. * autocomplete: {StringArray} function({String})
  34. * }
  35. *
  36. * @returns {string} Returns the string input or (if sigint === false)
  37. * null if user terminates with a ^C
  38. */
  39. function prompt(ask, value, opts) {
  40. var insert = 0, savedinsert = 0, res, i, savedstr;
  41. opts = opts || {};
  42. if (Object(ask) === ask) {
  43. opts = ask;
  44. ask = opts.ask;
  45. } else if (Object(value) === value) {
  46. opts = value;
  47. value = opts.value;
  48. }
  49. ask = ask || '';
  50. var echo = opts.echo;
  51. var masked = 'echo' in opts;
  52. autocomplete = opts.autocomplete || autocomplete;
  53. var fd = (process.platform === 'win32') ?
  54. process.stdin.fd :
  55. fs.openSync('/dev/tty', 'rs');
  56. var wasRaw = process.stdin.isRaw;
  57. if (!wasRaw) { process.stdin.setRawMode && process.stdin.setRawMode(true); }
  58. var buf = Buffer.alloc(3);
  59. var str = '', character, read;
  60. savedstr = '';
  61. if (ask) {
  62. process.stdout.write(ask);
  63. }
  64. var cycle = 0;
  65. var prevComplete;
  66. while (true) {
  67. read = fs.readSync(fd, buf, 0, 3);
  68. if (read > 1) { // received a control sequence
  69. switch(buf.toString()) {
  70. case '\u001b[A': //up arrow
  71. if (masked) break;
  72. if (!history) break;
  73. if (history.atStart()) break;
  74. if (history.atEnd()) {
  75. savedstr = str;
  76. savedinsert = insert;
  77. }
  78. str = history.prev();
  79. insert = str.length;
  80. process.stdout.write('\u001b[2K\u001b[0G' + ask + str);
  81. break;
  82. case '\u001b[B': //down arrow
  83. if (masked) break;
  84. if (!history) break;
  85. if (history.pastEnd()) break;
  86. if (history.atPenultimate()) {
  87. str = savedstr;
  88. insert = savedinsert;
  89. history.next();
  90. } else {
  91. str = history.next();
  92. insert = str.length;
  93. }
  94. process.stdout.write('\u001b[2K\u001b[0G'+ ask + str + '\u001b['+(insert+ask.length+1)+'G');
  95. break;
  96. case '\u001b[D': //left arrow
  97. if (masked) break;
  98. var before = insert;
  99. insert = (--insert < 0) ? 0 : insert;
  100. if (before - insert)
  101. process.stdout.write('\u001b[1D');
  102. break;
  103. case '\u001b[C': //right arrow
  104. if (masked) break;
  105. insert = (++insert > str.length) ? str.length : insert;
  106. process.stdout.write('\u001b[' + (insert+ask.length+1) + 'G');
  107. break;
  108. default:
  109. if (buf.toString()) {
  110. str = str + buf.toString();
  111. str = str.replace(/\0/g, '');
  112. insert = str.length;
  113. promptPrint(masked, ask, echo, str, insert);
  114. process.stdout.write('\u001b[' + (insert+ask.length+1) + 'G');
  115. buf = Buffer.alloc(3);
  116. }
  117. }
  118. continue; // any other 3 character sequence is ignored
  119. }
  120. // if it is not a control character seq, assume only one character is read
  121. character = buf[read-1];
  122. // catch a ^C and return null
  123. if (character == 3){
  124. process.stdout.write('^C\n');
  125. fs.closeSync(fd);
  126. if (sigint) process.exit(130);
  127. process.stdin.setRawMode && process.stdin.setRawMode(wasRaw);
  128. return null;
  129. }
  130. // catch a ^D and exit
  131. if (character == 4) {
  132. if (str.length == 0 && eot) {
  133. process.stdout.write('exit\n');
  134. process.exit(0);
  135. }
  136. }
  137. // catch the terminating character
  138. if (character == term) {
  139. fs.closeSync(fd);
  140. if (!history) break;
  141. if (!masked && str.length) history.push(str);
  142. history.reset();
  143. break;
  144. }
  145. // catch a TAB and implement autocomplete
  146. if (character == 9) { // TAB
  147. res = autocomplete(str);
  148. if (str == res[0]) {
  149. res = autocomplete('');
  150. } else {
  151. prevComplete = res.length;
  152. }
  153. if (res.length == 0) {
  154. process.stdout.write('\t');
  155. continue;
  156. }
  157. var item = res[cycle++] || res[cycle = 0, cycle++];
  158. if (item) {
  159. process.stdout.write('\r\u001b[K' + ask + item);
  160. str = item;
  161. insert = item.length;
  162. }
  163. }
  164. if (character == 127 || (process.platform == 'win32' && character == 8)) { //backspace
  165. if (!insert) continue;
  166. str = str.slice(0, insert-1) + str.slice(insert);
  167. insert--;
  168. process.stdout.write('\u001b[2D');
  169. } else {
  170. if ((character < 32 ) || (character > 126))
  171. continue;
  172. str = str.slice(0, insert) + String.fromCharCode(character) + str.slice(insert);
  173. insert++;
  174. };
  175. promptPrint(masked, ask, echo, str, insert);
  176. }
  177. process.stdout.write('\n')
  178. process.stdin.setRawMode && process.stdin.setRawMode(wasRaw);
  179. return str || value || '';
  180. };
  181. function promptPrint(masked, ask, echo, str, insert) {
  182. if (masked) {
  183. process.stdout.write('\u001b[2K\u001b[0G' + ask + Array(str.length+1).join(echo));
  184. } else {
  185. process.stdout.write('\u001b[s');
  186. if (insert == str.length) {
  187. process.stdout.write('\u001b[2K\u001b[0G'+ ask + str);
  188. } else {
  189. if (ask) {
  190. process.stdout.write('\u001b[2K\u001b[0G'+ ask + str);
  191. } else {
  192. process.stdout.write('\u001b[2K\u001b[0G'+ str + '\u001b[' + (str.length - insert) + 'D');
  193. }
  194. }
  195. // Reposition the cursor to the right of the insertion point
  196. var askLength = stripAnsi(ask).length;
  197. process.stdout.write(`\u001b[${askLength+1+(echo==''? 0:insert)}G`);
  198. }
  199. }
  200. };
  201. module.exports = create;