3 * `rawlist` type prompt
7 uniq: require('lodash/uniq'),
8 isString: require('lodash/isString'),
9 isNumber: require('lodash/isNumber'),
10 findIndex: require('lodash/findIndex'),
12 var chalk = require('chalk');
13 var { map, takeUntil } = require('rxjs/operators');
14 var Base = require('./base');
15 var Separator = require('../objects/separator');
16 var observe = require('../utils/events');
17 var Paginator = require('../utils/paginator');
19 class ExpandPrompt extends Base {
20 constructor(questions, rl, answers) {
21 super(questions, rl, answers);
23 if (!this.opt.choices) {
24 this.throwParamError('choices');
27 this.validateChoices(this.opt.choices);
29 // Add the default `help` (/expand) option
30 this.opt.choices.push({
32 name: 'Help, list all options',
36 this.opt.validate = (choice) => {
38 return 'Please enter a valid command';
41 return choice !== 'help';
44 // Setup the default string (capitalize the default key)
45 this.opt.default = this.generateChoicesString(this.opt.choices, this.opt.default);
47 this.paginator = new Paginator(this.screen);
51 * Start the Inquiry session
52 * @param {Function} cb Callback when prompt is done
59 // Save user answer and update prompt to show selected option.
60 var events = observe(this.rl);
61 var validation = this.handleSubmitEvents(
62 events.line.pipe(map(this.getCurrentValue.bind(this)))
64 validation.success.forEach(this.onSubmit.bind(this));
65 validation.error.forEach(this.onError.bind(this));
66 this.keypressObs = events.keypress
67 .pipe(takeUntil(validation.success))
68 .forEach(this.onKeypress.bind(this));
77 * Render the prompt to screen
78 * @return {ExpandPrompt} self
82 var message = this.getQuestion();
83 var bottomContent = '';
85 if (this.status === 'answered') {
86 message += chalk.cyan(this.answer);
87 } else if (this.status === 'expanded') {
88 var choicesStr = renderChoices(this.opt.choices, this.selectedKey);
89 message += this.paginator.paginate(choicesStr, this.selectedKey, this.opt.pageSize);
90 message += '\n Answer: ';
93 message += this.rl.line;
96 bottomContent = chalk.red('>> ') + error;
100 bottomContent = chalk.cyan('>> ') + hint;
103 this.screen.render(message, bottomContent);
106 getCurrentValue(input) {
108 input = this.rawDefault;
111 var selected = this.opt.choices.where({ key: input.toLowerCase().trim() })[0];
116 return selected.value;
120 * Generate the prompt choices string
121 * @return {String} Choices string
127 this.opt.choices.forEach((choice) => {
130 if (choice.type === 'separator') {
131 output += ' ' + choice;
135 var choiceStr = choice.key + ') ' + choice.name;
136 if (this.selectedKey === choice.key) {
137 choiceStr = chalk.cyan(choiceStr);
147 if (state.value === 'help') {
148 this.selectedKey = '';
149 this.status = 'expanded';
154 this.render(state.isValid);
158 * When user press `enter` key
162 this.status = 'answered';
163 var choice = this.opt.choices.where({ value: state.value })[0];
164 this.answer = choice.short || choice.name;
169 this.done(state.value);
173 * When user press a key
177 this.selectedKey = this.rl.line.toLowerCase();
178 var selected = this.opt.choices.where({ key: this.selectedKey })[0];
179 if (this.status === 'expanded') {
182 this.render(null, selected ? selected.name : null);
187 * Validate the choices
188 * @param {Array} choices
191 validateChoices(choices) {
195 choices.filter(Separator.exclude).forEach((choice) => {
196 if (!choice.key || choice.key.length !== 1) {
200 if (keymap[choice.key]) {
201 errors.push(choice.key);
204 keymap[choice.key] = true;
205 choice.key = String(choice.key).toLowerCase();
210 'Format error: `key` param must be a single letter and is required.'
216 'Reserved key error: `key` param cannot be `h` - this value is reserved.'
222 'Duplicate key error: `key` param must be unique. Duplicates: ' +
223 _.uniq(errors).join(', ')
229 * Generate a string out of the choices keys
230 * @param {Array} choices
231 * @param {Number|String} default - the choice index or name to capitalize
232 * @return {String} The rendered choices key string
234 generateChoicesString(choices, defaultChoice) {
235 var defIndex = choices.realLength - 1;
236 if (_.isNumber(defaultChoice) && this.opt.choices.getChoice(defaultChoice)) {
237 defIndex = defaultChoice;
238 } else if (_.isString(defaultChoice)) {
239 let index = _.findIndex(
241 ({ value }) => value === defaultChoice
243 defIndex = index === -1 ? defIndex : index;
246 var defStr = this.opt.choices.pluck('key');
247 this.rawDefault = defStr[defIndex];
248 defStr[defIndex] = String(defStr[defIndex]).toUpperCase();
249 return defStr.join('');
254 * Function for rendering checkbox choices
255 * @param {String} pointer Selected key
256 * @return {String} Rendered content
259 function renderChoices(choices, pointer) {
262 choices.forEach((choice) => {
265 if (choice.type === 'separator') {
266 output += ' ' + choice;
270 var choiceStr = choice.key + ') ' + choice.name;
271 if (pointer === choice.key) {
272 choiceStr = chalk.cyan(choiceStr);
281 module.exports = ExpandPrompt;