@promptctl/rich-js 0.19.0 → 0.20.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/color.d.ts +1 -0
- package/dist/core/color.js +3 -2
- 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 +103 -72
- package/dist/core/strip.js +240 -212
- package/dist/core/style.js +9 -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 +84 -0
- package/dist/host/input.js +498 -0
- package/dist/host/terminal-host.d.ts +12 -0
- package/dist/host/terminal-host.js +2 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +6 -3
- package/dist/node/terminal-host.d.ts +7 -0
- package/dist/node/terminal-host.js +7 -0
- package/dist/renderables/effect.d.ts +23 -8
- package/dist/renderables/effect.js +48 -26
- package/dist/renderables/effects.d.ts +252 -0
- package/dist/renderables/effects.js +565 -0
- package/dist/renderables/embed.d.ts +11 -5
- package/dist/renderables/embed.js +13 -6
- 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 +51 -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/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 +22 -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/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;
|
|
@@ -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
|
+
}
|
package/dist/core/strip.d.ts
CHANGED
|
@@ -70,83 +70,114 @@ export declare class Strip<T extends StyledRenderable = StyledRenderable> implem
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
73
|
+
* A colour a joiner's cell is drawn in. Each variant names where the colour
|
|
74
|
+
* comes from when the join is drawn:
|
|
75
|
+
* • an `edge` — a neighbour's ground (`bg`) or text (`fg`) at the column
|
|
76
|
+
* facing the join, read through `StyledRenderable.edgeStyle`;
|
|
77
|
+
* • a `role` — a style name, resolved against the render's theme, whose
|
|
78
|
+
* foreground colour is the colour;
|
|
79
|
+
* • a `mix` — two colours blended at `t` (0 is the first, 1 the second).
|
|
80
|
+
*/
|
|
81
|
+
export type Paint = {
|
|
82
|
+
readonly edge: "left" | "right";
|
|
83
|
+
readonly part: "bg" | "fg";
|
|
84
|
+
} | {
|
|
85
|
+
readonly role: string;
|
|
86
|
+
} | {
|
|
87
|
+
readonly mix: readonly [Paint, Paint, number];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* A `Paint`, or `"ground"`: the terminal's own background, whose colour nobody
|
|
91
|
+
* knows. That is why it is never mixed (`Paint` excludes it), and why a cell
|
|
92
|
+
* may put it on one side only. As a cell's `fg` it is drawn in reverse video —
|
|
93
|
+
* the glyph's ink shows the terminal's background through a cell filled with
|
|
94
|
+
* the `bg` colour — so an inverted shape is the same glyph, never a glyph of
|
|
95
|
+
* its own.
|
|
96
|
+
*/
|
|
97
|
+
export type CellColor = Paint | "ground";
|
|
98
|
+
/**
|
|
99
|
+
* One styled run of a join. A `oneOf` glyph is a set any member of which may
|
|
100
|
+
* stand in the cell; every member has the same width, because consumers
|
|
101
|
+
* measure a join by rendering it.
|
|
102
|
+
*/
|
|
103
|
+
export interface JoinCell {
|
|
104
|
+
readonly glyph: string | {
|
|
105
|
+
readonly oneOf: readonly string[];
|
|
106
|
+
};
|
|
107
|
+
readonly fg: CellColor;
|
|
108
|
+
readonly bg: CellColor;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The cells a joiner draws at each kind of join:
|
|
112
|
+
* • `seam` — between two neighbours;
|
|
113
|
+
* • `divider` — in the seam's place where the two grounds are drawn as one
|
|
114
|
+
* colour (see `SEAM_MIN_DELTA_E`), so the seam would vanish into them;
|
|
115
|
+
* the same width as `seam`;
|
|
116
|
+
* • `lead` — where a run is entered from nothing: the strip's start, or a
|
|
117
|
+
* neighbour with no ground for the seam to paint. It cannot name `left`;
|
|
118
|
+
* • `tail` — where a run leaves into nothing. It cannot name `right`.
|
|
119
|
+
*/
|
|
120
|
+
export interface JoinShape {
|
|
121
|
+
readonly seam: readonly JoinCell[];
|
|
122
|
+
readonly divider: readonly JoinCell[];
|
|
123
|
+
readonly lead: readonly JoinCell[];
|
|
124
|
+
readonly tail: readonly JoinCell[];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The least ΔE_OK two neighbouring backgrounds must differ by for a seam drawn
|
|
128
|
+
* between them to be seen. Below it the seam is drawn in a colour the eye
|
|
129
|
+
* cannot tell from its own background, so the joiner draws the divider
|
|
76
130
|
* instead. Twice the ~.02 threshold of a visible difference, because a seam is
|
|
77
131
|
* one cell wide.
|
|
78
132
|
*/
|
|
79
133
|
export declare const SEAM_MIN_DELTA_E = 0.04;
|
|
80
|
-
export interface PowerlineJoinerOptions {
|
|
81
|
-
/** Glyph for every join between two coloured items. */
|
|
82
|
-
glyph: string;
|
|
83
|
-
/**
|
|
84
|
-
* Glyph drawn between neighbours whose backgrounds the eye cannot tell apart,
|
|
85
|
-
* in the left item's text colour — the arrow itself would vanish into the
|
|
86
|
-
* shared background.
|
|
87
|
-
*/
|
|
88
|
-
divider: string;
|
|
89
|
-
/**
|
|
90
|
-
* Glyph for every join a coloured item is entered from nothing — the strip's
|
|
91
|
-
* start, or a colourless left neighbour — painted in the right item's
|
|
92
|
-
* background. `""` begins a coloured run flat.
|
|
93
|
-
*/
|
|
94
|
-
lead: string;
|
|
95
|
-
/**
|
|
96
|
-
* Glyph for every join a coloured item leaves into nothing — the strip's end,
|
|
97
|
-
* or a colourless right neighbour — painted in the left item's background.
|
|
98
|
-
* `""` ends a coloured run flat.
|
|
99
|
-
*/
|
|
100
|
-
tail: string;
|
|
101
|
-
}
|
|
102
134
|
/**
|
|
103
|
-
* The
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* the
|
|
108
|
-
* with
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
135
|
+
* The one joiner: what it draws at each join is the `JoinShape` it is given,
|
|
136
|
+
* so a new joiner is a value, never a class.
|
|
137
|
+
*
|
|
138
|
+
* Which part a join draws is read off its two neighbours. A neighbour is
|
|
139
|
+
* missing at the strip's ends, and counts as missing — an endpoint — when the
|
|
140
|
+
* shape paints with its ground anywhere and it has none to paint. With both present the
|
|
141
|
+
* join is the seam, or the divider where the two grounds are drawn as one
|
|
142
|
+
* colour at `options.colorSystem`; with only the right, the lead; with only
|
|
143
|
+
* the left, the tail; with neither, nothing.
|
|
144
|
+
*
|
|
145
|
+
* `ascii` is the shape an ASCII-only output draws instead, whole.
|
|
112
146
|
*/
|
|
113
|
-
export declare
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
export interface CapsuleJoinerOptions {
|
|
120
|
-
/** Left-cap glyph (default: U+E0B6, powerline rounded left). */
|
|
121
|
-
left?: string;
|
|
122
|
-
/** Right-cap glyph (default: U+E0B4, powerline rounded right). */
|
|
123
|
-
right?: string;
|
|
124
|
-
/** Separator inserted between adjacent capsules in the middle position. */
|
|
125
|
-
separator?: string;
|
|
126
|
-
}
|
|
127
|
-
export declare class CapsuleJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
|
|
128
|
-
private readonly _caps;
|
|
129
|
-
private readonly _separator;
|
|
130
|
-
constructor(options?: CapsuleJoinerOptions);
|
|
131
|
-
_emit(left: T | null, right: T | null, options: RenderOptions): Iterable<Segment>;
|
|
132
|
-
join(left: T | null, right: T | null): Renderable;
|
|
133
|
-
}
|
|
134
|
-
export interface PlainJoinerOptions {
|
|
135
|
-
separator?: string;
|
|
136
|
-
style?: Style;
|
|
137
|
-
}
|
|
138
|
-
export declare class PlainJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
|
|
139
|
-
private readonly _separator;
|
|
140
|
-
private readonly _style;
|
|
141
|
-
constructor(options?: PlainJoinerOptions);
|
|
142
|
-
join(left: T | null, right: T | null): Renderable;
|
|
143
|
-
}
|
|
144
|
-
export interface GradientJoinerOptions {
|
|
145
|
-
/** Number of cells between adjacent items (default: 4). */
|
|
146
|
-
steps?: number;
|
|
147
|
-
}
|
|
148
|
-
export declare class GradientJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
|
|
149
|
-
private readonly _steps;
|
|
150
|
-
constructor(options?: GradientJoinerOptions);
|
|
147
|
+
export declare class CellJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
|
|
148
|
+
readonly shape: JoinShape;
|
|
149
|
+
readonly ascii: JoinShape;
|
|
150
|
+
private readonly _forms;
|
|
151
|
+
constructor(shape: JoinShape, ascii: JoinShape);
|
|
151
152
|
join(left: T | null, right: T | null): Renderable;
|
|
152
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The powerline joiner: U+E0B0 (right-arrow) in the left ground over the
|
|
156
|
+
* right, divided by U+E0B1 (thin right-arrow) in the left item's text. The
|
|
157
|
+
* lead is the arrow with the left ground replaced by the terminal's — drawn
|
|
158
|
+
* in reverse video it leaves a notch whose base is the cell's left edge, inked
|
|
159
|
+
* in the terminal's background colour. That ink is opaque, so under a
|
|
160
|
+
* translucent terminal the notch does not let what is behind it through. The
|
|
161
|
+
* tail is the arrow over the terminal's ground.
|
|
162
|
+
* ASCII draws `>` everywhere and begins each run flat.
|
|
163
|
+
*/
|
|
164
|
+
export declare const POWERLINE_JOINER: CellJoiner<StyledRenderable>;
|
|
165
|
+
/**
|
|
166
|
+
* Rounded pills: each run opened by U+E0B6 and closed by U+E0B4, both in its
|
|
167
|
+
* ground over the terminal's, a space between pills. ASCII draws `(` and `)`.
|
|
168
|
+
*/
|
|
169
|
+
export declare const CAPSULE_JOINER: CellJoiner<StyledRenderable>;
|
|
170
|
+
/**
|
|
171
|
+
* ` | ` between every pair of items, in the theme's `strip.separator` colour;
|
|
172
|
+
* nothing at the ends. Its shape names no neighbour, so it joins items with no
|
|
173
|
+
* ground too.
|
|
174
|
+
*/
|
|
175
|
+
export declare const PLAIN_JOINER: CellJoiner<StyledRenderable>;
|
|
176
|
+
/**
|
|
177
|
+
* A ramp of `steps` cells from the left ground to the right. Each cell is the
|
|
178
|
+
* half block U+258C, ink on its left half and ground on its right, so it
|
|
179
|
+
* carries two samples; ASCII draws a space, keeping the grounds alone. Nothing
|
|
180
|
+
* at the ends, or beside a neighbour with no ground — there is no colour to
|
|
181
|
+
* ramp toward.
|
|
182
|
+
*/
|
|
183
|
+
export declare function gradientJoiner(steps?: number): CellJoiner;
|