1'use strict';
2
3const align = {
4    right: alignRight,
5    center: alignCenter
6};
7const top = 0;
8const right = 1;
9const bottom = 2;
10const left = 3;
11class UI {
12    constructor(opts) {
13        var _a;
14        this.width = opts.width;
15        this.wrap = (_a = opts.wrap) !== null && _a !== void 0 ? _a : true;
16        this.rows = [];
17    }
18    span(...args) {
19        const cols = this.div(...args);
20        cols.span = true;
21    }
22    resetOutput() {
23        this.rows = [];
24    }
25    div(...args) {
26        if (args.length === 0) {
27            this.div('');
28        }
29        if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {
30            return this.applyLayoutDSL(args[0]);
31        }
32        const cols = args.map(arg => {
33            if (typeof arg === 'string') {
34                return this.colFromString(arg);
35            }
36            return arg;
37        });
38        this.rows.push(cols);
39        return cols;
40    }
41    shouldApplyLayoutDSL(...args) {
42        return args.length === 1 && typeof args[0] === 'string' &&
43            /[\t\n]/.test(args[0]);
44    }
45    applyLayoutDSL(str) {
46        const rows = str.split('\n').map(row => row.split('\t'));
47        let leftColumnWidth = 0;
48        // simple heuristic for layout, make sure the
49        // second column lines up along the left-hand.
50        // don't allow the first column to take up more
51        // than 50% of the screen.
52        rows.forEach(columns => {
53            if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {
54                leftColumnWidth = Math.min(Math.floor(this.width * 0.5), mixin.stringWidth(columns[0]));
55            }
56        });
57        // generate a table:
58        //  replacing ' ' with padding calculations.
59        //  using the algorithmically generated width.
60        rows.forEach(columns => {
61            this.div(...columns.map((r, i) => {
62                return {
63                    text: r.trim(),
64                    padding: this.measurePadding(r),
65                    width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
66                };
67            }));
68        });
69        return this.rows[this.rows.length - 1];
70    }
71    colFromString(text) {
72        return {
73            text,
74            padding: this.measurePadding(text)
75        };
76    }
77    measurePadding(str) {
78        // measure padding without ansi escape codes
79        const noAnsi = mixin.stripAnsi(str);
80        return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];
81    }
82    toString() {
83        const lines = [];
84        this.rows.forEach(row => {
85            this.rowToString(row, lines);
86        });
87        // don't display any lines with the
88        // hidden flag set.
89        return lines
90            .filter(line => !line.hidden)
91            .map(line => line.text)
92            .join('\n');
93    }
94    rowToString(row, lines) {
95        this.rasterize(row).forEach((rrow, r) => {
96            let str = '';
97            rrow.forEach((col, c) => {
98                const { width } = row[c]; // the width with padding.
99                const wrapWidth = this.negatePadding(row[c]); // the width without padding.
100                let ts = col; // temporary string used during alignment/padding.
101                if (wrapWidth > mixin.stringWidth(col)) {
102                    ts += ' '.repeat(wrapWidth - mixin.stringWidth(col));
103                }
104                // align the string within its column.
105                if (row[c].align && row[c].align !== 'left' && this.wrap) {
106                    const fn = align[row[c].align];
107                    ts = fn(ts, wrapWidth);
108                    if (mixin.stringWidth(ts) < wrapWidth) {
109                        ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1);
110                    }
111                }
112                // apply border and padding to string.
113                const padding = row[c].padding || [0, 0, 0, 0];
114                if (padding[left]) {
115                    str += ' '.repeat(padding[left]);
116                }
117                str += addBorder(row[c], ts, '| ');
118                str += ts;
119                str += addBorder(row[c], ts, ' |');
120                if (padding[right]) {
121                    str += ' '.repeat(padding[right]);
122                }
123                // if prior row is span, try to render the
124                // current row on the prior line.
125                if (r === 0 && lines.length > 0) {
126                    str = this.renderInline(str, lines[lines.length - 1]);
127                }
128            });
129            // remove trailing whitespace.
130            lines.push({
131                text: str.replace(/ +$/, ''),
132                span: row.span
133            });
134        });
135        return lines;
136    }
137    // if the full 'source' can render in
138    // the target line, do so.
139    renderInline(source, previousLine) {
140        const match = source.match(/^ */);
141        const leadingWhitespace = match ? match[0].length : 0;
142        const target = previousLine.text;
143        const targetTextWidth = mixin.stringWidth(target.trimRight());
144        if (!previousLine.span) {
145            return source;
146        }
147        // if we're not applying wrapping logic,
148        // just always append to the span.
149        if (!this.wrap) {
150            previousLine.hidden = true;
151            return target + source;
152        }
153        if (leadingWhitespace < targetTextWidth) {
154            return source;
155        }
156        previousLine.hidden = true;
157        return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft();
158    }
159    rasterize(row) {
160        const rrows = [];
161        const widths = this.columnWidths(row);
162        let wrapped;
163        // word wrap all columns, and create
164        // a data-structure that is easy to rasterize.
165        row.forEach((col, c) => {
166            // leave room for left and right padding.
167            col.width = widths[c];
168            if (this.wrap) {
169                wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n');
170            }
171            else {
172                wrapped = col.text.split('\n');
173            }
174            if (col.border) {
175                wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.');
176                wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'");
177            }
178            // add top and bottom padding.
179            if (col.padding) {
180                wrapped.unshift(...new Array(col.padding[top] || 0).fill(''));
181                wrapped.push(...new Array(col.padding[bottom] || 0).fill(''));
182            }
183            wrapped.forEach((str, r) => {
184                if (!rrows[r]) {
185                    rrows.push([]);
186                }
187                const rrow = rrows[r];
188                for (let i = 0; i < c; i++) {
189                    if (rrow[i] === undefined) {
190                        rrow.push('');
191                    }
192                }
193                rrow.push(str);
194            });
195        });
196        return rrows;
197    }
198    negatePadding(col) {
199        let wrapWidth = col.width || 0;
200        if (col.padding) {
201            wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0);
202        }
203        if (col.border) {
204            wrapWidth -= 4;
205        }
206        return wrapWidth;
207    }
208    columnWidths(row) {
209        if (!this.wrap) {
210            return row.map(col => {
211                return col.width || mixin.stringWidth(col.text);
212            });
213        }
214        let unset = row.length;
215        let remainingWidth = this.width;
216        // column widths can be set in config.
217        const widths = row.map(col => {
218            if (col.width) {
219                unset--;
220                remainingWidth -= col.width;
221                return col.width;
222            }
223            return undefined;
224        });
225        // any unset widths should be calculated.
226        const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0;
227        return widths.map((w, i) => {
228            if (w === undefined) {
229                return Math.max(unsetWidth, _minWidth(row[i]));
230            }
231            return w;
232        });
233    }
234}
235function addBorder(col, ts, style) {
236    if (col.border) {
237        if (/[.']-+[.']/.test(ts)) {
238            return '';
239        }
240        if (ts.trim().length !== 0) {
241            return style;
242        }
243        return '  ';
244    }
245    return '';
246}
247// calculates the minimum width of
248// a column, based on padding preferences.
249function _minWidth(col) {
250    const padding = col.padding || [];
251    const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0);
252    if (col.border) {
253        return minWidth + 4;
254    }
255    return minWidth;
256}
257function getWindowWidth() {
258    /* istanbul ignore next: depends on terminal */
259    if (typeof process === 'object' && process.stdout && process.stdout.columns) {
260        return process.stdout.columns;
261    }
262    return 80;
263}
264function alignRight(str, width) {
265    str = str.trim();
266    const strWidth = mixin.stringWidth(str);
267    if (strWidth < width) {
268        return ' '.repeat(width - strWidth) + str;
269    }
270    return str;
271}
272function alignCenter(str, width) {
273    str = str.trim();
274    const strWidth = mixin.stringWidth(str);
275    /* istanbul ignore next */
276    if (strWidth >= width) {
277        return str;
278    }
279    return ' '.repeat((width - strWidth) >> 1) + str;
280}
281let mixin;
282function cliui(opts, _mixin) {
283    mixin = _mixin;
284    return new UI({
285        width: (opts === null || opts === void 0 ? void 0 : opts.width) || getWindowWidth(),
286        wrap: opts === null || opts === void 0 ? void 0 : opts.wrap
287    });
288}
289
290// Bootstrap cliui with CommonJS dependencies:
291const stringWidth = require('string-width');
292const stripAnsi = require('strip-ansi');
293const wrap = require('wrap-ansi');
294function ui(opts) {
295    return cliui(opts, {
296        stringWidth,
297        stripAnsi,
298        wrap
299    });
300}
301
302module.exports = ui;
303