| 'use strict'; | 
|   | 
| const colors = require('ansi-colors'); | 
| const Prompt = require('../prompt'); | 
| const roles = require('../roles'); | 
| const utils = require('../utils'); | 
| const { reorder, scrollUp, scrollDown, isObject, swap } = utils; | 
|   | 
| class ArrayPrompt extends Prompt { | 
|   constructor(options) { | 
|     super(options); | 
|     this.cursorHide(); | 
|     this.maxSelected = options.maxSelected || Infinity; | 
|     this.multiple = options.multiple || false; | 
|     this.initial = options.initial || 0; | 
|     this.delay = options.delay || 0; | 
|     this.longest = 0; | 
|     this.num = ''; | 
|   } | 
|   | 
|   async initialize() { | 
|     if (typeof this.options.initial === 'function') { | 
|       this.initial = await this.options.initial.call(this); | 
|     } | 
|     await this.reset(true); | 
|     await super.initialize(); | 
|   } | 
|   | 
|   async reset() { | 
|     let { choices, initial, autofocus, suggest } = this.options; | 
|     this.state._choices = []; | 
|     this.state.choices = []; | 
|   | 
|     this.choices = await Promise.all(await this.toChoices(choices)); | 
|     this.choices.forEach(ch => (ch.enabled = false)); | 
|   | 
|     if (typeof suggest !== 'function' && this.selectable.length === 0) { | 
|       throw new Error('At least one choice must be selectable'); | 
|     } | 
|   | 
|     if (isObject(initial)) initial = Object.keys(initial); | 
|     if (Array.isArray(initial)) { | 
|       if (autofocus != null) this.index = this.findIndex(autofocus); | 
|       initial.forEach(v => this.enable(this.find(v))); | 
|       await this.render(); | 
|     } else { | 
|       if (autofocus != null) initial = autofocus; | 
|       if (typeof initial === 'string') initial = this.findIndex(initial); | 
|       if (typeof initial === 'number' && initial > -1) { | 
|         this.index = Math.max(0, Math.min(initial, this.choices.length)); | 
|         this.enable(this.find(this.index)); | 
|       } | 
|     } | 
|   | 
|     if (this.isDisabled(this.focused)) { | 
|       await this.down(); | 
|     } | 
|   } | 
|   | 
|   async toChoices(value, parent) { | 
|     this.state.loadingChoices = true; | 
|     let choices = []; | 
|     let index = 0; | 
|   | 
|     let toChoices = async(items, parent) => { | 
|       if (typeof items === 'function') items = await items.call(this); | 
|       if (items instanceof Promise) items = await items; | 
|   | 
|       for (let i = 0; i < items.length; i++) { | 
|         let choice = items[i] = await this.toChoice(items[i], index++, parent); | 
|         choices.push(choice); | 
|   | 
|         if (choice.choices) { | 
|           await toChoices(choice.choices, choice); | 
|         } | 
|       } | 
|       return choices; | 
|     }; | 
|   | 
|     return toChoices(value, parent) | 
|       .then(choices => { | 
|         this.state.loadingChoices = false; | 
|         return choices; | 
|       }); | 
|   } | 
|   | 
|   async toChoice(ele, i, parent) { | 
|     if (typeof ele === 'function') ele = await ele.call(this, this); | 
|     if (ele instanceof Promise) ele = await ele; | 
|     if (typeof ele === 'string') ele = { name: ele }; | 
|   | 
|     if (ele.normalized) return ele; | 
|     ele.normalized = true; | 
|   | 
|     let origVal = ele.value; | 
|     let role = roles(ele.role, this.options); | 
|     ele = role(this, ele); | 
|   | 
|     if (typeof ele.disabled === 'string' && !ele.hint) { | 
|       ele.hint = ele.disabled; | 
|       ele.disabled = true; | 
|     } | 
|   | 
|     if (ele.disabled === true && ele.hint == null) { | 
|       ele.hint = '(disabled)'; | 
|     } | 
|   | 
|     // if the choice was already normalized, return it | 
|     if (ele.index != null) return ele; | 
|     ele.name = ele.name || ele.key || ele.title || ele.value || ele.message; | 
|     ele.message = ele.message || ele.name || ''; | 
|     ele.value = [ele.value, ele.name].find(this.isValue.bind(this)); | 
|   | 
|     ele.input = ''; | 
|     ele.index = i; | 
|     ele.cursor = 0; | 
|   | 
|     utils.define(ele, 'parent', parent); | 
|     ele.level = parent ? parent.level + 1 : 1; | 
|     if (ele.indent == null) { | 
|       ele.indent = parent ? parent.indent + '  ' : (ele.indent || ''); | 
|     } | 
|   | 
|     ele.path = parent ? parent.path + '.' + ele.name : ele.name; | 
|     ele.enabled = !!(this.multiple && !this.isDisabled(ele) && (ele.enabled || this.isSelected(ele))); | 
|   | 
|     if (!this.isDisabled(ele)) { | 
|       this.longest = Math.max(this.longest, colors.unstyle(ele.message).length); | 
|     } | 
|   | 
|     // shallow clone the choice first | 
|     let choice = { ...ele }; | 
|   | 
|     // then allow the choice to be reset using the "original" values | 
|     ele.reset = (input = choice.input, value = choice.value) => { | 
|       for (let key of Object.keys(choice)) ele[key] = choice[key]; | 
|       ele.input = input; | 
|       ele.value = value; | 
|     }; | 
|   | 
|     if (origVal == null && typeof ele.initial === 'function') { | 
|       ele.input = await ele.initial.call(this, this.state, ele, i); | 
|     } | 
|   | 
|     return ele; | 
|   } | 
|   | 
|   async onChoice(choice, i) { | 
|     this.emit('choice', choice, i, this); | 
|   | 
|     if (typeof choice.onChoice === 'function') { | 
|       await choice.onChoice.call(this, this.state, choice, i); | 
|     } | 
|   } | 
|   | 
|   async addChoice(ele, i, parent) { | 
|     let choice = await this.toChoice(ele, i, parent); | 
|     this.choices.push(choice); | 
|     this.index = this.choices.length - 1; | 
|     this.limit = this.choices.length; | 
|     return choice; | 
|   } | 
|   | 
|   async newItem(item, i, parent) { | 
|     let ele = { name: 'New choice name?', editable: true, newChoice: true, ...item }; | 
|     let choice = await this.addChoice(ele, i, parent); | 
|   | 
|     choice.updateChoice = () => { | 
|       delete choice.newChoice; | 
|       choice.name = choice.message = choice.input; | 
|       choice.input = ''; | 
|       choice.cursor = 0; | 
|     }; | 
|   | 
|     return this.render(); | 
|   } | 
|   | 
|   indent(choice) { | 
|     if (choice.indent == null) { | 
|       return choice.level > 1 ? '  '.repeat(choice.level - 1) : ''; | 
|     } | 
|     return choice.indent; | 
|   } | 
|   | 
|   dispatch(s, key) { | 
|     if (this.multiple && this[key.name]) return this[key.name](); | 
|     this.alert(); | 
|   } | 
|   | 
|   focus(choice, enabled) { | 
|     if (typeof enabled !== 'boolean') enabled = choice.enabled; | 
|     if (enabled && !choice.enabled && this.selected.length >= this.maxSelected) { | 
|       return this.alert(); | 
|     } | 
|     this.index = choice.index; | 
|     choice.enabled = enabled && !this.isDisabled(choice); | 
|     return choice; | 
|   } | 
|   | 
|   space() { | 
|     if (!this.multiple) return this.alert(); | 
|     this.toggle(this.focused); | 
|     return this.render(); | 
|   } | 
|   | 
|   a() { | 
|     if (this.maxSelected < this.choices.length) return this.alert(); | 
|     let enabled = this.selectable.every(ch => ch.enabled); | 
|     this.choices.forEach(ch => (ch.enabled = !enabled)); | 
|     return this.render(); | 
|   } | 
|   | 
|   i() { | 
|     // don't allow choices to be inverted if it will result in | 
|     // more than the maximum number of allowed selected items. | 
|     if (this.choices.length - this.selected.length > this.maxSelected) { | 
|       return this.alert(); | 
|     } | 
|     this.choices.forEach(ch => (ch.enabled = !ch.enabled)); | 
|     return this.render(); | 
|   } | 
|   | 
|   g(choice = this.focused) { | 
|     if (!this.choices.some(ch => !!ch.parent)) return this.a(); | 
|     this.toggle((choice.parent && !choice.choices) ? choice.parent : choice); | 
|     return this.render(); | 
|   } | 
|   | 
|   toggle(choice, enabled) { | 
|     if (!choice.enabled && this.selected.length >= this.maxSelected) { | 
|       return this.alert(); | 
|     } | 
|   | 
|     if (typeof enabled !== 'boolean') enabled = !choice.enabled; | 
|     choice.enabled = enabled; | 
|   | 
|     if (choice.choices) { | 
|       choice.choices.forEach(ch => this.toggle(ch, enabled)); | 
|     } | 
|   | 
|     let parent = choice.parent; | 
|     while (parent) { | 
|       let choices = parent.choices.filter(ch => this.isDisabled(ch)); | 
|       parent.enabled = choices.every(ch => ch.enabled === true); | 
|       parent = parent.parent; | 
|     } | 
|   | 
|     reset(this, this.choices); | 
|     this.emit('toggle', choice, this); | 
|     return choice; | 
|   } | 
|   | 
|   enable(choice) { | 
|     if (this.selected.length >= this.maxSelected) return this.alert(); | 
|     choice.enabled = !this.isDisabled(choice); | 
|     choice.choices && choice.choices.forEach(this.enable.bind(this)); | 
|     return choice; | 
|   } | 
|   | 
|   disable(choice) { | 
|     choice.enabled = false; | 
|     choice.choices && choice.choices.forEach(this.disable.bind(this)); | 
|     return choice; | 
|   } | 
|   | 
|   number(n) { | 
|     this.num += n; | 
|   | 
|     let number = num => { | 
|       let i = Number(num); | 
|       if (i > this.choices.length - 1) return this.alert(); | 
|   | 
|       let focused = this.focused; | 
|       let choice = this.choices.find(ch => i === ch.index); | 
|   | 
|       if (!choice.enabled && this.selected.length >= this.maxSelected) { | 
|         return this.alert(); | 
|       } | 
|   | 
|       if (this.visible.indexOf(choice) === -1) { | 
|         let choices = reorder(this.choices); | 
|         let actualIdx = choices.indexOf(choice); | 
|   | 
|         if (focused.index > actualIdx) { | 
|           let start = choices.slice(actualIdx, actualIdx + this.limit); | 
|           let end = choices.filter(ch => !start.includes(ch)); | 
|           this.choices = start.concat(end); | 
|         } else { | 
|           let pos = actualIdx - this.limit + 1; | 
|           this.choices = choices.slice(pos).concat(choices.slice(0, pos)); | 
|         } | 
|       } | 
|   | 
|       this.index = this.choices.indexOf(choice); | 
|       this.toggle(this.focused); | 
|       return this.render(); | 
|     }; | 
|   | 
|     clearTimeout(this.numberTimeout); | 
|   | 
|     return new Promise(resolve => { | 
|       let len = this.choices.length; | 
|       let num = this.num; | 
|   | 
|       let handle = (val = false, res) => { | 
|         clearTimeout(this.numberTimeout); | 
|         if (val) res = number(num); | 
|         this.num = ''; | 
|         resolve(res); | 
|       }; | 
|   | 
|       if (num === '0' || (num.length === 1 && Number(num + '0') > len)) { | 
|         return handle(true); | 
|       } | 
|   | 
|       if (Number(num) > len) { | 
|         return handle(false, this.alert()); | 
|       } | 
|   | 
|       this.numberTimeout = setTimeout(() => handle(true), this.delay); | 
|     }); | 
|   } | 
|   | 
|   home() { | 
|     this.choices = reorder(this.choices); | 
|     this.index = 0; | 
|     return this.render(); | 
|   } | 
|   | 
|   end() { | 
|     let pos = this.choices.length - this.limit; | 
|     let choices = reorder(this.choices); | 
|     this.choices = choices.slice(pos).concat(choices.slice(0, pos)); | 
|     this.index = this.limit - 1; | 
|     return this.render(); | 
|   } | 
|   | 
|   first() { | 
|     this.index = 0; | 
|     return this.render(); | 
|   } | 
|   | 
|   last() { | 
|     this.index = this.visible.length - 1; | 
|     return this.render(); | 
|   } | 
|   | 
|   prev() { | 
|     if (this.visible.length <= 1) return this.alert(); | 
|     return this.up(); | 
|   } | 
|   | 
|   next() { | 
|     if (this.visible.length <= 1) return this.alert(); | 
|     return this.down(); | 
|   } | 
|   | 
|   right() { | 
|     if (this.cursor >= this.input.length) return this.alert(); | 
|     this.cursor++; | 
|     return this.render(); | 
|   } | 
|   | 
|   left() { | 
|     if (this.cursor <= 0) return this.alert(); | 
|     this.cursor--; | 
|     return this.render(); | 
|   } | 
|   | 
|   up() { | 
|     let len = this.choices.length; | 
|     let vis = this.visible.length; | 
|     let idx = this.index; | 
|     if (this.options.scroll === false && idx === 0) { | 
|       return this.alert(); | 
|     } | 
|     if (len > vis && idx === 0) { | 
|       return this.scrollUp(); | 
|     } | 
|     this.index = ((idx - 1 % len) + len) % len; | 
|     if (this.isDisabled()) { | 
|       return this.up(); | 
|     } | 
|     return this.render(); | 
|   } | 
|   | 
|   down() { | 
|     let len = this.choices.length; | 
|     let vis = this.visible.length; | 
|     let idx = this.index; | 
|     if (this.options.scroll === false && idx === vis - 1) { | 
|       return this.alert(); | 
|     } | 
|     if (len > vis && idx === vis - 1) { | 
|       return this.scrollDown(); | 
|     } | 
|     this.index = (idx + 1) % len; | 
|     if (this.isDisabled()) { | 
|       return this.down(); | 
|     } | 
|     return this.render(); | 
|   } | 
|   | 
|   scrollUp(i = 0) { | 
|     this.choices = scrollUp(this.choices); | 
|     this.index = i; | 
|     if (this.isDisabled()) { | 
|       return this.up(); | 
|     } | 
|     return this.render(); | 
|   } | 
|   | 
|   scrollDown(i = this.visible.length - 1) { | 
|     this.choices = scrollDown(this.choices); | 
|     this.index = i; | 
|     if (this.isDisabled()) { | 
|       return this.down(); | 
|     } | 
|     return this.render(); | 
|   } | 
|   | 
|   async shiftUp() { | 
|     if (this.options.sort === true) { | 
|       this.sorting = true; | 
|       this.swap(this.index - 1); | 
|       await this.up(); | 
|       this.sorting = false; | 
|       return; | 
|     } | 
|     return this.scrollUp(this.index); | 
|   } | 
|   | 
|   async shiftDown() { | 
|     if (this.options.sort === true) { | 
|       this.sorting = true; | 
|       this.swap(this.index + 1); | 
|       await this.down(); | 
|       this.sorting = false; | 
|       return; | 
|     } | 
|     return this.scrollDown(this.index); | 
|   } | 
|   | 
|   pageUp() { | 
|     if (this.visible.length <= 1) return this.alert(); | 
|     this.limit = Math.max(this.limit - 1, 0); | 
|     this.index = Math.min(this.limit - 1, this.index); | 
|     this._limit = this.limit; | 
|     if (this.isDisabled()) { | 
|       return this.up(); | 
|     } | 
|     return this.render(); | 
|   } | 
|   | 
|   pageDown() { | 
|     if (this.visible.length >= this.choices.length) return this.alert(); | 
|     this.index = Math.max(0, this.index); | 
|     this.limit = Math.min(this.limit + 1, this.choices.length); | 
|     this._limit = this.limit; | 
|     if (this.isDisabled()) { | 
|       return this.down(); | 
|     } | 
|     return this.render(); | 
|   } | 
|   | 
|   swap(pos) { | 
|     swap(this.choices, this.index, pos); | 
|   } | 
|   | 
|   isDisabled(choice = this.focused) { | 
|     let keys = ['disabled', 'collapsed', 'hidden', 'completing', 'readonly']; | 
|     if (choice && keys.some(key => choice[key] === true)) { | 
|       return true; | 
|     } | 
|     return choice && choice.role === 'heading'; | 
|   } | 
|   | 
|   isEnabled(choice = this.focused) { | 
|     if (Array.isArray(choice)) return choice.every(ch => this.isEnabled(ch)); | 
|     if (choice.choices) { | 
|       let choices = choice.choices.filter(ch => !this.isDisabled(ch)); | 
|       return choice.enabled && choices.every(ch => this.isEnabled(ch)); | 
|     } | 
|     return choice.enabled && !this.isDisabled(choice); | 
|   } | 
|   | 
|   isChoice(choice, value) { | 
|     return choice.name === value || choice.index === Number(value); | 
|   } | 
|   | 
|   isSelected(choice) { | 
|     if (Array.isArray(this.initial)) { | 
|       return this.initial.some(value => this.isChoice(choice, value)); | 
|     } | 
|     return this.isChoice(choice, this.initial); | 
|   } | 
|   | 
|   map(names = [], prop = 'value') { | 
|     return [].concat(names || []).reduce((acc, name) => { | 
|       acc[name] = this.find(name, prop); | 
|       return acc; | 
|     }, {}); | 
|   } | 
|   | 
|   filter(value, prop) { | 
|     let isChoice = (ele, i) => [ele.name, i].includes(value); | 
|     let fn = typeof value === 'function' ? value : isChoice; | 
|     let choices = this.options.multiple ? this.state._choices : this.choices; | 
|     let result = choices.filter(fn); | 
|     if (prop) { | 
|       return result.map(ch => ch[prop]); | 
|     } | 
|     return result; | 
|   } | 
|   | 
|   find(value, prop) { | 
|     if (isObject(value)) return prop ? value[prop] : value; | 
|     let isChoice = (ele, i) => [ele.name, i].includes(value); | 
|     let fn = typeof value === 'function' ? value : isChoice; | 
|     let choice = this.choices.find(fn); | 
|     if (choice) { | 
|       return prop ? choice[prop] : choice; | 
|     } | 
|   } | 
|   | 
|   findIndex(value) { | 
|     return this.choices.indexOf(this.find(value)); | 
|   } | 
|   | 
|   async submit() { | 
|     let choice = this.focused; | 
|     if (!choice) return this.alert(); | 
|   | 
|     if (choice.newChoice) { | 
|       if (!choice.input) return this.alert(); | 
|       choice.updateChoice(); | 
|       return this.render(); | 
|     } | 
|   | 
|     if (this.choices.some(ch => ch.newChoice)) { | 
|       return this.alert(); | 
|     } | 
|   | 
|     let { reorder, sort } = this.options; | 
|     let multi = this.multiple === true; | 
|     let value = this.selected; | 
|     if (value === void 0) { | 
|       return this.alert(); | 
|     } | 
|   | 
|     // re-sort choices to original order | 
|     if (Array.isArray(value) && reorder !== false && sort !== true) { | 
|       value = utils.reorder(value); | 
|     } | 
|   | 
|     this.value = multi ? value.map(ch => ch.name) : value.name; | 
|     return super.submit(); | 
|   } | 
|   | 
|   set choices(choices = []) { | 
|     this.state._choices = this.state._choices || []; | 
|     this.state.choices = choices; | 
|   | 
|     for (let choice of choices) { | 
|       if (!this.state._choices.some(ch => ch.name === choice.name)) { | 
|         this.state._choices.push(choice); | 
|       } | 
|     } | 
|   | 
|     if (!this._initial && this.options.initial) { | 
|       this._initial = true; | 
|       let init = this.initial; | 
|       if (typeof init === 'string' || typeof init === 'number') { | 
|         let choice = this.find(init); | 
|         if (choice) { | 
|           this.initial = choice.index; | 
|           this.focus(choice, true); | 
|         } | 
|       } | 
|     } | 
|   } | 
|   get choices() { | 
|     return reset(this, this.state.choices || []); | 
|   } | 
|   | 
|   set visible(visible) { | 
|     this.state.visible = visible; | 
|   } | 
|   get visible() { | 
|     return (this.state.visible || this.choices).slice(0, this.limit); | 
|   } | 
|   | 
|   set limit(num) { | 
|     this.state.limit = num; | 
|   } | 
|   get limit() { | 
|     let { state, options, choices } = this; | 
|     let limit = state.limit || this._limit || options.limit || choices.length; | 
|     return Math.min(limit, this.height); | 
|   } | 
|   | 
|   set value(value) { | 
|     super.value = value; | 
|   } | 
|   get value() { | 
|     if (typeof super.value !== 'string' && super.value === this.initial) { | 
|       return this.input; | 
|     } | 
|     return super.value; | 
|   } | 
|   | 
|   set index(i) { | 
|     this.state.index = i; | 
|   } | 
|   get index() { | 
|     return Math.max(0, this.state ? this.state.index : 0); | 
|   } | 
|   | 
|   get enabled() { | 
|     return this.filter(this.isEnabled.bind(this)); | 
|   } | 
|   | 
|   get focused() { | 
|     let choice = this.choices[this.index]; | 
|     if (choice && this.state.submitted && this.multiple !== true) { | 
|       choice.enabled = true; | 
|     } | 
|     return choice; | 
|   } | 
|   | 
|   get selectable() { | 
|     return this.choices.filter(choice => !this.isDisabled(choice)); | 
|   } | 
|   | 
|   get selected() { | 
|     return this.multiple ? this.enabled : this.focused; | 
|   } | 
| } | 
|   | 
| function reset(prompt, choices) { | 
|   if (choices instanceof Promise) return choices; | 
|   if (typeof choices === 'function') { | 
|     if (utils.isAsyncFn(choices)) return choices; | 
|     choices = choices.call(prompt, prompt); | 
|   } | 
|   for (let choice of choices) { | 
|     if (Array.isArray(choice.choices)) { | 
|       let items = choice.choices.filter(ch => !prompt.isDisabled(ch)); | 
|       choice.enabled = items.every(ch => ch.enabled === true); | 
|     } | 
|     if (prompt.isDisabled(choice) === true) { | 
|       delete choice.enabled; | 
|     } | 
|   } | 
|   return choices; | 
| } | 
|   | 
| module.exports = ArrayPrompt; |