console-toolkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +34 -0
- package/README.md +92 -0
- package/package.json +59 -0
- package/src/alphanumeric/arrows.js +31 -0
- package/src/alphanumeric/fractions.js +82 -0
- package/src/alphanumeric/number-formatters.js +131 -0
- package/src/alphanumeric/roman.js +86 -0
- package/src/alphanumeric/unicode-cultural-numbers.js +68 -0
- package/src/alphanumeric/unicode-letters.js +63 -0
- package/src/alphanumeric/unicode-numbers.js +75 -0
- package/src/alphanumeric/utils.js +44 -0
- package/src/ansi/csi.js +67 -0
- package/src/ansi/index.js +20 -0
- package/src/ansi/sgr-constants.js +99 -0
- package/src/ansi/sgr-state.js +398 -0
- package/src/ansi/sgr.js +214 -0
- package/src/box.js +253 -0
- package/src/charts/bars/block-frac-grouped.js +6 -0
- package/src/charts/bars/block-frac.js +36 -0
- package/src/charts/bars/block-grouped.js +6 -0
- package/src/charts/bars/block.js +43 -0
- package/src/charts/bars/draw-grouped.js +39 -0
- package/src/charts/bars/draw-stacked.js +24 -0
- package/src/charts/bars/frac-grouped.js +33 -0
- package/src/charts/bars/plain-grouped.js +6 -0
- package/src/charts/bars/plain.js +63 -0
- package/src/charts/columns/block-frac-grouped.js +6 -0
- package/src/charts/columns/block-frac.js +30 -0
- package/src/charts/columns/block-grouped.js +6 -0
- package/src/charts/columns/block.js +37 -0
- package/src/charts/columns/draw-grouped.js +48 -0
- package/src/charts/columns/draw-stacked.js +31 -0
- package/src/charts/columns/frac-grouped.js +27 -0
- package/src/charts/columns/plain-grouped.js +6 -0
- package/src/charts/columns/plain.js +39 -0
- package/src/charts/themes/default.js +12 -0
- package/src/charts/themes/rainbow-reversed.js +5 -0
- package/src/charts/themes/rainbow.js +8 -0
- package/src/charts/utils.js +75 -0
- package/src/draw-block-frac.js +33 -0
- package/src/draw-block.js +55 -0
- package/src/meta.js +41 -0
- package/src/output/show.js +40 -0
- package/src/output/updater.js +82 -0
- package/src/output/writer.js +131 -0
- package/src/panel.js +748 -0
- package/src/plot/bitmap.js +108 -0
- package/src/plot/draw-line.js +26 -0
- package/src/plot/draw-rect.js +216 -0
- package/src/plot/index.js +24 -0
- package/src/plot/to-quads.js +32 -0
- package/src/spinner/index.js +8 -0
- package/src/spinner/spin.js +51 -0
- package/src/spinner/spinner.js +75 -0
- package/src/spinner/spinners.js +65 -0
- package/src/strings.js +72 -0
- package/src/style.js +620 -0
- package/src/symbols.js +131 -0
- package/src/table/draw-borders.js +87 -0
- package/src/table/index.js +7 -0
- package/src/table/table.js +330 -0
- package/src/themes/blocks/unicode-half.js +9 -0
- package/src/themes/blocks/unicode-thin.js +9 -0
- package/src/themes/lines/ascii-compact.js +11 -0
- package/src/themes/lines/ascii-dots.js +9 -0
- package/src/themes/lines/ascii-girder.js +9 -0
- package/src/themes/lines/ascii-github.js +11 -0
- package/src/themes/lines/ascii-reddit.js +11 -0
- package/src/themes/lines/ascii-rounded.js +11 -0
- package/src/themes/lines/ascii.js +11 -0
- package/src/themes/lines/unicode-bold.js +15 -0
- package/src/themes/lines/unicode-rounded.js +15 -0
- package/src/themes/lines/unicode.js +15 -0
- package/src/themes/utils.js +38 -0
- package/src/turtle/draw-line-art.js +46 -0
- package/src/turtle/draw-unicode.js +33 -0
- package/src/turtle/index.js +12 -0
- package/src/turtle/turtle.js +286 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import Box from '../box.js';
|
|
2
|
+
import {addAlias} from '../meta.js';
|
|
3
|
+
import {fullBlock} from '../symbols.js';
|
|
4
|
+
|
|
5
|
+
export class Bitmap {
|
|
6
|
+
constructor(width, height, blockWidth = 5, blockHeight = 5) {
|
|
7
|
+
// parameters check
|
|
8
|
+
width = Math.round(width);
|
|
9
|
+
if (isNaN(width) || width <= 0) throw new Error(`Width should be a positive integer instead of "${width}"`);
|
|
10
|
+
height = Math.round(height);
|
|
11
|
+
if (isNaN(width) || height <= 0) throw new Error(`Height should be a positive integer instead of "${height}"`);
|
|
12
|
+
blockWidth = Math.round(blockWidth);
|
|
13
|
+
if (isNaN(width) || blockWidth <= 0)
|
|
14
|
+
throw new Error(`Block width should be a positive integer instead of "${blockWidth}"`);
|
|
15
|
+
blockHeight = Math.round(blockHeight);
|
|
16
|
+
if (isNaN(width) || blockHeight <= 0)
|
|
17
|
+
throw new Error(`Block height should be a positive integer instead of "${blockHeight}"`);
|
|
18
|
+
if (isNaN(width) || blockWidth * blockHeight > 32)
|
|
19
|
+
throw new Error("Multiplication of 'blockWidth' and 'blockHeight' should be equal or less than 32");
|
|
20
|
+
|
|
21
|
+
this.width = width;
|
|
22
|
+
this.height = height;
|
|
23
|
+
this.blockWidth = blockWidth;
|
|
24
|
+
this.blockHeight = blockHeight;
|
|
25
|
+
|
|
26
|
+
// create a bitmap
|
|
27
|
+
this.lineSize = Math.floor(this.width / this.blockWidth);
|
|
28
|
+
if (this.width % this.blockWidth) ++this.lineSize;
|
|
29
|
+
this.lineCount = Math.floor(this.height / this.blockHeight);
|
|
30
|
+
if (this.height % this.blockHeight) ++this.lineCount;
|
|
31
|
+
this.bitmap = new Array(this.lineCount * this.lineSize).fill(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
verifyPos(x, y) {
|
|
35
|
+
if (x < 0 || x >= this.width) throw new Error(`X is out of bounds: "${x}`);
|
|
36
|
+
if (y < 0 || y >= this.height) throw new Error(`Y is out of bounds: "${y}`);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getWordIndex(x, y) {
|
|
41
|
+
const line = Math.floor(y / this.blockHeight),
|
|
42
|
+
linePos = Math.floor(x / this.blockWidth);
|
|
43
|
+
return line * this.lineSize + linePos;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getWordMask(x, y) {
|
|
47
|
+
const shiftX = x % this.blockWidth,
|
|
48
|
+
shiftY = y % this.blockHeight;
|
|
49
|
+
return 1 << (shiftX + this.blockWidth * shiftY);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getBit(x, y) {
|
|
53
|
+
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return 0;
|
|
54
|
+
const index = this.getWordIndex(x, y),
|
|
55
|
+
mask = this.getWordMask(x, y);
|
|
56
|
+
return this.bitmap[index] & mask;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setBit(x, y, value = 1) {
|
|
60
|
+
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return this;
|
|
61
|
+
const index = this.getWordIndex(x, y),
|
|
62
|
+
mask = this.getWordMask(x, y);
|
|
63
|
+
this.bitmap[index] =
|
|
64
|
+
value > 0 ? this.bitmap[index] | mask : value < 0 ? this.bitmap[index] ^ mask : this.bitmap[index] & ~mask;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
clear(value) {
|
|
69
|
+
this.bitmap.fill(value ? ~0 : 0);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// toBox(one = fullBlock, zero = ' ') {
|
|
74
|
+
// // this algorithm is trivial, and not very efficient
|
|
75
|
+
// const result = [];
|
|
76
|
+
// for (let i = 0; i < this.height; ++i) {
|
|
77
|
+
// let row = '';
|
|
78
|
+
// for (let j = 0; j < this.width; ++j) row += this.getBit(j, i) ? one : zero;
|
|
79
|
+
// result.push(row);
|
|
80
|
+
// }
|
|
81
|
+
// return result;
|
|
82
|
+
// }
|
|
83
|
+
|
|
84
|
+
toBox(one = fullBlock, zero = ' ') {
|
|
85
|
+
const result = [];
|
|
86
|
+
for (let k = 0, kBase = 0; k < this.lineCount; ++k, kBase += this.blockHeight) {
|
|
87
|
+
const iLimit = Math.min(this.blockHeight, this.height - kBase);
|
|
88
|
+
for (let i = 0; i < iLimit; ++i) {
|
|
89
|
+
let row = '';
|
|
90
|
+
for (let j = 0, jBase = 0; j < this.lineSize; ++j, jBase += this.blockWidth) {
|
|
91
|
+
const index = k * this.lineSize + j,
|
|
92
|
+
word = this.bitmap[index],
|
|
93
|
+
mLimit = Math.min(this.blockWidth, this.width - jBase);
|
|
94
|
+
let mask = 1 << (this.blockWidth * i);
|
|
95
|
+
for (let m = 0; m < mLimit; ++m, mask <<= 1) {
|
|
96
|
+
row += word & mask ? one : zero;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
result.push(row);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new Box(result, true);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
addAlias(Bitmap, 'set', 'setBit');
|
|
107
|
+
|
|
108
|
+
export default Bitmap;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Drawing lines using Bresenham's line algorithm
|
|
2
|
+
// The canonic version from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
|
3
|
+
|
|
4
|
+
export const drawLine = (bmp, x0, y0, x1, y1, value = 1) => {
|
|
5
|
+
const dx = Math.abs(x1 - x0),
|
|
6
|
+
sx = x0 < x1 ? 1 : -1,
|
|
7
|
+
dy = -Math.abs(y1 - y0),
|
|
8
|
+
sy = y0 < y1 ? 1 : -1;
|
|
9
|
+
for (let error = dx + dy; ; ) {
|
|
10
|
+
bmp.setBit(x0, y0, value);
|
|
11
|
+
if (x0 == x1 && y0 == y1) break;
|
|
12
|
+
const e2 = 2 * error;
|
|
13
|
+
if (e2 >= dy) {
|
|
14
|
+
if (x0 == x1) break;
|
|
15
|
+
error += dy;
|
|
16
|
+
x0 += sx;
|
|
17
|
+
}
|
|
18
|
+
if (e2 <= dx) {
|
|
19
|
+
if (y0 == y1) break;
|
|
20
|
+
error += dx;
|
|
21
|
+
y0 += sy;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default drawLine;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
|
|
2
|
+
if (x1 < x0) [x0, x1] = [x1, x0];
|
|
3
|
+
if (y1 < y0) [y0, y1] = [y1, y0];
|
|
4
|
+
|
|
5
|
+
let indexL = bmp.getWordIndex(x0, y0),
|
|
6
|
+
indexR = bmp.getWordIndex(x1, y0);
|
|
7
|
+
|
|
8
|
+
const mask0 = bmp.getWordMask(x0, 0),
|
|
9
|
+
mask1 = bmp.getWordMask(x1, 0),
|
|
10
|
+
rowMask = (1 << bmp.blockWidth) - 1,
|
|
11
|
+
firstRows = y0 % bmp.blockHeight,
|
|
12
|
+
firstRowShift = bmp.blockWidth * firstRows,
|
|
13
|
+
height = y1 - y0 + 1;
|
|
14
|
+
|
|
15
|
+
if (firstRows + (y1 - y0) < bmp.blockHeight) {
|
|
16
|
+
if (indexL === indexR) {
|
|
17
|
+
// our rows are in a word: calculate a block mask
|
|
18
|
+
const mask = ~(mask0 - 1) & (mask1 | (mask1 - 1));
|
|
19
|
+
|
|
20
|
+
// apply the mask to the last incomplete block
|
|
21
|
+
let fullMask = mask;
|
|
22
|
+
for (let i = 1; i < height; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
|
|
23
|
+
fullMask <<= firstRowShift;
|
|
24
|
+
|
|
25
|
+
bmp.bitmap[indexL] =
|
|
26
|
+
value > 0
|
|
27
|
+
? bmp.bitmap[indexL] | fullMask
|
|
28
|
+
: value < 0
|
|
29
|
+
? bmp.bitmap[indexL] ^ fullMask
|
|
30
|
+
: bmp.bitmap[indexL] & ~fullMask;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// our rows are all in one block vertically but over several words
|
|
35
|
+
// calculate a left mask and a right masks
|
|
36
|
+
const maskL = ~(mask0 - 1) & rowMask,
|
|
37
|
+
maskR = mask1 | (mask1 - 1);
|
|
38
|
+
|
|
39
|
+
// calculate masks
|
|
40
|
+
let fullMaskL = maskL,
|
|
41
|
+
fullMaskR = maskR,
|
|
42
|
+
fullMaskM = ((1 << (bmp.blockWidth * height)) - 1) << firstRowShift;
|
|
43
|
+
for (let i = 1; i < height; ++i) {
|
|
44
|
+
fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
|
|
45
|
+
fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
|
|
46
|
+
}
|
|
47
|
+
fullMaskL <<= firstRowShift;
|
|
48
|
+
fullMaskR <<= firstRowShift;
|
|
49
|
+
|
|
50
|
+
// apply masks
|
|
51
|
+
bmp.bitmap[indexL] =
|
|
52
|
+
value > 0
|
|
53
|
+
? bmp.bitmap[indexL] | fullMaskL
|
|
54
|
+
: value < 0
|
|
55
|
+
? bmp.bitmap[indexL] ^ fullMaskL
|
|
56
|
+
: bmp.bitmap[indexL] & ~fullMaskL;
|
|
57
|
+
for (let index = indexL + 1; index < indexR; ++index)
|
|
58
|
+
bmp.bitmap[index] =
|
|
59
|
+
value > 0
|
|
60
|
+
? bmp.bitmap[index] | fullMaskM
|
|
61
|
+
: value < 0
|
|
62
|
+
? bmp.bitmap[index] ^ fullMaskM
|
|
63
|
+
: bmp.bitmap[index] & ~fullMaskM;
|
|
64
|
+
bmp.bitmap[indexR] =
|
|
65
|
+
value > 0
|
|
66
|
+
? bmp.bitmap[indexR] | fullMaskR
|
|
67
|
+
: value < 0
|
|
68
|
+
? bmp.bitmap[indexR] ^ fullMaskR
|
|
69
|
+
: bmp.bitmap[indexR] & ~fullMaskR;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rowsOverFullBlock = height - (firstRows ? bmp.blockHeight - firstRows : 0),
|
|
74
|
+
fullLines = Math.floor(rowsOverFullBlock / bmp.blockHeight),
|
|
75
|
+
lastRows = rowsOverFullBlock % bmp.blockHeight;
|
|
76
|
+
|
|
77
|
+
if (indexL === indexR) {
|
|
78
|
+
// our row is confined in a word: calculate a row mask
|
|
79
|
+
const mask = ~(mask0 - 1) & (mask1 | (mask1 - 1));
|
|
80
|
+
if (firstRows) {
|
|
81
|
+
// apply the mask to the first incomplete block
|
|
82
|
+
let fullMask = mask;
|
|
83
|
+
for (let i = 1, n = bmp.blockHeight - firstRows; i < n; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
|
|
84
|
+
fullMask <<= firstRowShift;
|
|
85
|
+
bmp.bitmap[indexL] =
|
|
86
|
+
value > 0
|
|
87
|
+
? bmp.bitmap[indexL] | fullMask
|
|
88
|
+
: value < 0
|
|
89
|
+
? bmp.bitmap[indexL] ^ fullMask
|
|
90
|
+
: bmp.bitmap[indexL] & ~fullMask;
|
|
91
|
+
indexL += bmp.lineSize;
|
|
92
|
+
}
|
|
93
|
+
if (fullLines) {
|
|
94
|
+
// calculate a mask for full blocks
|
|
95
|
+
let fullMask = mask;
|
|
96
|
+
for (let i = 1; i < bmp.blockHeight; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
|
|
97
|
+
// apply the mask to full blocks
|
|
98
|
+
for (let i = 0; i < fullLines; ++i, indexL += bmp.lineSize)
|
|
99
|
+
bmp.bitmap[indexL] =
|
|
100
|
+
value > 0
|
|
101
|
+
? bmp.bitmap[indexL] | fullMask
|
|
102
|
+
: value < 0
|
|
103
|
+
? bmp.bitmap[indexL] ^ fullMask
|
|
104
|
+
: bmp.bitmap[indexL] & ~fullMask;
|
|
105
|
+
}
|
|
106
|
+
if (lastRows) {
|
|
107
|
+
// apply the mask to the last incomplete block
|
|
108
|
+
let fullMask = mask;
|
|
109
|
+
for (let i = 1; i < lastRows; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
|
|
110
|
+
bmp.bitmap[indexL] =
|
|
111
|
+
value > 0
|
|
112
|
+
? bmp.bitmap[indexL] | fullMask
|
|
113
|
+
: value < 0
|
|
114
|
+
? bmp.bitmap[indexL] ^ fullMask
|
|
115
|
+
: bmp.bitmap[indexL] & ~fullMask;
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// calculate a left mask and a right masks
|
|
121
|
+
const maskL = ~(mask0 - 1) & rowMask,
|
|
122
|
+
maskR = mask1 | (mask1 - 1);
|
|
123
|
+
|
|
124
|
+
if (firstRows) {
|
|
125
|
+
// apply the mask to the first incomplete block
|
|
126
|
+
let fullMaskL = maskL,
|
|
127
|
+
fullMaskR = maskR,
|
|
128
|
+
fullMaskM = ((1 << (bmp.blockWidth * (bmp.blockHeight - firstRows))) - 1) << firstRowShift;
|
|
129
|
+
for (let i = 1, n = bmp.blockHeight - firstRows; i < n; ++i) {
|
|
130
|
+
fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
|
|
131
|
+
fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
|
|
132
|
+
}
|
|
133
|
+
fullMaskL <<= firstRowShift;
|
|
134
|
+
fullMaskR <<= firstRowShift;
|
|
135
|
+
bmp.bitmap[indexL] =
|
|
136
|
+
value > 0
|
|
137
|
+
? bmp.bitmap[indexL] | fullMaskL
|
|
138
|
+
: value < 0
|
|
139
|
+
? bmp.bitmap[indexL] ^ fullMaskL
|
|
140
|
+
: bmp.bitmap[indexL] & ~fullMaskL;
|
|
141
|
+
for (let index = indexL + 1; index < indexR; ++index)
|
|
142
|
+
bmp.bitmap[index] =
|
|
143
|
+
value > 0
|
|
144
|
+
? bmp.bitmap[index] | fullMaskM
|
|
145
|
+
: value < 0
|
|
146
|
+
? bmp.bitmap[index] ^ fullMaskM
|
|
147
|
+
: bmp.bitmap[index] & ~fullMaskM;
|
|
148
|
+
bmp.bitmap[indexR] =
|
|
149
|
+
value > 0
|
|
150
|
+
? bmp.bitmap[indexR] | fullMaskR
|
|
151
|
+
: value < 0
|
|
152
|
+
? bmp.bitmap[indexR] ^ fullMaskR
|
|
153
|
+
: bmp.bitmap[indexR] & ~fullMaskR;
|
|
154
|
+
indexL += bmp.lineSize;
|
|
155
|
+
indexR += bmp.lineSize;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (fullLines) {
|
|
159
|
+
// calculate masks
|
|
160
|
+
let fullMaskL = maskL,
|
|
161
|
+
fullMaskR = maskR;
|
|
162
|
+
for (let i = 1; i < bmp.blockHeight; ++i) {
|
|
163
|
+
fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
|
|
164
|
+
fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
|
|
165
|
+
}
|
|
166
|
+
// apply masks
|
|
167
|
+
for (let i = 0; i < fullLines; ++i, indexL += bmp.lineSize, indexR += bmp.lineSize) {
|
|
168
|
+
bmp.bitmap[indexL] =
|
|
169
|
+
value > 0
|
|
170
|
+
? bmp.bitmap[indexL] | fullMaskL
|
|
171
|
+
: value < 0
|
|
172
|
+
? bmp.bitmap[indexL] ^ fullMaskL
|
|
173
|
+
: bmp.bitmap[indexL] & ~fullMaskL;
|
|
174
|
+
for (let index = indexL + 1; index < indexR; ++index)
|
|
175
|
+
bmp.bitmap[index] = value > 0 ? ~0 : value < 0 ? ~bmp.bitmap[index] : 0;
|
|
176
|
+
bmp.bitmap[indexR] =
|
|
177
|
+
value > 0
|
|
178
|
+
? bmp.bitmap[indexR] | fullMaskR
|
|
179
|
+
: value < 0
|
|
180
|
+
? bmp.bitmap[indexR] ^ fullMaskR
|
|
181
|
+
: bmp.bitmap[indexR] & ~fullMaskR;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (lastRows) {
|
|
186
|
+
// apply the mask to an incomplete block
|
|
187
|
+
let fullMaskL = maskL,
|
|
188
|
+
fullMaskR = maskR,
|
|
189
|
+
fullMaskM = (1 << (bmp.blockWidth * lastRows)) - 1;
|
|
190
|
+
for (let i = 1; i < lastRows; ++i) {
|
|
191
|
+
fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
|
|
192
|
+
fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
|
|
193
|
+
}
|
|
194
|
+
bmp.bitmap[indexL] =
|
|
195
|
+
value > 0
|
|
196
|
+
? bmp.bitmap[indexL] | fullMaskL
|
|
197
|
+
: value < 0
|
|
198
|
+
? bmp.bitmap[indexL] ^ fullMaskL
|
|
199
|
+
: bmp.bitmap[indexL] & ~fullMaskL;
|
|
200
|
+
for (let index = indexL + 1; index < indexR; ++index)
|
|
201
|
+
bmp.bitmap[index] =
|
|
202
|
+
value > 0
|
|
203
|
+
? bmp.bitmap[index] | fullMaskM
|
|
204
|
+
: value < 0
|
|
205
|
+
? bmp.bitmap[index] ^ fullMaskM
|
|
206
|
+
: bmp.bitmap[index] & ~fullMaskM;
|
|
207
|
+
bmp.bitmap[indexR] =
|
|
208
|
+
value > 0
|
|
209
|
+
? bmp.bitmap[indexR] | fullMaskR
|
|
210
|
+
: value < 0
|
|
211
|
+
? bmp.bitmap[indexR] ^ fullMaskR
|
|
212
|
+
: bmp.bitmap[indexR] & ~fullMaskR;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export default drawRect;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Bitmap from './bitmap.js';
|
|
2
|
+
import drawLine from './draw-line.js';
|
|
3
|
+
import drawRect from './draw-rect.js';
|
|
4
|
+
import toQuads from './to-quads.js';
|
|
5
|
+
|
|
6
|
+
// patch Bitmap
|
|
7
|
+
|
|
8
|
+
Bitmap.prototype.line = function (...args) {
|
|
9
|
+
drawLine(this, ...args);
|
|
10
|
+
return this;
|
|
11
|
+
};
|
|
12
|
+
Bitmap.prototype.rect = function (...args) {
|
|
13
|
+
drawRect(this, ...args);
|
|
14
|
+
return this;
|
|
15
|
+
};
|
|
16
|
+
Bitmap.prototype.toQuads = function () {
|
|
17
|
+
return toQuads(this);
|
|
18
|
+
};
|
|
19
|
+
Bitmap.prototype.toStrings = function () {
|
|
20
|
+
return this.toBox().toStrings();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export {Bitmap, drawLine, drawRect, toQuads};
|
|
24
|
+
export default Bitmap;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Box from '../box.js';
|
|
2
|
+
import {quadrants} from '../symbols.js';
|
|
3
|
+
|
|
4
|
+
export const toQuads = bmp => {
|
|
5
|
+
const result = [],
|
|
6
|
+
rowSize = Math.floor((bmp.width + 1) / 2),
|
|
7
|
+
accumulator = new Array(rowSize).fill(0);
|
|
8
|
+
for (let k = 0, kBase = 0; k < bmp.lineCount; ++k, kBase += bmp.blockHeight) {
|
|
9
|
+
const iLimit = Math.min(bmp.blockHeight, bmp.height - kBase);
|
|
10
|
+
for (let i = 0; i < iLimit; ++i) {
|
|
11
|
+
for (let j = 0, jBase = 0; j < bmp.lineSize; ++j, jBase += bmp.blockWidth) {
|
|
12
|
+
const index = k * bmp.lineSize + j,
|
|
13
|
+
word = bmp.bitmap[index],
|
|
14
|
+
mLimit = Math.min(bmp.blockWidth, bmp.width - jBase);
|
|
15
|
+
let mask = 1 << (bmp.blockWidth * i);
|
|
16
|
+
for (let m = 0; m < mLimit; ++m, mask <<= 1) {
|
|
17
|
+
accumulator[(jBase + m) >> 1] += word & mask ? ((kBase + i) & 1 ? 4 : 1) << ((jBase + m) & 1) : 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if ((kBase + i) & 1) {
|
|
21
|
+
result.push(accumulator.map(i => quadrants[i]).join(''));
|
|
22
|
+
if (kBase + i + 1 < bmp.height) accumulator.fill(0);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (bmp.height & 1) {
|
|
27
|
+
result.push(accumulator.map(i => quadrants[i]).join(''));
|
|
28
|
+
}
|
|
29
|
+
return new Box(result, true);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default toQuads;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import {SpinnerBase} from './spinner.js';
|
|
4
|
+
|
|
5
|
+
class Spinner extends SpinnerBase {
|
|
6
|
+
constructor(strings, args) {
|
|
7
|
+
super(false);
|
|
8
|
+
this.strings = strings;
|
|
9
|
+
this.args = args;
|
|
10
|
+
this.indices = new WeakMap();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getFrame() {
|
|
14
|
+
let result = '';
|
|
15
|
+
for (let i = 0; i < this.args.length; ++i) {
|
|
16
|
+
const arg = this.args[i];
|
|
17
|
+
|
|
18
|
+
result += this.strings[i];
|
|
19
|
+
|
|
20
|
+
if (typeof arg == 'function') {
|
|
21
|
+
result += String(arg(this.state));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (arg instanceof SpinnerBase) {
|
|
26
|
+
arg.active = this.active;
|
|
27
|
+
arg.paused = this.paused;
|
|
28
|
+
arg.finished = this.finished;
|
|
29
|
+
result += arg.getFrame();
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Array.isArray(arg)) {
|
|
34
|
+
if (!this.indices.has(arg)) this.indices.set(arg, 0);
|
|
35
|
+
const index = this.indices.get(arg);
|
|
36
|
+
if (!this.finished && !this.paused && this.active) this.indices.set(arg, (index + 1) % arg.length);
|
|
37
|
+
result += String(arg[index]);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
result += String(arg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
result += this.strings[this.args.length];
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const spin = (strings, ...args) => new Spinner(strings, args);
|
|
50
|
+
|
|
51
|
+
export default spin;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import {checkMarkHeavy} from '../symbols.js';
|
|
4
|
+
|
|
5
|
+
export class SpinnerBase {
|
|
6
|
+
constructor(isStarted) {
|
|
7
|
+
this.active = isStarted;
|
|
8
|
+
this.paused = false;
|
|
9
|
+
this.finished = false;
|
|
10
|
+
this.frameIndex = 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get isStarted() {
|
|
14
|
+
return this.active || this.finished;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get isActive() {
|
|
18
|
+
return this.active;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get isFinished() {
|
|
22
|
+
return this.finished;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get state() {
|
|
26
|
+
if (this.finished) return 'finished';
|
|
27
|
+
if (this.active) return this.paused ? 'paused' : 'active';
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set state(value) {
|
|
32
|
+
this.finished = this.active = this.paused = false;
|
|
33
|
+
switch (value) {
|
|
34
|
+
case 'finished':
|
|
35
|
+
this.finished = true;
|
|
36
|
+
break;
|
|
37
|
+
case 'paused':
|
|
38
|
+
this.active = this.paused = true;
|
|
39
|
+
break;
|
|
40
|
+
case 'active':
|
|
41
|
+
this.active = true;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
reset() {
|
|
47
|
+
this.active = this.paused = this.finished = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
nextFrameIndex(length) {
|
|
51
|
+
return (this.frameIndex = (this.frameIndex + 1) % length);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getFrame() {
|
|
55
|
+
return 'X';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const defaultSpinnerDefinition = {notStarted: [' '], finished: [checkMarkHeavy], frames: [...'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏']};
|
|
60
|
+
|
|
61
|
+
export class Spinner extends SpinnerBase {
|
|
62
|
+
constructor(spinnerDefinition, isStarted) {
|
|
63
|
+
super(isStarted);
|
|
64
|
+
this.spinner = {...defaultSpinnerDefinition, ...spinnerDefinition};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getFrame() {
|
|
68
|
+
if (this.finished) return this.spinner.finished[this.nextFrameIndex(this.spinner.finished.length)];
|
|
69
|
+
if (!this.active) return this.spinner.notStarted[this.nextFrameIndex(this.spinner.notStarted.length)];
|
|
70
|
+
if (!this.paused) this.nextFrameIndex(this.spinner.frames.length);
|
|
71
|
+
return this.spinner.frames[this.frameIndex];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default Spinner;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Spinners are from https://github.com/sindresorhus/cli-spinners under the MIT license.
|
|
4
|
+
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
5
|
+
// See `cli-spinners` for more great options.
|
|
6
|
+
|
|
7
|
+
export const dots = {frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']};
|
|
8
|
+
export const sand = {frames: [...'⠁⠂⠄⡀⡈⡐⡠⣀⣁⣂⣄⣌⣔⣤⣥⣦⣮⣶⣷⣿⡿⠿⢟⠟⡛⠛⠫⢋⠋⠍⡉⠉⠑⠡⢁']};
|
|
9
|
+
export const line = {frames: ['-', '\\', '|', '/']};
|
|
10
|
+
export const pipe = {frame: ['┤', '┘', '┴', '└', '├', '┌', '┬', '┐']};
|
|
11
|
+
export const growVertical = {frame: ['▁', '▃', '▄', '▅', '▆', '▇', '▆', '▅', '▄', '▃']};
|
|
12
|
+
export const growHorizontal = {frame: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '▊', '▋', '▌', '▍', '▎']};
|
|
13
|
+
export const noise = {frame: ['▓', '▒', '░']};
|
|
14
|
+
export const bounce = {frame: ['⠁', '⠂', '⠄', '⠂']};
|
|
15
|
+
export const arc = {frame: ['◜', '◠', '◝', '◞', '◡', '◟']};
|
|
16
|
+
export const squareQuarters = {frames: ['◰', '◳', '◲', '◱']};
|
|
17
|
+
export const circleQuarters = {frames: ['◴', '◷', '◶', '◵']};
|
|
18
|
+
export const circleHalves = {frames: ['◐', '◓', '◑', '◒']};
|
|
19
|
+
export const arrows = {frame: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙']};
|
|
20
|
+
|
|
21
|
+
export const clock = {
|
|
22
|
+
frames: ['🕛 ', '🕐 ', '🕑 ', '🕒 ', '🕓 ', '🕔 ', '🕕 ', '🕖 ', '🕗 ', '🕘 ', '🕙 ', '🕚 '],
|
|
23
|
+
notStarted: [' '],
|
|
24
|
+
finished: ['✔ ']
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const bouncingBar = {
|
|
28
|
+
frames: [
|
|
29
|
+
'[ ]',
|
|
30
|
+
'[= ]',
|
|
31
|
+
'[== ]',
|
|
32
|
+
'[=== ]',
|
|
33
|
+
'[====]',
|
|
34
|
+
'[ ===]',
|
|
35
|
+
'[ ==]',
|
|
36
|
+
'[ =]',
|
|
37
|
+
'[ ]',
|
|
38
|
+
'[ =]',
|
|
39
|
+
'[ ==]',
|
|
40
|
+
'[ ===]',
|
|
41
|
+
'[====]',
|
|
42
|
+
'[=== ]',
|
|
43
|
+
'[== ]',
|
|
44
|
+
'[= ]'
|
|
45
|
+
],
|
|
46
|
+
notStarted: ['[ ]'],
|
|
47
|
+
finished: ['[####]']
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const bouncingBall = {
|
|
51
|
+
frames: [
|
|
52
|
+
'( ● )',
|
|
53
|
+
'( ● )',
|
|
54
|
+
'( ● )',
|
|
55
|
+
'( ● )',
|
|
56
|
+
'( ●)',
|
|
57
|
+
'( ● )',
|
|
58
|
+
'( ● )',
|
|
59
|
+
'( ● )',
|
|
60
|
+
'( ● )',
|
|
61
|
+
'(● )'
|
|
62
|
+
],
|
|
63
|
+
notStarted: ['( )'],
|
|
64
|
+
finished: ['(●●●●●●)']
|
|
65
|
+
};
|
package/src/strings.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const matchCsiNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g;
|
|
2
|
+
export const matchCsiNoSgrNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x6C\x6E-\x7E]/g;
|
|
3
|
+
|
|
4
|
+
export const getLength = (s, matcher = matchCsiNoGroups) => {
|
|
5
|
+
s = String(s);
|
|
6
|
+
let counter = 0,
|
|
7
|
+
start = 0;
|
|
8
|
+
matcher.lastIndex = 0;
|
|
9
|
+
for (const match of s.matchAll(matcher)) {
|
|
10
|
+
counter += [...s.substring(start, match.index)].length;
|
|
11
|
+
start = match.index + match[0].length;
|
|
12
|
+
}
|
|
13
|
+
counter += [...s.substring(start)].length;
|
|
14
|
+
return counter;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const getMaxLength = (strings, matcher = matchCsiNoGroups) =>
|
|
18
|
+
Math.max(0, ...strings.map(s => getLength(s, matcher)));
|
|
19
|
+
|
|
20
|
+
export const clip = (s, width, includeLastCommand, matcher = matchCsiNoGroups) => {
|
|
21
|
+
s = String(s);
|
|
22
|
+
let counter = 0,
|
|
23
|
+
start = 0;
|
|
24
|
+
matcher.lastIndex = 0;
|
|
25
|
+
for (const match of s.matchAll(matcher)) {
|
|
26
|
+
if (counter >= width) return s.substring(0, match.index + (includeLastCommand ? match[0].length : 0));
|
|
27
|
+
const prev = [...s.substring(start, match.index)];
|
|
28
|
+
counter += prev.length;
|
|
29
|
+
if (includeLastCommand ? counter > width : counter >= width) {
|
|
30
|
+
const diff = width - counter,
|
|
31
|
+
end = start + (diff ? prev.slice(0, prev.length + diff).join('').length : prev.length);
|
|
32
|
+
return s.substring(0, end);
|
|
33
|
+
}
|
|
34
|
+
start = match.index + match[0].length;
|
|
35
|
+
}
|
|
36
|
+
if (counter >= width) return s.substring(0, start);
|
|
37
|
+
const prev = [...s.substring(start)];
|
|
38
|
+
if (counter + prev.length > width) {
|
|
39
|
+
const end = start + prev.slice(0, width - counter).join('').length;
|
|
40
|
+
return s.substring(0, end);
|
|
41
|
+
}
|
|
42
|
+
return s; // unchanged
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const clipStrings = (strings, width, includeLastCommand, matcher = matchCsiNoGroups) =>
|
|
46
|
+
strings.map(s => clip(s, width, includeLastCommand, matcher));
|
|
47
|
+
|
|
48
|
+
export const toStrings = s => {
|
|
49
|
+
main: for (;;) {
|
|
50
|
+
switch (typeof s) {
|
|
51
|
+
case 'function':
|
|
52
|
+
for (let i = 0; i < 10 && typeof s == 'function'; ++i) s = s();
|
|
53
|
+
if (typeof s == 'function') s = String(s);
|
|
54
|
+
continue main;
|
|
55
|
+
case 'number':
|
|
56
|
+
case 'boolean':
|
|
57
|
+
return [String(s)];
|
|
58
|
+
case 'string':
|
|
59
|
+
return String(s).split(/\r?\n/g);
|
|
60
|
+
case 'object':
|
|
61
|
+
if (Array.isArray(s)) return [...s];
|
|
62
|
+
if (!s) break main;
|
|
63
|
+
if (typeof s.toStrings == 'function') return s.toStrings();
|
|
64
|
+
if (typeof s.toBox == 'function') return s.toBox().box;
|
|
65
|
+
if (typeof s.toPanel == 'function') return s.toPanel().toStrings();
|
|
66
|
+
s = String(s);
|
|
67
|
+
continue main;
|
|
68
|
+
}
|
|
69
|
+
break main;
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
};
|