.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / enquirer / lib / prompts / scale.js
1 'use strict';
2
3 const colors = require('ansi-colors');
4 const ArrayPrompt = require('../types/array');
5 const utils = require('../utils');
6
7 class LikertScale extends ArrayPrompt {
8   constructor(options = {}) {
9     super(options);
10     this.widths = [].concat(options.messageWidth || 50);
11     this.align = [].concat(options.align || 'left');
12     this.linebreak = options.linebreak || false;
13     this.edgeLength = options.edgeLength || 3;
14     this.newline = options.newline || '\n   ';
15     let start = options.startNumber || 1;
16     if (typeof this.scale === 'number') {
17       this.scaleKey = false;
18       this.scale = Array(this.scale).fill(0).map((v, i) => ({ name: i + start }));
19     }
20   }
21
22   async reset() {
23     this.tableized = false;
24     await super.reset();
25     return this.render();
26   }
27
28   tableize() {
29     if (this.tableized === true) return;
30     this.tableized = true;
31     let longest = 0;
32
33     for (let ch of this.choices) {
34       longest = Math.max(longest, ch.message.length);
35       ch.scaleIndex = ch.initial || 2;
36       ch.scale = [];
37
38       for (let i = 0; i < this.scale.length; i++) {
39         ch.scale.push({ index: i });
40       }
41     }
42     this.widths[0] = Math.min(this.widths[0], longest + 3);
43   }
44
45   async dispatch(s, key) {
46     if (this.multiple) {
47       return this[key.name] ? await this[key.name](s, key) : await super.dispatch(s, key);
48     }
49     this.alert();
50   }
51
52   heading(msg, item, i) {
53     return this.styles.strong(msg);
54   }
55
56   separator() {
57     return this.styles.muted(this.symbols.ellipsis);
58   }
59
60   right() {
61     let choice = this.focused;
62     if (choice.scaleIndex >= this.scale.length - 1) return this.alert();
63     choice.scaleIndex++;
64     return this.render();
65   }
66
67   left() {
68     let choice = this.focused;
69     if (choice.scaleIndex <= 0) return this.alert();
70     choice.scaleIndex--;
71     return this.render();
72   }
73
74   indent() {
75     return '';
76   }
77
78   format() {
79     if (this.state.submitted) {
80       let values = this.choices.map(ch => this.styles.info(ch.index));
81       return values.join(', ');
82     }
83     return '';
84   }
85
86   pointer() {
87     return '';
88   }
89
90   /**
91    * Render the scale "Key". Something like:
92    * @return {String}
93    */
94
95   renderScaleKey() {
96     if (this.scaleKey === false) return '';
97     if (this.state.submitted) return '';
98     let scale = this.scale.map(item => `   ${item.name} - ${item.message}`);
99     let key = ['', ...scale].map(item => this.styles.muted(item));
100     return key.join('\n');
101   }
102
103   /**
104    * Render the heading row for the scale.
105    * @return {String}
106    */
107
108   renderScaleHeading(max) {
109     let keys = this.scale.map(ele => ele.name);
110     if (typeof this.options.renderScaleHeading === 'function') {
111       keys = this.options.renderScaleHeading.call(this, max);
112     }
113     let diff = this.scaleLength - keys.join('').length;
114     let spacing = Math.round(diff / (keys.length - 1));
115     let names = keys.map(key => this.styles.strong(key));
116     let headings = names.join(' '.repeat(spacing));
117     let padding = ' '.repeat(this.widths[0]);
118     return this.margin[3] + padding + this.margin[1] + headings;
119   }
120
121   /**
122    * Render a scale indicator => ◯ or ◉ by default
123    */
124
125   scaleIndicator(choice, item, i) {
126     if (typeof this.options.scaleIndicator === 'function') {
127       return this.options.scaleIndicator.call(this, choice, item, i);
128     }
129     let enabled = choice.scaleIndex === item.index;
130     if (item.disabled) return this.styles.hint(this.symbols.radio.disabled);
131     if (enabled) return this.styles.success(this.symbols.radio.on);
132     return this.symbols.radio.off;
133   }
134
135   /**
136    * Render the actual scale => ◯────◯────◉────◯────◯
137    */
138
139   renderScale(choice, i) {
140     let scale = choice.scale.map(item => this.scaleIndicator(choice, item, i));
141     let padding = this.term === 'Hyper' ? '' : ' ';
142     return scale.join(padding + this.symbols.line.repeat(this.edgeLength));
143   }
144
145   /**
146    * Render a choice, including scale =>
147    *   "The website is easy to navigate. ◯───◯───◉───◯───◯"
148    */
149
150   async renderChoice(choice, i) {
151     await this.onChoice(choice, i);
152
153     let focused = this.index === i;
154     let pointer = await this.pointer(choice, i);
155     let hint = await choice.hint;
156
157     if (hint && !utils.hasColor(hint)) {
158       hint = this.styles.muted(hint);
159     }
160
161     let pad = str => this.margin[3] + str.replace(/\s+$/, '').padEnd(this.widths[0], ' ');
162     let newline = this.newline;
163     let ind = this.indent(choice);
164     let message = await this.resolve(choice.message, this.state, choice, i);
165     let scale = await this.renderScale(choice, i);
166     let margin = this.margin[1] + this.margin[3];
167     this.scaleLength = colors.unstyle(scale).length;
168     this.widths[0] = Math.min(this.widths[0], this.width - this.scaleLength - margin.length);
169     let msg = utils.wordWrap(message, { width: this.widths[0], newline });
170     let lines = msg.split('\n').map(line => pad(line) + this.margin[1]);
171
172     if (focused) {
173       scale = this.styles.info(scale);
174       lines = lines.map(line => this.styles.info(line));
175     }
176
177     lines[0] += scale;
178
179     if (this.linebreak) lines.push('');
180     return [ind + pointer, lines.join('\n')].filter(Boolean);
181   }
182
183   async renderChoices() {
184     if (this.state.submitted) return '';
185     this.tableize();
186     let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i));
187     let visible = await Promise.all(choices);
188     let heading = await this.renderScaleHeading();
189     return this.margin[0] + [heading, ...visible.map(v => v.join(' '))].join('\n');
190   }
191
192   async render() {
193     let { submitted, size } = this.state;
194
195     let prefix = await this.prefix();
196     let separator = await this.separator();
197     let message = await this.message();
198
199     let prompt = '';
200     if (this.options.promptLine !== false) {
201       prompt = [prefix, message, separator, ''].join(' ');
202       this.state.prompt = prompt;
203     }
204
205     let header = await this.header();
206     let output = await this.format();
207     let key = await this.renderScaleKey();
208     let help = await this.error() || await this.hint();
209     let body = await this.renderChoices();
210     let footer = await this.footer();
211     let err = this.emptyError;
212
213     if (output) prompt += output;
214     if (help && !prompt.includes(help)) prompt += ' ' + help;
215
216     if (submitted && !output && !body.trim() && this.multiple && err != null) {
217       prompt += this.styles.danger(err);
218     }
219
220     this.clear(size);
221     this.write([header, prompt, key, body, footer].filter(Boolean).join('\n'));
222     if (!this.state.submitted) {
223       this.write(this.margin[2]);
224     }
225     this.restore();
226   }
227
228   submit() {
229     this.value = {};
230     for (let choice of this.choices) {
231       this.value[choice.name] = choice.scaleIndex;
232     }
233     return this.base.submit.call(this);
234   }
235 }
236
237 module.exports = LikertScale;