You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
5.0 KiB

4 years ago
  1. 'use strict';
  2. const colors = require('ansi-colors');
  3. const SelectPrompt = require('./select');
  4. const placeholder = require('../placeholder');
  5. class FormPrompt extends SelectPrompt {
  6. constructor(options) {
  7. super({ ...options, multiple: true });
  8. this.type = 'form';
  9. this.initial = this.options.initial;
  10. this.align = [this.options.align, 'right'].find(v => v != null);
  11. this.emptyError = '';
  12. this.values = {};
  13. }
  14. async reset(first) {
  15. await super.reset();
  16. if (first === true) this._index = this.index;
  17. this.index = this._index;
  18. this.values = {};
  19. this.choices.forEach(choice => choice.reset && choice.reset());
  20. return this.render();
  21. }
  22. dispatch(char) {
  23. return !!char && this.append(char);
  24. }
  25. append(char) {
  26. let choice = this.focused;
  27. if (!choice) return this.alert();
  28. let { cursor, input } = choice;
  29. choice.value = choice.input = input.slice(0, cursor) + char + input.slice(cursor);
  30. choice.cursor++;
  31. return this.render();
  32. }
  33. delete() {
  34. let choice = this.focused;
  35. if (!choice || choice.cursor <= 0) return this.alert();
  36. let { cursor, input } = choice;
  37. choice.value = choice.input = input.slice(0, cursor - 1) + input.slice(cursor);
  38. choice.cursor--;
  39. return this.render();
  40. }
  41. deleteForward() {
  42. let choice = this.focused;
  43. if (!choice) return this.alert();
  44. let { cursor, input } = choice;
  45. if (input[cursor] === void 0) return this.alert();
  46. let str = `${input}`.slice(0, cursor) + `${input}`.slice(cursor + 1);
  47. choice.value = choice.input = str;
  48. return this.render();
  49. }
  50. right() {
  51. let choice = this.focused;
  52. if (!choice) return this.alert();
  53. if (choice.cursor >= choice.input.length) return this.alert();
  54. choice.cursor++;
  55. return this.render();
  56. }
  57. left() {
  58. let choice = this.focused;
  59. if (!choice) return this.alert();
  60. if (choice.cursor <= 0) return this.alert();
  61. choice.cursor--;
  62. return this.render();
  63. }
  64. space(ch, key) {
  65. return this.dispatch(ch, key);
  66. }
  67. number(ch, key) {
  68. return this.dispatch(ch, key);
  69. }
  70. next() {
  71. let ch = this.focused;
  72. if (!ch) return this.alert();
  73. let { initial, input } = ch;
  74. if (initial && initial.startsWith(input) && input !== initial) {
  75. ch.value = ch.input = initial;
  76. ch.cursor = ch.value.length;
  77. return this.render();
  78. }
  79. return super.next();
  80. }
  81. prev() {
  82. let ch = this.focused;
  83. if (!ch) return this.alert();
  84. if (ch.cursor === 0) return super.prev();
  85. ch.value = ch.input = '';
  86. ch.cursor = 0;
  87. return this.render();
  88. }
  89. separator() {
  90. return '';
  91. }
  92. format(value) {
  93. return !this.state.submitted ? super.format(value) : '';
  94. }
  95. pointer() {
  96. return '';
  97. }
  98. indicator(choice) {
  99. return choice.input ? '⦿' : '⊙';
  100. }
  101. async choiceSeparator(choice, i) {
  102. let sep = await this.resolve(choice.separator, this.state, choice, i) || ':';
  103. return sep ? ' ' + this.styles.disabled(sep) : '';
  104. }
  105. async renderChoice(choice, i) {
  106. await this.onChoice(choice, i);
  107. let { state, styles } = this;
  108. let { cursor, initial = '', name, hint, input = '' } = choice;
  109. let { muted, submitted, primary, danger } = styles;
  110. let help = hint;
  111. let focused = this.index === i;
  112. let validate = choice.validate || (() => true);
  113. let sep = await this.choiceSeparator(choice, i);
  114. let msg = choice.message;
  115. if (this.align === 'right') msg = msg.padStart(this.longest + 1, ' ');
  116. if (this.align === 'left') msg = msg.padEnd(this.longest + 1, ' ');
  117. // re-populate the form values (answers) object
  118. let value = this.values[name] = (input || initial);
  119. let color = input ? 'success' : 'dark';
  120. if ((await validate.call(choice, value, this.state)) !== true) {
  121. color = 'danger';
  122. }
  123. let style = styles[color];
  124. let indicator = style(await this.indicator(choice, i)) + (choice.pad || '');
  125. let indent = this.indent(choice);
  126. let line = () => [indent, indicator, msg + sep, input, help].filter(Boolean).join(' ');
  127. if (state.submitted) {
  128. msg = colors.unstyle(msg);
  129. input = submitted(input);
  130. help = '';
  131. return line();
  132. }
  133. if (choice.format) {
  134. input = await choice.format.call(this, input, choice, i);
  135. } else {
  136. let color = this.styles.muted;
  137. let options = { input, initial, pos: cursor, showCursor: focused, color };
  138. input = placeholder(this, options);
  139. }
  140. if (!this.isValue(input)) {
  141. input = this.styles.muted(this.symbols.ellipsis);
  142. }
  143. if (choice.result) {
  144. this.values[name] = await choice.result.call(this, value, choice, i);
  145. }
  146. if (focused) {
  147. msg = primary(msg);
  148. }
  149. if (choice.error) {
  150. input += (input ? ' ' : '') + danger(choice.error.trim());
  151. } else if (choice.hint) {
  152. input += (input ? ' ' : '') + muted(choice.hint.trim());
  153. }
  154. return line();
  155. }
  156. async submit() {
  157. this.value = this.values;
  158. return super.base.submit.call(this);
  159. }
  160. }
  161. module.exports = FormPrompt;