rawlist.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. 'use strict';
  2. /**
  3. * `rawlist` type prompt
  4. */
  5. const chalk = require('chalk');
  6. const { map, takeUntil } = require('rxjs/operators');
  7. const Base = require('./base');
  8. const Separator = require('../objects/separator');
  9. const observe = require('../utils/events');
  10. const Paginator = require('../utils/paginator');
  11. const incrementListIndex = require('../utils/incrementListIndex');
  12. class RawListPrompt extends Base {
  13. constructor(questions, rl, answers) {
  14. super(questions, rl, answers);
  15. this.hiddenLine = '';
  16. this.lastKey = '';
  17. if (!this.opt.choices) {
  18. this.throwParamError('choices');
  19. }
  20. this.opt.validChoices = this.opt.choices.filter(Separator.exclude);
  21. this.selected = 0;
  22. this.rawDefault = 0;
  23. Object.assign(this.opt, {
  24. validate(val) {
  25. return val != null;
  26. },
  27. });
  28. const def = this.opt.default;
  29. if (typeof def === 'number' && def >= 0 && def < this.opt.choices.realLength) {
  30. this.selected = def;
  31. this.rawDefault = def;
  32. } else if (typeof def !== 'number' && def != null) {
  33. const index = this.opt.choices.realChoices.findIndex(({ value }) => value === def);
  34. const safeIndex = Math.max(index, 0);
  35. this.selected = safeIndex;
  36. this.rawDefault = safeIndex;
  37. }
  38. // Make sure no default is set (so it won't be printed)
  39. this.opt.default = null;
  40. const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop;
  41. this.paginator = new Paginator(undefined, { isInfinite: shouldLoop });
  42. }
  43. /**
  44. * Start the Inquiry session
  45. * @param {Function} cb Callback when prompt is done
  46. * @return {this}
  47. */
  48. _run(cb) {
  49. this.done = cb;
  50. // Once user confirm (enter key)
  51. const events = observe(this.rl);
  52. const submit = events.line.pipe(map(this.getCurrentValue.bind(this)));
  53. const validation = this.handleSubmitEvents(submit);
  54. validation.success.forEach(this.onEnd.bind(this));
  55. validation.error.forEach(this.onError.bind(this));
  56. events.normalizedUpKey
  57. .pipe(takeUntil(validation.success))
  58. .forEach(this.onUpKey.bind(this));
  59. events.normalizedDownKey
  60. .pipe(takeUntil(validation.success))
  61. .forEach(this.onDownKey.bind(this));
  62. events.keypress
  63. .pipe(takeUntil(validation.success))
  64. .forEach(this.onKeypress.bind(this));
  65. // Init the prompt
  66. this.render();
  67. return this;
  68. }
  69. /**
  70. * Render the prompt to screen
  71. * @return {RawListPrompt} self
  72. */
  73. render(error) {
  74. // Render question
  75. let message = this.getQuestion();
  76. let bottomContent = '';
  77. if (this.status === 'answered') {
  78. message += chalk.cyan(this.opt.choices.getChoice(this.selected).short);
  79. } else {
  80. const choicesStr = renderChoices(this.opt.choices, this.selected);
  81. message +=
  82. '\n' + this.paginator.paginate(choicesStr, this.selected, this.opt.pageSize);
  83. message += '\n Answer: ';
  84. }
  85. message += this.rl.line;
  86. if (error) {
  87. bottomContent = '\n' + chalk.red('>> ') + error;
  88. }
  89. this.screen.render(message, bottomContent);
  90. }
  91. /**
  92. * When user press `enter` key
  93. */
  94. getCurrentValue(index) {
  95. if (index == null) {
  96. index = this.rawDefault;
  97. } else if (index === '') {
  98. this.selected = this.selected === undefined ? -1 : this.selected;
  99. index = this.selected;
  100. } else {
  101. index -= 1;
  102. }
  103. const choice = this.opt.choices.getChoice(index);
  104. return choice ? choice.value : null;
  105. }
  106. onEnd(state) {
  107. this.status = 'answered';
  108. this.answer = state.value;
  109. // Re-render prompt
  110. this.render();
  111. this.screen.done();
  112. this.done(state.value);
  113. }
  114. onError() {
  115. this.render('Please enter a valid index');
  116. }
  117. /**
  118. * When user press a key
  119. */
  120. onKeypress() {
  121. let index;
  122. if (this.lastKey === 'arrow') {
  123. index = this.hiddenLine.length ? Number(this.hiddenLine) - 1 : 0;
  124. } else {
  125. index = this.rl.line.length ? Number(this.rl.line) - 1 : 0;
  126. }
  127. this.lastKey = '';
  128. if (this.opt.choices.getChoice(index)) {
  129. this.selected = index;
  130. } else {
  131. this.selected = undefined;
  132. }
  133. this.render();
  134. }
  135. /**
  136. * When user press up key
  137. */
  138. onUpKey() {
  139. this.onArrowKey('up');
  140. }
  141. /**
  142. * When user press down key
  143. */
  144. onDownKey() {
  145. this.onArrowKey('down');
  146. }
  147. /**
  148. * When user press up or down key
  149. * @param {String} type Arrow type: up or down
  150. */
  151. onArrowKey(type) {
  152. this.selected = incrementListIndex(this.selected, type, this.opt) || 0;
  153. this.hiddenLine = String(this.selected + 1);
  154. this.rl.line = '';
  155. this.lastKey = 'arrow';
  156. }
  157. }
  158. /**
  159. * Function for rendering list choices
  160. * @param {Number} pointer Position of the pointer
  161. * @return {String} Rendered content
  162. */
  163. function renderChoices(choices, pointer) {
  164. let output = '';
  165. let separatorOffset = 0;
  166. choices.forEach((choice, i) => {
  167. output += output ? '\n ' : ' ';
  168. if (choice.type === 'separator') {
  169. separatorOffset++;
  170. output += ' ' + choice;
  171. return;
  172. }
  173. const index = i - separatorOffset;
  174. let display = index + 1 + ') ' + choice.name;
  175. if (index === pointer) {
  176. display = chalk.cyan(display);
  177. }
  178. output += display;
  179. });
  180. return output;
  181. }
  182. module.exports = RawListPrompt;