@textui/core 0.3.0 → 0.5.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/dist/app/app.d.ts +19 -0
- package/dist/app/app.d.ts.map +1 -1
- package/dist/app/app.js +79 -3
- package/dist/jsx/intrinsics.d.ts +14 -1
- package/dist/jsx/intrinsics.d.ts.map +1 -1
- package/dist/render/buffer.d.ts +16 -1
- package/dist/render/buffer.d.ts.map +1 -1
- package/dist/render/buffer.js +27 -2
- package/dist/render/layout.d.ts +18 -0
- package/dist/render/layout.d.ts.map +1 -1
- package/dist/render/layout.js +16 -6
- package/dist/runtime/paint.d.ts.map +1 -1
- package/dist/runtime/paint.js +104 -17
- package/dist/types/app.d.ts +11 -0
- package/dist/types/app.d.ts.map +1 -1
- package/dist/util/text.d.ts +18 -1
- package/dist/util/text.d.ts.map +1 -1
- package/dist/util/text.js +55 -4
- package/package.json +1 -1
- package/src/app/app.ts +81 -3
- package/src/jsx/intrinsics.ts +14 -1
- package/src/render/buffer.ts +28 -2
- package/src/render/layout.ts +27 -6
- package/src/runtime/paint.ts +110 -18
- package/src/types/app.ts +11 -0
- package/src/util/text.ts +55 -4
package/src/util/text.ts
CHANGED
|
@@ -20,7 +20,7 @@ const segmenter =
|
|
|
20
20
|
* code, file names, labels, key hints. Every character is then one grapheme
|
|
21
21
|
* one cell wide, and none of the Unicode machinery below has anything to do.
|
|
22
22
|
*/
|
|
23
|
-
function isAscii(text: string): boolean {
|
|
23
|
+
export function isAscii(text: string): boolean {
|
|
24
24
|
for (let i = 0; i < text.length; i++) {
|
|
25
25
|
const c = text.charCodeAt(i);
|
|
26
26
|
if (c < 0x20 || c > 0x7e) return false;
|
|
@@ -152,14 +152,42 @@ export function graphemeWidth(cluster: string): number {
|
|
|
152
152
|
return 1;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Widths already worked out, for strings that are not plain ASCII.
|
|
157
|
+
*
|
|
158
|
+
* The same strings are measured over and over. A frame measures the tree, lays
|
|
159
|
+
* it out and paints it - which is three walks over the same text - and then
|
|
160
|
+
* the next frame does all three again for content that has not changed. Nearly
|
|
161
|
+
* every row in a terminal application carries one glyph among its ASCII, a box
|
|
162
|
+
* rule or a bullet or an icon, and a single non-ASCII character sends the whole
|
|
163
|
+
* string down the slow path: an array of one-character strings, and a width
|
|
164
|
+
* classification for each of them. That was a quarter of the frame in a long
|
|
165
|
+
* transcript, spent proving the same sentences were the same width they were a
|
|
166
|
+
* thirtieth of a second ago.
|
|
167
|
+
*
|
|
168
|
+
* Only the slow path is remembered. ASCII is answered by `.length` before the
|
|
169
|
+
* map is even consulted, which is cheaper than the lookup would be, so the
|
|
170
|
+
* cache holds only what it saves anything on.
|
|
171
|
+
*/
|
|
172
|
+
const widths = new Map<string, number>();
|
|
173
|
+
const WIDTH_CACHE = 8192;
|
|
174
|
+
|
|
155
175
|
/** Width of a string in terminal cells. */
|
|
156
176
|
export function stringWidth(text: string): number {
|
|
157
177
|
if (text === '') return 0;
|
|
158
178
|
// Fast path: pure ASCII printable.
|
|
159
179
|
if (isAscii(text)) return text.length;
|
|
160
180
|
|
|
181
|
+
const seen = widths.get(text);
|
|
182
|
+
if (seen !== undefined) return seen;
|
|
183
|
+
|
|
161
184
|
let w = 0;
|
|
162
185
|
for (const g of graphemes(text)) w += graphemeWidth(g);
|
|
186
|
+
// Emptied rather than evicted one at a time: a terminal's vocabulary of
|
|
187
|
+
// strings is small and slow-moving, so the cap is only ever reached by
|
|
188
|
+
// something streaming new text, and for that the whole map is stale.
|
|
189
|
+
if (widths.size >= WIDTH_CACHE) widths.clear();
|
|
190
|
+
widths.set(text, w);
|
|
163
191
|
return w;
|
|
164
192
|
}
|
|
165
193
|
|
|
@@ -414,10 +442,33 @@ export function stripAnsi(text: string): string {
|
|
|
414
442
|
return text.replace(ANSI_RE, '');
|
|
415
443
|
}
|
|
416
444
|
|
|
417
|
-
|
|
445
|
+
/*
|
|
446
|
+
* Test copies, without the `g` flag.
|
|
447
|
+
*
|
|
448
|
+
* `test` on a global regular expression advances its `lastIndex` and starts
|
|
449
|
+
* the next call from there, so the same string tested twice answers yes then
|
|
450
|
+
* no. Asking is a different question from replacing and gets its own pattern.
|
|
451
|
+
*/
|
|
452
|
+
// eslint-disable-next-line no-control-regex
|
|
453
|
+
const ANSI_TEST = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07\x1B]*(?:\x07|\x1B\\))/;
|
|
454
|
+
// eslint-disable-next-line no-control-regex
|
|
455
|
+
const CONTROL_TEST = /[\x00-\x08\x0B-\x1F\x7F]/;
|
|
456
|
+
// eslint-disable-next-line no-control-regex
|
|
457
|
+
const CONTROL_RE = /[\x00-\x08\x0B-\x1F\x7F]/g;
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Remove control characters that would corrupt the frame. Keeps newlines.
|
|
461
|
+
*
|
|
462
|
+
* Two regular expressions over every string on screen, every frame, and for
|
|
463
|
+
* almost all of them the answer is the string it was given. `test` before
|
|
464
|
+
* `replace` is one scan that stops at the first match instead of two that
|
|
465
|
+
* always run to the end and allocate a copy each - and it returns the original
|
|
466
|
+
* string, so the callers that follow it get a cache hit on the width they
|
|
467
|
+
* already worked out rather than an equal-but-different string.
|
|
468
|
+
*/
|
|
418
469
|
export function sanitize(text: string): string {
|
|
419
|
-
|
|
420
|
-
return
|
|
470
|
+
if (ANSI_TEST.test(text)) return stripAnsi(text).replace(CONTROL_RE, '');
|
|
471
|
+
return CONTROL_TEST.test(text) ? text.replace(CONTROL_RE, '') : text;
|
|
421
472
|
}
|
|
422
473
|
|
|
423
474
|
/** Repeat a grapheme to exactly `width` cells (wide chars land short). */
|