.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / enquirer / lib / prompt.js
1 'use strict';
2
3 const Events = require('events');
4 const colors = require('ansi-colors');
5 const keypress = require('./keypress');
6 const timer = require('./timer');
7 const State = require('./state');
8 const theme = require('./theme');
9 const utils = require('./utils');
10 const ansi = require('./ansi');
11
12 /**
13  * Base class for creating a new Prompt.
14  * @param {Object} `options` Question object.
15  */
16
17 class Prompt extends Events {
18   constructor(options = {}) {
19     super();
20     this.name = options.name;
21     this.type = options.type;
22     this.options = options;
23     theme(this);
24     timer(this);
25     this.state = new State(this);
26     this.initial = [options.initial, options.default].find(v => v != null);
27     this.stdout = options.stdout || process.stdout;
28     this.stdin = options.stdin || process.stdin;
29     this.scale = options.scale || 1;
30     this.term = this.options.term || process.env.TERM_PROGRAM;
31     this.margin = margin(this.options.margin);
32     this.setMaxListeners(0);
33     setOptions(this);
34   }
35
36   async keypress(input, event = {}) {
37     this.keypressed = true;
38     let key = keypress.action(input, keypress(input, event), this.options.actions);
39     this.state.keypress = key;
40     this.emit('keypress', input, key);
41     this.emit('state', this.state.clone());
42     let fn = this.options[key.action] || this[key.action] || this.dispatch;
43     if (typeof fn === 'function') {
44       return await fn.call(this, input, key);
45     }
46     this.alert();
47   }
48
49   alert() {
50     delete this.state.alert;
51     if (this.options.show === false) {
52       this.emit('alert');
53     } else {
54       this.stdout.write(ansi.code.beep);
55     }
56   }
57
58   cursorHide() {
59     this.stdout.write(ansi.cursor.hide());
60     utils.onExit(() => this.cursorShow());
61   }
62
63   cursorShow() {
64     this.stdout.write(ansi.cursor.show());
65   }
66
67   write(str) {
68     if (!str) return;
69     if (this.stdout && this.state.show !== false) {
70       this.stdout.write(str);
71     }
72     this.state.buffer += str;
73   }
74
75   clear(lines = 0) {
76     let buffer = this.state.buffer;
77     this.state.buffer = '';
78     if ((!buffer && !lines) || this.options.show === false) return;
79     this.stdout.write(ansi.cursor.down(lines) + ansi.clear(buffer, this.width));
80   }
81
82   restore() {
83     if (this.state.closed || this.options.show === false) return;
84
85     let { prompt, after, rest } = this.sections();
86     let { cursor, initial = '', input = '', value = '' } = this;
87
88     let size = this.state.size = rest.length;
89     let state = { after, cursor, initial, input, prompt, size, value };
90     let codes = ansi.cursor.restore(state);
91     if (codes) {
92       this.stdout.write(codes);
93     }
94   }
95
96   sections() {
97     let { buffer, input, prompt } = this.state;
98     prompt = colors.unstyle(prompt);
99     let buf = colors.unstyle(buffer);
100     let idx = buf.indexOf(prompt);
101     let header = buf.slice(0, idx);
102     let rest = buf.slice(idx);
103     let lines = rest.split('\n');
104     let first = lines[0];
105     let last = lines[lines.length - 1];
106     let promptLine = prompt + (input ? ' ' + input : '');
107     let len = promptLine.length;
108     let after = len < first.length ? first.slice(len + 1) : '';
109     return { header, prompt: first, after, rest: lines.slice(1), last };
110   }
111
112   async submit() {
113     this.state.submitted = true;
114     this.state.validating = true;
115
116     // this will only be called when the prompt is directly submitted
117     // without initializing, i.e. when the prompt is skipped, etc. Otherwize,
118     // "options.onSubmit" is will be handled by the "initialize()" method.
119     if (this.options.onSubmit) {
120       await this.options.onSubmit.call(this, this.name, this.value, this);
121     }
122
123     let result = this.state.error || await this.validate(this.value, this.state);
124     if (result !== true) {
125       let error = '\n' + this.symbols.pointer + ' ';
126
127       if (typeof result === 'string') {
128         error += result.trim();
129       } else {
130         error += 'Invalid input';
131       }
132
133       this.state.error = '\n' + this.styles.danger(error);
134       this.state.submitted = false;
135       await this.render();
136       await this.alert();
137       this.state.validating = false;
138       this.state.error = void 0;
139       return;
140     }
141
142     this.state.validating = false;
143     await this.render();
144     await this.close();
145
146     this.value = await this.result(this.value);
147     this.emit('submit', this.value);
148   }
149
150   async cancel(err) {
151     this.state.cancelled = this.state.submitted = true;
152
153     await this.render();
154     await this.close();
155
156     if (typeof this.options.onCancel === 'function') {
157       await this.options.onCancel.call(this, this.name, this.value, this);
158     }
159
160     this.emit('cancel', await this.error(err));
161   }
162
163   async close() {
164     this.state.closed = true;
165
166     try {
167       let sections = this.sections();
168       let lines = Math.ceil(sections.prompt.length / this.width);
169       if (sections.rest) {
170         this.write(ansi.cursor.down(sections.rest.length));
171       }
172       this.write('\n'.repeat(lines));
173     } catch (err) { /* do nothing */ }
174
175     this.emit('close');
176   }
177
178   start() {
179     if (!this.stop && this.options.show !== false) {
180       this.stop = keypress.listen(this, this.keypress.bind(this));
181       this.once('close', this.stop);
182     }
183   }
184
185   async skip() {
186     this.skipped = this.options.skip === true;
187     if (typeof this.options.skip === 'function') {
188       this.skipped = await this.options.skip.call(this, this.name, this.value);
189     }
190     return this.skipped;
191   }
192
193   async initialize() {
194     let { format, options, result } = this;
195
196     this.format = () => format.call(this, this.value);
197     this.result = () => result.call(this, this.value);
198
199     if (typeof options.initial === 'function') {
200       this.initial = await options.initial.call(this, this);
201     }
202
203     if (typeof options.onRun === 'function') {
204       await options.onRun.call(this, this);
205     }
206
207     // if "options.onSubmit" is defined, we wrap the "submit" method to guarantee
208     // that "onSubmit" will always called first thing inside the submit
209     // method, regardless of how it's handled in inheriting prompts.
210     if (typeof options.onSubmit === 'function') {
211       let onSubmit = options.onSubmit.bind(this);
212       let submit = this.submit.bind(this);
213       delete this.options.onSubmit;
214       this.submit = async() => {
215         await onSubmit(this.name, this.value, this);
216         return submit();
217       };
218     }
219
220     await this.start();
221     await this.render();
222   }
223
224   render() {
225     throw new Error('expected prompt to have a custom render method');
226   }
227
228   run() {
229     return new Promise(async(resolve, reject) => {
230       this.once('submit', resolve);
231       this.once('cancel', reject);
232       if (await this.skip()) {
233         this.render = () => {};
234         return this.submit();
235       }
236       await this.initialize();
237       this.emit('run');
238     });
239   }
240
241   async element(name, choice, i) {
242     let { options, state, symbols, timers } = this;
243     let timer = timers && timers[name];
244     state.timer = timer;
245     let value = options[name] || state[name] || symbols[name];
246     let val = choice && choice[name] != null ? choice[name] : await value;
247     if (val === '') return val;
248
249     let res = await this.resolve(val, state, choice, i);
250     if (!res && choice && choice[name]) {
251       return this.resolve(value, state, choice, i);
252     }
253     return res;
254   }
255
256   async prefix() {
257     let element = await this.element('prefix') || this.symbols;
258     let timer = this.timers && this.timers.prefix;
259     let state = this.state;
260     state.timer = timer;
261     if (utils.isObject(element)) element = element[state.status] || element.pending;
262     if (!utils.hasColor(element)) {
263       let style = this.styles[state.status] || this.styles.pending;
264       return style(element);
265     }
266     return element;
267   }
268
269   async message() {
270     let message = await this.element('message');
271     if (!utils.hasColor(message)) {
272       return this.styles.strong(message);
273     }
274     return message;
275   }
276
277   async separator() {
278     let element = await this.element('separator') || this.symbols;
279     let timer = this.timers && this.timers.separator;
280     let state = this.state;
281     state.timer = timer;
282     let value = element[state.status] || element.pending || state.separator;
283     let ele = await this.resolve(value, state);
284     if (utils.isObject(ele)) ele = ele[state.status] || ele.pending;
285     if (!utils.hasColor(ele)) {
286       return this.styles.muted(ele);
287     }
288     return ele;
289   }
290
291   async pointer(choice, i) {
292     let val = await this.element('pointer', choice, i);
293
294     if (typeof val === 'string' && utils.hasColor(val)) {
295       return val;
296     }
297
298     if (val) {
299       let styles = this.styles;
300       let focused = this.index === i;
301       let style = focused ? styles.primary : val => val;
302       let ele = await this.resolve(val[focused ? 'on' : 'off'] || val, this.state);
303       let styled = !utils.hasColor(ele) ? style(ele) : ele;
304       return focused ? styled : ' '.repeat(ele.length);
305     }
306   }
307
308   async indicator(choice, i) {
309     let val = await this.element('indicator', choice, i);
310     if (typeof val === 'string' && utils.hasColor(val)) {
311       return val;
312     }
313     if (val) {
314       let styles = this.styles;
315       let enabled = choice.enabled === true;
316       let style = enabled ? styles.success : styles.dark;
317       let ele = val[enabled ? 'on' : 'off'] || val;
318       return !utils.hasColor(ele) ? style(ele) : ele;
319     }
320     return '';
321   }
322
323   body() {
324     return null;
325   }
326
327   footer() {
328     if (this.state.status === 'pending') {
329       return this.element('footer');
330     }
331   }
332
333   header() {
334     if (this.state.status === 'pending') {
335       return this.element('header');
336     }
337   }
338
339   async hint() {
340     if (this.state.status === 'pending' && !this.isValue(this.state.input)) {
341       let hint = await this.element('hint');
342       if (!utils.hasColor(hint)) {
343         return this.styles.muted(hint);
344       }
345       return hint;
346     }
347   }
348
349   error(err) {
350     return !this.state.submitted ? (err || this.state.error) : '';
351   }
352
353   format(value) {
354     return value;
355   }
356
357   result(value) {
358     return value;
359   }
360
361   validate(value) {
362     if (this.options.required === true) {
363       return this.isValue(value);
364     }
365     return true;
366   }
367
368   isValue(value) {
369     return value != null && value !== '';
370   }
371
372   resolve(value, ...args) {
373     return utils.resolve(this, value, ...args);
374   }
375
376   get base() {
377     return Prompt.prototype;
378   }
379
380   get style() {
381     return this.styles[this.state.status];
382   }
383
384   get height() {
385     return this.options.rows || utils.height(this.stdout, 25);
386   }
387   get width() {
388     return this.options.columns || utils.width(this.stdout, 80);
389   }
390   get size() {
391     return { width: this.width, height: this.height };
392   }
393
394   set cursor(value) {
395     this.state.cursor = value;
396   }
397   get cursor() {
398     return this.state.cursor;
399   }
400
401   set input(value) {
402     this.state.input = value;
403   }
404   get input() {
405     return this.state.input;
406   }
407
408   set value(value) {
409     this.state.value = value;
410   }
411   get value() {
412     let { input, value } = this.state;
413     let result = [value, input].find(this.isValue.bind(this));
414     return this.isValue(result) ? result : this.initial;
415   }
416
417   static get prompt() {
418     return options => new this(options).run();
419   }
420 }
421
422 function setOptions(prompt) {
423   let isValidKey = key => {
424     return prompt[key] === void 0 || typeof prompt[key] === 'function';
425   };
426
427   let ignore = [
428     'actions',
429     'choices',
430     'initial',
431     'margin',
432     'roles',
433     'styles',
434     'symbols',
435     'theme',
436     'timers',
437     'value'
438   ];
439
440   let ignoreFn = [
441     'body',
442     'footer',
443     'error',
444     'header',
445     'hint',
446     'indicator',
447     'message',
448     'prefix',
449     'separator',
450     'skip'
451   ];
452
453   for (let key of Object.keys(prompt.options)) {
454     if (ignore.includes(key)) continue;
455     if (/^on[A-Z]/.test(key)) continue;
456     let option = prompt.options[key];
457     if (typeof option === 'function' && isValidKey(key)) {
458       if (!ignoreFn.includes(key)) {
459         prompt[key] = option.bind(prompt);
460       }
461     } else if (typeof prompt[key] !== 'function') {
462       prompt[key] = option;
463     }
464   }
465 }
466
467 function margin(value) {
468   if (typeof value === 'number') {
469     value = [value, value, value, value];
470   }
471   let arr = [].concat(value || []);
472   let pad = i => i % 2 === 0 ? '\n' : ' ';
473   let res = [];
474   for (let i = 0; i < 4; i++) {
475     let char = pad(i);
476     if (arr[i]) {
477       res.push(char.repeat(arr[i]));
478     } else {
479       res.push('');
480     }
481   }
482   return res;
483 }
484
485 module.exports = Prompt;