@promptctl/rich-js 0.19.0 → 0.21.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/README.md +3 -2
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +10 -0
- package/dist/core/color.d.ts +1 -0
- package/dist/core/color.js +14 -3
- package/dist/core/noise.d.ts +31 -0
- package/dist/core/noise.js +94 -0
- package/dist/core/paint.js +13 -2
- package/dist/core/pretty.d.ts +23 -2
- package/dist/core/pretty.js +54 -18
- package/dist/core/protocol.d.ts +2 -0
- package/dist/core/protocol.js +3 -2
- package/dist/core/selection.d.ts +12 -0
- package/dist/core/selection.js +14 -0
- package/dist/core/strip.d.ts +159 -76
- package/dist/core/strip.js +367 -215
- package/dist/core/style.js +10 -7
- package/dist/host/app.d.ts +68 -2
- package/dist/host/app.js +125 -15
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +87 -0
- package/dist/host/input.js +517 -0
- package/dist/host/terminal-host.d.ts +12 -0
- package/dist/host/terminal-host.js +2 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +7 -4
- package/dist/node/terminal-host.d.ts +7 -0
- package/dist/node/terminal-host.js +7 -0
- package/dist/renderables/effect.d.ts +89 -16
- package/dist/renderables/effect.js +115 -42
- package/dist/renderables/effects.d.ts +260 -0
- package/dist/renderables/effects.js +596 -0
- package/dist/renderables/embed.d.ts +11 -5
- package/dist/renderables/embed.js +13 -6
- package/dist/renderables/flexStrip.d.ts +1 -1
- package/dist/renderables/flexStrip.js +10 -9
- package/dist/renderables/list-view.d.ts +42 -0
- package/dist/renderables/list-view.js +78 -0
- package/dist/renderables/progress.d.ts +6 -0
- package/dist/renderables/progress.js +11 -4
- package/dist/renderables/progressBar.d.ts +22 -2
- package/dist/renderables/progressBar.js +52 -7
- package/dist/renderables/rule.d.ts +2 -2
- package/dist/renderables/rule.js +12 -10
- package/dist/renderables/table.js +5 -7
- package/dist/renderables/viewport.d.ts +30 -0
- package/dist/renderables/viewport.js +59 -3
- package/dist/widgets/dropdown.js +3 -3
- package/dist/widgets/event-router.d.ts +31 -40
- package/dist/widgets/event-router.js +52 -483
- package/dist/widgets/hit.js +2 -4
- package/dist/widgets/index.d.ts +2 -1
- package/dist/widgets/types.d.ts +8 -25
- package/dist/widgets/types.js +8 -0
- package/dist/widgets/widget-app.d.ts +22 -7
- package/dist/widgets/widget-app.js +22 -12
- package/package.json +26 -18
package/README.md
CHANGED
|
@@ -387,9 +387,10 @@ npm run demo
|
|
|
387
387
|
| `npm run template-bindings` | rich-template-bindings | Type a template on the left and watch it render on the right. |
|
|
388
388
|
| `npm run themes-and-color-studio` | themes-and-color-studio | A printed tour of colours, palettes, bundled themes, and contrast. Prints once and exits; set any of `EXPORT_HTML=out.html`, `EXPORT_SVG=out.svg` and `EXPORT_TEXT=out.txt` to also save it to each file named. |
|
|
389
389
|
| `npm run strip` | rich-strip | Every built-in `Joiner`, printed side by side. Prints once and exits. |
|
|
390
|
-
| `npm run effects-feel` | effects-feel | Shimmer, pulse
|
|
390
|
+
| `npm run effects-feel` | effects-feel | Shimmer, a gentle pulse on chosen elements, sparkle, a hue wheel, fade-in and a dissolve-out that eddies back before it goes, on a powerline strip and a run of text, at a colour depth you choose, with keys to step the frame rate, the demo's pace and the theme while it runs. A demo for agreeing how the effects feel; `npm run effects-feel -- --help` lists the flags and keys. |
|
|
391
391
|
| `npm run markup-plugins` | rich-markup-plugins | Custom markup tags registered through `MarkupRegistry`. Prints once and exits. |
|
|
392
|
-
| `npm run viewport` | rich-viewport | A
|
|
392
|
+
| `npm run viewport` | rich-viewport | A selection you move through a list taller than its window with the arrow keys, `j`/`k`, PgUp/PgDn and Home/End, kept in view by `Viewport.ensureVisible`; the mouse wheel scrolls the window, and `q` quits. |
|
|
393
|
+
| `npm run terminal-host` | terminal-host | Each key you press printed as the bytes the terminal sends for it, and each resize as the new size — a program written against `TerminalHost` alone. `examples/terminal-host/embed.ts` runs the same program on an xterm.js terminal in your own page. |
|
|
393
394
|
|
|
394
395
|
`Ctrl-C` quits any demo that stays running.
|
|
395
396
|
|
package/dist/core/anchor.d.ts
CHANGED
|
@@ -43,3 +43,8 @@ export interface Anchor {
|
|
|
43
43
|
* every level of the nesting — each level names the same cell.
|
|
44
44
|
*/
|
|
45
45
|
export declare function shiftAnchor(anchor: Anchor, cells: number): Anchor;
|
|
46
|
+
/**
|
|
47
|
+
* Whether `owner` drew the cell `anchor` names, itself or through an owner
|
|
48
|
+
* nested in its output.
|
|
49
|
+
*/
|
|
50
|
+
export declare function drawnBy(anchor: Anchor | undefined, owner: object): boolean;
|
package/dist/core/anchor.js
CHANGED
|
@@ -37,3 +37,13 @@ export function shiftAnchor(anchor, cells) {
|
|
|
37
37
|
inner: anchor.inner && shiftAnchor(anchor.inner, cells),
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether `owner` drew the cell `anchor` names, itself or through an owner
|
|
42
|
+
* nested in its output.
|
|
43
|
+
*/
|
|
44
|
+
export function drawnBy(anchor, owner) {
|
|
45
|
+
for (let level = anchor; level; level = level.inner)
|
|
46
|
+
if (level.owner === owner)
|
|
47
|
+
return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
package/dist/core/color.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export declare function relativeLuminance(c: ColorRgba): number;
|
|
|
58
58
|
* foreground, flatten it first (or use `ensureContrast`, which does).
|
|
59
59
|
*/
|
|
60
60
|
export declare function contrastRatio(a: ColorRgba, b: ColorRgba): number;
|
|
61
|
+
export declare function rgbDistance(a: ColorRgba, b: ColorRgba): number;
|
|
61
62
|
/**
|
|
62
63
|
* An indexed palette: entry `i` of `colors` is terminal index `firstIndex + i`.
|
|
63
64
|
* `firstIndex` lets a table hold only the part of a palette a downgrade may
|
package/dist/core/color.js
CHANGED
|
@@ -113,8 +113,9 @@ function luminanceRatio(la, lb) {
|
|
|
113
113
|
}
|
|
114
114
|
// --- ColorTable ---
|
|
115
115
|
// [LAW:one-source-of-truth] The one distance every table scan ranks by, so
|
|
116
|
-
// `matchWhere`'s "nearest by the distance `match` uses" cannot drift
|
|
117
|
-
|
|
116
|
+
// `matchWhere`'s "nearest by the distance `match` uses" cannot drift, and
|
|
117
|
+
// `Effected` weighs a written colour against `ColorSpec.matchOn` by it too.
|
|
118
|
+
export function rgbDistance(a, b) {
|
|
118
119
|
const dr = a.red - b.red;
|
|
119
120
|
const dg = a.green - b.green;
|
|
120
121
|
const db = a.blue - b.blue;
|
|
@@ -580,7 +581,17 @@ export class ColorSpec {
|
|
|
580
581
|
throw new ColorParseError(`A blend's fraction is in [0, 1]; got ${t}`);
|
|
581
582
|
const a = from.fixedValue;
|
|
582
583
|
const b = to.fixedValue;
|
|
583
|
-
|
|
584
|
+
if (a !== undefined && b !== undefined)
|
|
585
|
+
return ColorSpec.fromRgba(blendRgb(a, b, t));
|
|
586
|
+
// A blend that draws as one of its ends is that end — not a blend whose
|
|
587
|
+
// name no comparison will ever match back to it: at t 0 or 1, or between
|
|
588
|
+
// one slot of one palette spelled twice.
|
|
589
|
+
if (t === 0)
|
|
590
|
+
return from;
|
|
591
|
+
if (t === 1)
|
|
592
|
+
return to;
|
|
593
|
+
const oneSlot = from.type === to.type && (from.number !== undefined ? from.number === to.number : from.name === to.name);
|
|
594
|
+
return oneSlot ? from : new ColorBlend(from, to, t);
|
|
584
595
|
}
|
|
585
596
|
/**
|
|
586
597
|
* The spec that draws `value` nearest to it at `depth` on a terminal known
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gradient noise, the texture every effect in `renderables/effects` is drawn
|
|
3
|
+
* from. Ken Perlin's improved noise (2002): a smooth pseudo-random
|
|
4
|
+
* field over space and time, continuous in its first and second derivatives,
|
|
5
|
+
* so a cell sampled a moment later has moved a little, never jumped. Things
|
|
6
|
+
* the eye knows as alive — water, wind, breath, a glow — vary like this:
|
|
7
|
+
* coherent nearby, uncorrelated far apart, never repeating exactly.
|
|
8
|
+
*
|
|
9
|
+
* [LAW:no-ambient-temporal-coupling] A pure function of its coordinates; time
|
|
10
|
+
* is one of them. The permutation is fixed, so a moment draws the same frame
|
|
11
|
+
* on every run.
|
|
12
|
+
*
|
|
13
|
+
* Tier 0 of `src/core/`: imports nothing. Not part of the package's surface;
|
|
14
|
+
* the effects playground reaches it as source.
|
|
15
|
+
*/
|
|
16
|
+
/** Noise at (x, y, z), in about [-1, 1]; zero at every integer lattice point. */
|
|
17
|
+
export declare function noise(x: number, y: number, z: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Fractal Brownian motion: `octaves` layers of noise, each twice the
|
|
20
|
+
* frequency and half the amplitude of the last, scaled back into about
|
|
21
|
+
* [-1, 1]. One layer is a soft swell; three is a swell with detail on it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function fbm(x: number, y: number, z: number, octaves: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* A pseudo-random number in [0, 1) for the `n`th of something — a breath, a
|
|
26
|
+
* gust — uncorrelated with the `n ± 1`th, where `noise` would make
|
|
27
|
+
* neighbours alike. The classic shader hash: the fraction of a large sine.
|
|
28
|
+
*/
|
|
29
|
+
export declare function hash(n: number, z: number): number;
|
|
30
|
+
/** Hermite smoothstep from `a` to `b`, clamped to [0, 1]. */
|
|
31
|
+
export declare function smoothstep(a: number, b: number, x: number): number;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gradient noise, the texture every effect in `renderables/effects` is drawn
|
|
3
|
+
* from. Ken Perlin's improved noise (2002): a smooth pseudo-random
|
|
4
|
+
* field over space and time, continuous in its first and second derivatives,
|
|
5
|
+
* so a cell sampled a moment later has moved a little, never jumped. Things
|
|
6
|
+
* the eye knows as alive — water, wind, breath, a glow — vary like this:
|
|
7
|
+
* coherent nearby, uncorrelated far apart, never repeating exactly.
|
|
8
|
+
*
|
|
9
|
+
* [LAW:no-ambient-temporal-coupling] A pure function of its coordinates; time
|
|
10
|
+
* is one of them. The permutation is fixed, so a moment draws the same frame
|
|
11
|
+
* on every run.
|
|
12
|
+
*
|
|
13
|
+
* Tier 0 of `src/core/`: imports nothing. Not part of the package's surface;
|
|
14
|
+
* the effects playground reaches it as source.
|
|
15
|
+
*/
|
|
16
|
+
/** Ken Perlin's reference permutation, doubled so a lookup never wraps. */
|
|
17
|
+
const P = (() => {
|
|
18
|
+
const base = [
|
|
19
|
+
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10,
|
|
20
|
+
23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87,
|
|
21
|
+
174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211,
|
|
22
|
+
133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
|
|
23
|
+
89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5,
|
|
24
|
+
202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119,
|
|
25
|
+
248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,
|
|
26
|
+
178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
|
|
27
|
+
14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,
|
|
28
|
+
93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
|
|
29
|
+
];
|
|
30
|
+
return [...base, ...base];
|
|
31
|
+
})();
|
|
32
|
+
/** Quintic smoothstep: zero first and second derivative at 0 and 1. */
|
|
33
|
+
const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
|
|
34
|
+
const lerp = (a, b, t) => a + t * (b - a);
|
|
35
|
+
/** The dot of one of twelve edge gradients, picked by `hash`, with (x, y, z). */
|
|
36
|
+
function grad(hash, x, y, z) {
|
|
37
|
+
const h = hash & 15;
|
|
38
|
+
const u = h < 8 ? x : y;
|
|
39
|
+
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
|
|
40
|
+
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
|
41
|
+
}
|
|
42
|
+
/** Noise at (x, y, z), in about [-1, 1]; zero at every integer lattice point. */
|
|
43
|
+
export function noise(x, y, z) {
|
|
44
|
+
const fx = Math.floor(x);
|
|
45
|
+
const fy = Math.floor(y);
|
|
46
|
+
const fz = Math.floor(z);
|
|
47
|
+
const X = fx & 255;
|
|
48
|
+
const Y = fy & 255;
|
|
49
|
+
const Z = fz & 255;
|
|
50
|
+
x -= fx;
|
|
51
|
+
y -= fy;
|
|
52
|
+
z -= fz;
|
|
53
|
+
const u = fade(x);
|
|
54
|
+
const v = fade(y);
|
|
55
|
+
const w = fade(z);
|
|
56
|
+
const A = P[X] + Y;
|
|
57
|
+
const AA = P[A] + Z;
|
|
58
|
+
const AB = P[A + 1] + Z;
|
|
59
|
+
const B = P[X + 1] + Y;
|
|
60
|
+
const BA = P[B] + Z;
|
|
61
|
+
const BB = P[B + 1] + Z;
|
|
62
|
+
return lerp(lerp(lerp(grad(P[AA], x, y, z), grad(P[BA], x - 1, y, z), u), lerp(grad(P[AB], x, y - 1, z), grad(P[BB], x - 1, y - 1, z), u), v), lerp(lerp(grad(P[AA + 1], x, y, z - 1), grad(P[BA + 1], x - 1, y, z - 1), u), lerp(grad(P[AB + 1], x, y - 1, z - 1), grad(P[BB + 1], x - 1, y - 1, z - 1), u), v), w);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Fractal Brownian motion: `octaves` layers of noise, each twice the
|
|
66
|
+
* frequency and half the amplitude of the last, scaled back into about
|
|
67
|
+
* [-1, 1]. One layer is a soft swell; three is a swell with detail on it.
|
|
68
|
+
*/
|
|
69
|
+
export function fbm(x, y, z, octaves) {
|
|
70
|
+
let sum = 0;
|
|
71
|
+
let amplitude = 1;
|
|
72
|
+
let total = 0;
|
|
73
|
+
for (let i = 0; i < octaves; i++) {
|
|
74
|
+
const f = 2 ** i;
|
|
75
|
+
sum += amplitude * noise(x * f, y * f, z * f);
|
|
76
|
+
total += amplitude;
|
|
77
|
+
amplitude /= 2;
|
|
78
|
+
}
|
|
79
|
+
return sum / total;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A pseudo-random number in [0, 1) for the `n`th of something — a breath, a
|
|
83
|
+
* gust — uncorrelated with the `n ± 1`th, where `noise` would make
|
|
84
|
+
* neighbours alike. The classic shader hash: the fraction of a large sine.
|
|
85
|
+
*/
|
|
86
|
+
export function hash(n, z) {
|
|
87
|
+
const x = Math.sin(n * 12.9898 + z * 78.233) * 43758.5453;
|
|
88
|
+
return x - Math.floor(x);
|
|
89
|
+
}
|
|
90
|
+
/** Hermite smoothstep from `a` to `b`, clamped to [0, 1]. */
|
|
91
|
+
export function smoothstep(a, b, x) {
|
|
92
|
+
const t = Math.min(1, Math.max(0, (x - a) / (b - a)));
|
|
93
|
+
return t * t * (3 - 2 * t);
|
|
94
|
+
}
|
package/dist/core/paint.js
CHANGED
|
@@ -47,6 +47,15 @@ const GEOMETRY = {
|
|
|
47
47
|
// frames. The erase leads its row: after a row that fills the width, the
|
|
48
48
|
// cursor sits on its last cell, and an erase there would take it.
|
|
49
49
|
const ROW_START = "\r\x1b[2K";
|
|
50
|
+
// A frame's bytes between these two are shown at once: a terminal that has
|
|
51
|
+
// synchronized output (DEC private mode 2026 — iTerm2, kitty, WezTerm,
|
|
52
|
+
// Ghostty, foot, Windows Terminal …) holds the screen until the frame has
|
|
53
|
+
// fully arrived, so a repaint can never show a row erased and not yet
|
|
54
|
+
// redrawn. Thirty full-screen repaints a second show exactly that on a
|
|
55
|
+
// terminal that paints whenever bytes land, as a flicker over the whole
|
|
56
|
+
// frame. A terminal without the mode ignores both sequences.
|
|
57
|
+
const SYNC_START = "\x1b[?2026h";
|
|
58
|
+
const SYNC_END = "\x1b[?2026l";
|
|
50
59
|
const HIDE_CURSOR = "\x1b[?25l";
|
|
51
60
|
const SHOW_CURSOR = "\x1b[?25h";
|
|
52
61
|
const RESET_STYLE = "\x1b[0m";
|
|
@@ -79,7 +88,7 @@ export class SurfacePainter {
|
|
|
79
88
|
*/
|
|
80
89
|
paint(frame, screen, destination) {
|
|
81
90
|
const rows = fitRows(frame(), screen);
|
|
82
|
-
this.write(this.over(rows, screen, destination));
|
|
91
|
+
this.write(SYNC_START + this.over(rows, screen, destination) + SYNC_END);
|
|
83
92
|
return rows;
|
|
84
93
|
}
|
|
85
94
|
/**
|
|
@@ -93,7 +102,9 @@ export class SurfacePainter {
|
|
|
93
102
|
const rows = fitRows(frame(), screen);
|
|
94
103
|
const erase = this.over([], screen, destination);
|
|
95
104
|
const ended = text.endsWith("\n") ? text : `${text}\n`;
|
|
96
|
-
|
|
105
|
+
// One synchronized block around all three, so the terminal is released
|
|
106
|
+
// only once the frame is back.
|
|
107
|
+
return SYNC_START + erase + ended + this.over(rows, screen, destination) + SYNC_END;
|
|
97
108
|
}
|
|
98
109
|
// The bytes that paint `rows` over the last frame. They go out in one
|
|
99
110
|
// string, so whatever takes the last frame away arrives with the new one.
|
package/dist/core/pretty.d.ts
CHANGED
|
@@ -120,12 +120,33 @@ export declare class Pretty implements Renderable, Measurable {
|
|
|
120
120
|
* traversals.
|
|
121
121
|
*/
|
|
122
122
|
private _scalar;
|
|
123
|
+
/**
|
|
124
|
+
* `value` quoted, and cut to `maxString` when it is longer.
|
|
125
|
+
*
|
|
126
|
+
* The dropped count is an annotation about the value, not part of it, so it
|
|
127
|
+
* goes after the closing quote — the way `maxLength`'s `... +N` sits beside
|
|
128
|
+
* the kept entries rather than inside the last one. Inside the quotes it
|
|
129
|
+
* reads as content, and copying it out yields a string the program never
|
|
130
|
+
* held.
|
|
131
|
+
*
|
|
132
|
+
* A cluster is at least one code unit, so a string no longer than the cap in
|
|
133
|
+
* code units is within it in clusters, and needs no segmenting.
|
|
134
|
+
*/
|
|
135
|
+
private _string;
|
|
136
|
+
/**
|
|
137
|
+
* A key as an object literal would spell it: bare when it is a name or an
|
|
138
|
+
* index, which reads as itself, and quoted as a string otherwise. Rich reprs
|
|
139
|
+
* every key; printed raw, `"a b"` was indistinguishable from two words and a
|
|
140
|
+
* `\r` in one was dropped by `RichText`, so the key shown was not the key held.
|
|
141
|
+
* Quoting only what needs it keeps the JavaScript reading `{ a: 1 }`. A
|
|
142
|
+
* quoted key is a string and `maxString` cuts it; a bare one is a name.
|
|
143
|
+
*/
|
|
144
|
+
private _key;
|
|
123
145
|
/**
|
|
124
146
|
* `value` cut to `max` grapheme clusters, with the rest counted after the
|
|
125
147
|
* closing quote.
|
|
126
148
|
*
|
|
127
|
-
* The unit is the cluster of the value, counted before `
|
|
128
|
-
* escapes it. A code unit cut can keep half a surrogate pair, which
|
|
149
|
+
* The unit is the cluster of the value, counted before `quoted` escapes it. A code unit cut can keep half a surrogate pair, which
|
|
129
150
|
* JSON.stringify prints as a `\ud83d` escape; a code point cut can keep half
|
|
130
151
|
* a flag or a ZWJ family. Either way the kept prefix shows something the
|
|
131
152
|
* value never held, and the dropped count is in a unit nobody can see.
|
package/dist/core/pretty.js
CHANGED
|
@@ -447,6 +447,26 @@ const SEPARATOR = ", ";
|
|
|
447
447
|
const EXPAND_SEPARATOR = SEPARATOR.trimEnd();
|
|
448
448
|
/** The marker for positions the bound dropped, or nothing when it dropped none. */
|
|
449
449
|
const elided = (dropped) => dropped > 0 ? [{ head: `... +${dropped}`, join: "", holes: [] }] : [];
|
|
450
|
+
/**
|
|
451
|
+
* A key `Pretty._key` may leave bare. The bare form refuses a default-ignorable code point, which prints as
|
|
452
|
+
* nothing, so `"a\ufe0f"` cannot pass for `a`; `ID_Continue` admits several.
|
|
453
|
+
* An index is at most 15 digits: every such literal is a safe integer and
|
|
454
|
+
* names itself, where `99999999999999999999` would name `1e20`'s key.
|
|
455
|
+
*/
|
|
456
|
+
const BARE_KEY = /^(?![^]*\p{Default_Ignorable_Code_Point})(?:[\p{ID_Start}$_][\p{ID_Continue}$]*|0|[1-9]\d{0,14})$/u;
|
|
457
|
+
/**
|
|
458
|
+
* What Python's `str.isprintable` refuses \u2014 control, format, private-use,
|
|
459
|
+
* unassigned, and every separator but the space \u2014 which Rich's repr escapes
|
|
460
|
+
* and JSON.stringify leaves raw past C0. Raw, DEL vanishes in `RichText`, a C1
|
|
461
|
+
* byte reaches the terminal as a control, and a format character hides or
|
|
462
|
+
* reorders what is around it.
|
|
463
|
+
*/
|
|
464
|
+
const UNPRINTABLE = /(?! )[\p{Cc}\p{Cf}\p{Co}\p{Cn}\p{Zl}\p{Zp}\p{Zs}]/gu;
|
|
465
|
+
/** `value` in double quotes, every character that does not print escaped. */
|
|
466
|
+
const quoted = (value) => JSON.stringify(value).replace(UNPRINTABLE, (c) => {
|
|
467
|
+
const hex = c.codePointAt(0).toString(16).padStart(4, "0");
|
|
468
|
+
return hex.length > 4 ? `\\u{${hex}}` : `\\u${hex}`;
|
|
469
|
+
});
|
|
450
470
|
export class Pretty {
|
|
451
471
|
data;
|
|
452
472
|
indent;
|
|
@@ -520,19 +540,8 @@ export class Pretty {
|
|
|
520
540
|
if (value === undefined)
|
|
521
541
|
return "undefined";
|
|
522
542
|
switch (typeof value) {
|
|
523
|
-
case "string":
|
|
524
|
-
|
|
525
|
-
// so it goes after the closing quote — the way `maxLength`'s `... +N`
|
|
526
|
-
// sits beside the kept entries rather than inside the last one. Inside
|
|
527
|
-
// the quotes it reads as content, and copying it out yields a string
|
|
528
|
-
// the program never held.
|
|
529
|
-
//
|
|
530
|
-
// A cluster is at least one code unit, so a string no longer than the
|
|
531
|
-
// cap in code units is within it in clusters, and needs no segmenting.
|
|
532
|
-
if (this.maxString === undefined || value.length <= this.maxString)
|
|
533
|
-
return JSON.stringify(value);
|
|
534
|
-
return this._cutString(value, this.maxString);
|
|
535
|
-
}
|
|
543
|
+
case "string":
|
|
544
|
+
return this._string(value);
|
|
536
545
|
case "number":
|
|
537
546
|
case "bigint":
|
|
538
547
|
case "boolean":
|
|
@@ -544,12 +553,39 @@ export class Pretty {
|
|
|
544
553
|
}
|
|
545
554
|
return null;
|
|
546
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* `value` quoted, and cut to `maxString` when it is longer.
|
|
558
|
+
*
|
|
559
|
+
* The dropped count is an annotation about the value, not part of it, so it
|
|
560
|
+
* goes after the closing quote — the way `maxLength`'s `... +N` sits beside
|
|
561
|
+
* the kept entries rather than inside the last one. Inside the quotes it
|
|
562
|
+
* reads as content, and copying it out yields a string the program never
|
|
563
|
+
* held.
|
|
564
|
+
*
|
|
565
|
+
* A cluster is at least one code unit, so a string no longer than the cap in
|
|
566
|
+
* code units is within it in clusters, and needs no segmenting.
|
|
567
|
+
*/
|
|
568
|
+
_string(value) {
|
|
569
|
+
if (this.maxString === undefined || value.length <= this.maxString)
|
|
570
|
+
return quoted(value);
|
|
571
|
+
return this._cutString(value, this.maxString);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* A key as an object literal would spell it: bare when it is a name or an
|
|
575
|
+
* index, which reads as itself, and quoted as a string otherwise. Rich reprs
|
|
576
|
+
* every key; printed raw, `"a b"` was indistinguishable from two words and a
|
|
577
|
+
* `\r` in one was dropped by `RichText`, so the key shown was not the key held.
|
|
578
|
+
* Quoting only what needs it keeps the JavaScript reading `{ a: 1 }`. A
|
|
579
|
+
* quoted key is a string and `maxString` cuts it; a bare one is a name.
|
|
580
|
+
*/
|
|
581
|
+
_key(key) {
|
|
582
|
+
return BARE_KEY.test(key) ? key : this._string(key);
|
|
583
|
+
}
|
|
547
584
|
/**
|
|
548
585
|
* `value` cut to `max` grapheme clusters, with the rest counted after the
|
|
549
586
|
* closing quote.
|
|
550
587
|
*
|
|
551
|
-
* The unit is the cluster of the value, counted before `
|
|
552
|
-
* escapes it. A code unit cut can keep half a surrogate pair, which
|
|
588
|
+
* The unit is the cluster of the value, counted before `quoted` escapes it. A code unit cut can keep half a surrogate pair, which
|
|
553
589
|
* JSON.stringify prints as a `\ud83d` escape; a code point cut can keep half
|
|
554
590
|
* a flag or a ZWJ family. Either way the kept prefix shows something the
|
|
555
591
|
* value never held, and the dropped count is in a unit nobody can see.
|
|
@@ -563,8 +599,8 @@ export class Pretty {
|
|
|
563
599
|
if (cut === undefined) {
|
|
564
600
|
const clusters = graphemes(value);
|
|
565
601
|
cut = clusters.length <= max
|
|
566
|
-
?
|
|
567
|
-
:
|
|
602
|
+
? quoted(value)
|
|
603
|
+
: quoted(clusters.slice(0, max).join("")) + `+${clusters.length - max}`;
|
|
568
604
|
this.cutStrings.set(value, cut);
|
|
569
605
|
}
|
|
570
606
|
return cut;
|
|
@@ -640,7 +676,7 @@ export class Pretty {
|
|
|
640
676
|
const { record } = form;
|
|
641
677
|
const keys = Object.keys(record);
|
|
642
678
|
return this._container("{", "}", " ", keys.length, level, () => keys.slice(0, cap).map((k) => ({
|
|
643
|
-
head: k,
|
|
679
|
+
head: this._key(k),
|
|
644
680
|
join: ": ",
|
|
645
681
|
holes: [{ read: () => record[k], tail: flat("") }],
|
|
646
682
|
})));
|
package/dist/core/protocol.d.ts
CHANGED
|
@@ -96,6 +96,8 @@ export interface RenderOptions {
|
|
|
96
96
|
*/
|
|
97
97
|
onDraw?: (owner: object) => void;
|
|
98
98
|
}
|
|
99
|
+
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
100
|
+
export declare const isAscii: (text: string) => boolean;
|
|
99
101
|
/**
|
|
100
102
|
* `chosen` as this output can draw it: `ascii` in its place when the output is
|
|
101
103
|
* ASCII-only and `chosen` spends a glyph outside ASCII, otherwise `chosen`
|
package/dist/core/protocol.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { cellCount } from "./cells.js";
|
|
6
6
|
import { DEFAULT_THEME } from "./style.js";
|
|
7
|
-
|
|
7
|
+
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
8
|
+
export const isAscii = (text) => /^[\x00-\x7F]*$/.test(text);
|
|
8
9
|
export function drawable(options, chosen, ascii, glyphs = String) {
|
|
9
|
-
return options.asciiOnly === true && !
|
|
10
|
+
return options.asciiOnly === true && !isAscii(glyphs(chosen)) ? ascii : chosen;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* The budget a renderable hands the one child filling its space: its own, less
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list cursor: one clamp, shared by every list that moves a selection —
|
|
3
|
+
* `ListView`'s callers and `Dropdown` alike — so it has one definition.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The selection `delta` items on from `index` in a list of `length`, held to
|
|
7
|
+
* the list's ends: `-Infinity` is the first item and `Infinity` the last. A
|
|
8
|
+
* move that cannot go anywhere returns `index` itself, so `next === index`
|
|
9
|
+
* says nothing moved and the caller can skip a repaint. An empty list has
|
|
10
|
+
* nowhere to move to and returns `index`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function moveSelection(index: number, delta: number, length: number): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list cursor: one clamp, shared by every list that moves a selection —
|
|
3
|
+
* `ListView`'s callers and `Dropdown` alike — so it has one definition.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The selection `delta` items on from `index` in a list of `length`, held to
|
|
7
|
+
* the list's ends: `-Infinity` is the first item and `Infinity` the last. A
|
|
8
|
+
* move that cannot go anywhere returns `index` itself, so `next === index`
|
|
9
|
+
* says nothing moved and the caller can skip a repaint. An empty list has
|
|
10
|
+
* nowhere to move to and returns `index`.
|
|
11
|
+
*/
|
|
12
|
+
export function moveSelection(index, delta, length) {
|
|
13
|
+
return length === 0 ? index : Math.max(0, Math.min(length - 1, index + delta));
|
|
14
|
+
}
|