@promptctl/rich-js 0.18.1 → 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 +11 -8
- package/dist/core/box.d.ts +3 -1
- package/dist/core/box.js +6 -13
- package/dist/core/cells.d.ts +61 -28
- package/dist/core/cells.js +171 -124
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +99 -18
- package/dist/core/color.js +334 -146
- package/dist/core/console.d.ts +38 -7
- package/dist/core/console.js +159 -90
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.js +1 -4
- package/dist/core/export-lines.d.ts +22 -13
- package/dist/core/export-lines.js +37 -19
- package/dist/core/export-svg.d.ts +49 -0
- package/dist/core/export-svg.js +213 -0
- package/dist/core/fnv1a.d.ts +10 -0
- package/dist/core/fnv1a.js +17 -0
- package/dist/core/highlighter.d.ts +2 -2
- package/dist/core/highlighter.js +113 -47
- package/dist/core/json.js +1 -1
- package/dist/core/markup.d.ts +28 -7
- package/dist/core/markup.js +235 -312
- package/dist/core/measure.d.ts +3 -3
- package/dist/core/measure.js +12 -7
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/noise.d.ts +31 -0
- package/dist/core/noise.js +94 -0
- package/dist/core/osc8.d.ts +6 -6
- package/dist/core/osc8.js +9 -21
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +161 -0
- package/dist/core/place.d.ts +1 -1
- package/dist/core/place.js +4 -6
- package/dist/core/pretty.d.ts +126 -2
- package/dist/core/pretty.js +571 -105
- package/dist/core/protocol.d.ts +43 -3
- package/dist/core/protocol.js +23 -2
- package/dist/core/render.d.ts +33 -30
- package/dist/core/render.js +43 -83
- package/dist/core/segment.d.ts +6 -5
- package/dist/core/segment.js +11 -15
- package/dist/core/selection.d.ts +12 -0
- package/dist/core/selection.js +14 -0
- package/dist/core/strip.d.ts +116 -74
- package/dist/core/strip.js +266 -217
- package/dist/core/style.d.ts +27 -7
- package/dist/core/style.js +106 -41
- package/dist/core/text.d.ts +43 -16
- package/dist/core/text.js +180 -59
- package/dist/core/wrap.d.ts +5 -0
- package/dist/core/wrap.js +16 -3
- package/dist/host/app.d.ts +95 -17
- package/dist/host/app.js +201 -61
- package/dist/host/index.d.ts +4 -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 +15 -2
- package/dist/host/terminal-host.js +2 -0
- package/dist/index.d.ts +26 -14
- package/dist/index.js +17 -7
- package/dist/node/prompt.d.ts +13 -0
- package/dist/node/prompt.js +30 -9
- package/dist/node/save.d.ts +4 -3
- package/dist/node/save.js +6 -3
- package/dist/node/terminal-host.d.ts +10 -2
- package/dist/node/terminal-host.js +7 -0
- package/dist/node/traceback.d.ts +2 -1
- package/dist/node/traceback.js +3 -39
- package/dist/renderables/align.js +2 -9
- package/dist/renderables/columns.d.ts +45 -29
- package/dist/renderables/columns.js +142 -103
- package/dist/renderables/constrain.js +6 -12
- package/dist/renderables/effect.d.ts +113 -0
- package/dist/renderables/effect.js +182 -0
- package/dist/renderables/effects.d.ts +252 -0
- package/dist/renderables/effects.js +565 -0
- package/dist/renderables/embed.d.ts +94 -31
- package/dist/renderables/embed.js +141 -45
- package/dist/renderables/group.d.ts +6 -3
- package/dist/renderables/group.js +5 -0
- package/dist/renderables/layout.d.ts +6 -23
- package/dist/renderables/layout.js +24 -113
- package/dist/renderables/list-view.d.ts +42 -0
- package/dist/renderables/list-view.js +78 -0
- package/dist/renderables/live.d.ts +40 -16
- package/dist/renderables/live.js +96 -83
- package/dist/renderables/markdown.d.ts +16 -2
- package/dist/renderables/markdown.js +521 -118
- package/dist/renderables/padding.d.ts +22 -1
- package/dist/renderables/padding.js +30 -24
- package/dist/renderables/panel.d.ts +31 -33
- package/dist/renderables/panel.js +137 -141
- package/dist/renderables/progress.d.ts +110 -16
- package/dist/renderables/progress.js +212 -81
- package/dist/renderables/progressBar.d.ts +24 -4
- package/dist/renderables/progressBar.js +78 -23
- package/dist/renderables/prompt.d.ts +37 -15
- package/dist/renderables/prompt.js +125 -85
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +3 -2
- package/dist/renderables/rule.js +50 -52
- package/dist/renderables/spinner.d.ts +28 -2
- package/dist/renderables/spinner.js +52 -19
- package/dist/renderables/status.d.ts +13 -7
- package/dist/renderables/status.js +15 -29
- package/dist/renderables/syntax.d.ts +37 -7
- package/dist/renderables/syntax.js +134 -83
- package/dist/renderables/table.d.ts +58 -20
- package/dist/renderables/table.js +373 -197
- package/dist/renderables/traceback.d.ts +5 -4
- package/dist/renderables/traceback.js +239 -68
- package/dist/renderables/tree.d.ts +10 -2
- package/dist/renderables/tree.js +61 -34
- package/dist/renderables/viewport.d.ts +30 -0
- package/dist/renderables/viewport.js +61 -7
- package/dist/template-bindings/color-funcs.d.ts +31 -9
- package/dist/template-bindings/color-funcs.js +40 -14
- package/dist/template-bindings/helpers.d.ts +4 -2
- package/dist/template-bindings/helpers.js +5 -20
- package/dist/template-bindings/index.d.ts +29 -17
- package/dist/template-bindings/index.js +36 -20
- package/dist/template-bindings/palette-funcs.d.ts +3 -1
- package/dist/template-bindings/palette-funcs.js +11 -7
- package/dist/template-bindings/style-funcs.js +4 -2
- package/dist/themes/buildPalette.d.ts +24 -3
- package/dist/themes/buildPalette.js +52 -8
- package/dist/themes/colorMath.d.ts +29 -17
- package/dist/themes/colorMath.js +74 -48
- package/dist/themes/colorRef.d.ts +2 -17
- package/dist/themes/colorRef.js +13 -31
- package/dist/themes/palette.d.ts +24 -0
- package/dist/themes/palette.js +46 -3
- package/dist/themes/ramp.d.ts +21 -34
- package/dist/themes/ramp.js +23 -42
- package/dist/themes/registry.js +39 -28
- package/dist/widgets/button.d.ts +2 -6
- package/dist/widgets/button.js +9 -40
- package/dist/widgets/checkbox.d.ts +2 -5
- package/dist/widgets/checkbox.js +9 -31
- package/dist/widgets/dropdown.d.ts +2 -5
- package/dist/widgets/dropdown.js +17 -51
- 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/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +2 -5
- package/dist/widgets/slider.js +11 -34
- package/dist/widgets/text-input.d.ts +19 -10
- package/dist/widgets/text-input.js +156 -147
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +2 -5
- package/dist/widgets/toggle.js +8 -31
- package/dist/widgets/types.d.ts +8 -25
- package/dist/widgets/types.js +8 -0
- package/dist/widgets/widget-app.d.ts +24 -7
- package/dist/widgets/widget-app.js +35 -18
- package/dist/widgets/widget-base.d.ts +2 -0
- package/dist/widgets/widget-base.js +9 -2
- package/package.json +31 -17
package/dist/core/measure.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Measurement — min/max cell width calculation for renderables.
|
|
3
3
|
*/
|
|
4
|
-
import { measuring } from "./protocol.js";
|
|
4
|
+
import { isMeasurable, measuring } from "./protocol.js";
|
|
5
5
|
export class Measurement {
|
|
6
6
|
minimum;
|
|
7
7
|
maximum;
|
|
@@ -40,20 +40,25 @@ export class Measurement {
|
|
|
40
40
|
clamp(minWidth, maxWidth) {
|
|
41
41
|
return new Measurement(Math.min(Math.max(this.minimum, minWidth), maxWidth), Math.min(Math.max(this.maximum, minWidth), maxWidth));
|
|
42
42
|
}
|
|
43
|
-
// [LAW:single-enforcer] Single entry point for measuring a
|
|
44
|
-
|
|
43
|
+
// [LAW:single-enforcer] Single entry point for measuring a renderable. One
|
|
44
|
+
// that cannot measure itself asks for the whole offer, as the reference's
|
|
45
|
+
// `Measurement.get` answers for it — so under an unbounded offer it asks for
|
|
46
|
+
// `Infinity`, and `withBoundedWidth` refuses rather than guessing a width.
|
|
47
|
+
static get(options, renderable) {
|
|
45
48
|
if (options.maxWidth < 1)
|
|
46
49
|
return new Measurement(0, 0);
|
|
47
|
-
|
|
50
|
+
if (!isMeasurable(renderable))
|
|
51
|
+
return new Measurement(0, options.maxWidth);
|
|
52
|
+
const { minimum, maximum } = renderable.measure(measuring(options));
|
|
48
53
|
return new Measurement(minimum, Math.min(maximum, options.maxWidth)).normalize();
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
|
-
export function measureRenderables(options,
|
|
52
|
-
if (
|
|
56
|
+
export function measureRenderables(options, renderables) {
|
|
57
|
+
if (renderables.length === 0)
|
|
53
58
|
return new Measurement(0, 0);
|
|
54
59
|
let minOfAll = 0;
|
|
55
60
|
let maxOfAll = 0;
|
|
56
|
-
for (const m of
|
|
61
|
+
for (const m of renderables) {
|
|
57
62
|
const measurement = Measurement.get(options, m);
|
|
58
63
|
minOfAll = Math.max(minOfAll, measurement.minimum);
|
|
59
64
|
maxOfAll = Math.max(maxOfAll, measurement.maximum);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memo — a string-keyed memo with a size bound: the one cache shape for a
|
|
3
|
+
* pure computation whose keys a long-running host derives without end. A
|
|
4
|
+
* statusline daemon redrawing a time-varying colour parses a new hex string,
|
|
5
|
+
* matches a new RGB triple, measures a new cell, on every frame; a memo that
|
|
6
|
+
* never evicts grows by one entry per frame for the life of the process.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MEMO_MAX = 4096;
|
|
9
|
+
/**
|
|
10
|
+
* `get(key, compute)` returns the value remembered for `key`, computing and
|
|
11
|
+
* remembering it on a miss. Holds at most `MEMO_MAX` entries. `compute` must
|
|
12
|
+
* be a pure function of `key`, so a value recomputed after a clear is the one
|
|
13
|
+
* that was dropped.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Memo<V extends NonNullable<unknown>> {
|
|
16
|
+
private readonly entries;
|
|
17
|
+
get size(): number;
|
|
18
|
+
get(key: string, compute: (key: string) => V): V;
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memo — a string-keyed memo with a size bound: the one cache shape for a
|
|
3
|
+
* pure computation whose keys a long-running host derives without end. A
|
|
4
|
+
* statusline daemon redrawing a time-varying colour parses a new hex string,
|
|
5
|
+
* matches a new RGB triple, measures a new cell, on every frame; a memo that
|
|
6
|
+
* never evicts grows by one entry per frame for the life of the process.
|
|
7
|
+
*/
|
|
8
|
+
// [LAW:single-enforcer] The one size policy for every memo in the library.
|
|
9
|
+
// Clearing at the cap rather than evicting least-recently-used: a refill costs
|
|
10
|
+
// one recomputation per key, and a clear needs no bookkeeping on the hit path.
|
|
11
|
+
export const MEMO_MAX = 4096;
|
|
12
|
+
/**
|
|
13
|
+
* `get(key, compute)` returns the value remembered for `key`, computing and
|
|
14
|
+
* remembering it on a miss. Holds at most `MEMO_MAX` entries. `compute` must
|
|
15
|
+
* be a pure function of `key`, so a value recomputed after a clear is the one
|
|
16
|
+
* that was dropped.
|
|
17
|
+
*/
|
|
18
|
+
export class Memo {
|
|
19
|
+
entries = new Map();
|
|
20
|
+
get size() {
|
|
21
|
+
return this.entries.size;
|
|
22
|
+
}
|
|
23
|
+
get(key, compute) {
|
|
24
|
+
const remembered = this.entries.get(key);
|
|
25
|
+
if (remembered !== undefined)
|
|
26
|
+
return remembered;
|
|
27
|
+
// Computed before the clear, so a compute that throws costs no entries.
|
|
28
|
+
const value = compute(key);
|
|
29
|
+
if (this.entries.size >= MEMO_MAX)
|
|
30
|
+
this.entries.clear();
|
|
31
|
+
this.entries.set(key, value);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -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/osc8.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The OSC 8 hyperlink wire grammar — which bytes may not appear inside a
|
|
3
3
|
* link, how a link becomes bytes, and how bytes are read back as a link.
|
|
4
4
|
*
|
|
5
|
-
* [LAW:one-source-of-truth] The producer (`
|
|
5
|
+
* [LAW:one-source-of-truth] The producer (`segmentToString`, the one encoder
|
|
6
6
|
* every link reaches the wire through) opens a link with `osc8Open` and closes
|
|
7
7
|
* it with `OSC8_CLOSE`; every consumer that reads rendered bytes (a width measure, a
|
|
8
8
|
* test extracting URLs) matches them with `OSC8`; the data-model boundary
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
* terminator set below, so the bytes a sanitizer removes and the bytes a
|
|
11
11
|
* reader stops at cannot disagree.
|
|
12
12
|
*
|
|
13
|
-
* [LAW:locality-or-seam] This module depends on
|
|
13
|
+
* [LAW:locality-or-seam] This module depends only on `fnv1a`; every consumer
|
|
14
14
|
* imports downward.
|
|
15
15
|
*
|
|
16
16
|
* The `id=` parameter is what makes one link hover as one link. A terminal
|
|
17
17
|
* treats cells as the same hyperlink when they share BOTH the URI and the id
|
|
18
18
|
* (the OSC 8 spec, and VTE / iTerm2 / kitty / WezTerm alike); without an id,
|
|
19
|
-
* each open sequence is its own link. The
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* each open sequence is its own link. The encoder writes one OSC 8 pair per
|
|
20
|
+
* segment, as the reference does, so a link whose text spans several segments
|
|
21
|
+
* — a bold glyph beside plain text, a padded cell — is emitted as several
|
|
22
|
+
* pairs, and would highlight piecewise on hover.
|
|
23
23
|
*
|
|
24
24
|
* [LAW:types-are-the-program] The id is a pure function of the URI, so the
|
|
25
25
|
* byte stream stays a pure function of (style, text, destination): no counter,
|
package/dist/core/osc8.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The OSC 8 hyperlink wire grammar — which bytes may not appear inside a
|
|
3
3
|
* link, how a link becomes bytes, and how bytes are read back as a link.
|
|
4
4
|
*
|
|
5
|
-
* [LAW:one-source-of-truth] The producer (`
|
|
5
|
+
* [LAW:one-source-of-truth] The producer (`segmentToString`, the one encoder
|
|
6
6
|
* every link reaches the wire through) opens a link with `osc8Open` and closes
|
|
7
7
|
* it with `OSC8_CLOSE`; every consumer that reads rendered bytes (a width measure, a
|
|
8
8
|
* test extracting URLs) matches them with `OSC8`; the data-model boundary
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
* terminator set below, so the bytes a sanitizer removes and the bytes a
|
|
11
11
|
* reader stops at cannot disagree.
|
|
12
12
|
*
|
|
13
|
-
* [LAW:locality-or-seam] This module depends on
|
|
13
|
+
* [LAW:locality-or-seam] This module depends only on `fnv1a`; every consumer
|
|
14
14
|
* imports downward.
|
|
15
15
|
*
|
|
16
16
|
* The `id=` parameter is what makes one link hover as one link. A terminal
|
|
17
17
|
* treats cells as the same hyperlink when they share BOTH the URI and the id
|
|
18
18
|
* (the OSC 8 spec, and VTE / iTerm2 / kitty / WezTerm alike); without an id,
|
|
19
|
-
* each open sequence is its own link. The
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* each open sequence is its own link. The encoder writes one OSC 8 pair per
|
|
20
|
+
* segment, as the reference does, so a link whose text spans several segments
|
|
21
|
+
* — a bold glyph beside plain text, a padded cell — is emitted as several
|
|
22
|
+
* pairs, and would highlight piecewise on hover.
|
|
23
23
|
*
|
|
24
24
|
* [LAW:types-are-the-program] The id is a pure function of the URI, so the
|
|
25
25
|
* byte stream stays a pure function of (style, text, destination): no counter,
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* different URIs merges nothing — the URIs still differ — so a 32-bit hash is
|
|
31
31
|
* exact, not approximate.
|
|
32
32
|
*/
|
|
33
|
+
import { fnv1a } from "./fnv1a.js";
|
|
33
34
|
/**
|
|
34
35
|
* The bytes that prematurely terminate an OSC 8 sequence: ESC (`\x1b`, which
|
|
35
36
|
* begins ST `ESC \`), BEL (`\x07`), and the 8-bit ST (`\x9c`).
|
|
@@ -40,20 +41,6 @@ const TERMINATOR_RE = new RegExp(`[${TERMINATORS}]`, "g");
|
|
|
40
41
|
export function stripOscTerminators(url) {
|
|
41
42
|
return url.replace(TERMINATOR_RE, "");
|
|
42
43
|
}
|
|
43
|
-
const utf8 = new TextEncoder();
|
|
44
|
-
/**
|
|
45
|
-
* FNV-1a, 32-bit, over the URL's UTF-8 bytes, as 8 lowercase hex digits — a
|
|
46
|
-
* legal `id=` value (no `:`/`;`). UTF-8 rather than UTF-16 code units, so any
|
|
47
|
-
* other runtime computing standard FNV-1a over the same URL gets the same id.
|
|
48
|
-
*/
|
|
49
|
-
function linkId(url) {
|
|
50
|
-
let h = 0x811c9dc5;
|
|
51
|
-
for (const byte of utf8.encode(url)) {
|
|
52
|
-
h ^= byte;
|
|
53
|
-
h = Math.imul(h, 0x01000193);
|
|
54
|
-
}
|
|
55
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
56
|
-
}
|
|
57
44
|
/**
|
|
58
45
|
* The bytes that open a hyperlink to `url`.
|
|
59
46
|
*
|
|
@@ -64,7 +51,8 @@ function linkId(url) {
|
|
|
64
51
|
*/
|
|
65
52
|
export function osc8Open(url) {
|
|
66
53
|
const clean = stripOscTerminators(url);
|
|
67
|
-
|
|
54
|
+
// Hex digits are a legal `id=` value: no `:` or `;`.
|
|
55
|
+
return `\x1b]8;id=${fnv1a(clean)};${clean}\x1b\\`;
|
|
68
56
|
}
|
|
69
57
|
/** The bytes that close the current hyperlink. */
|
|
70
58
|
export const OSC8_CLOSE = "\x1b]8;;\x1b\\";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Painting frames over one another on a terminal: the bytes that take it,
|
|
3
|
+
* paint each frame over the last one in place, and hand it back — and, on an
|
|
4
|
+
* output that is not a terminal, the one frame that is left when it is handed
|
|
5
|
+
* back.
|
|
6
|
+
*
|
|
7
|
+
* [LAW:one-source-of-truth] Where the cursor rests between frames, and how
|
|
8
|
+
* many rows the next paint goes back over, is one fact, and a `SurfacePainter`
|
|
9
|
+
* is its only owner: `App` holds one, and so does `Live` on a terminal, so
|
|
10
|
+
* they cannot disagree about it. They once did: `Live` wrote a newline after its last row, and on a
|
|
11
|
+
* frame as tall as the terminal that newline scrolled the screen, so every
|
|
12
|
+
* refresh left the frame's top row behind in scrollback. The cursor rests on
|
|
13
|
+
* the last row, and the newline that ends it is written once, when the frame
|
|
14
|
+
* is handed back.
|
|
15
|
+
*/
|
|
16
|
+
import type { Destination } from "./color.js";
|
|
17
|
+
import type { Height } from "./protocol.js";
|
|
18
|
+
import { Segment } from "./segment.js";
|
|
19
|
+
/**
|
|
20
|
+
* Where a frame is painted. `alternate` is the whole terminal, in the
|
|
21
|
+
* alternate screen buffer, so the rows printed before it are there again when
|
|
22
|
+
* it is handed back. `inline` starts at the cursor's line, as tall as the
|
|
23
|
+
* frame, and stays on the terminal when it is handed back.
|
|
24
|
+
*/
|
|
25
|
+
export type Surface = "alternate" | "inline";
|
|
26
|
+
/** The terminal a frame is painted on, in cells. */
|
|
27
|
+
export interface Screen {
|
|
28
|
+
readonly rows: number;
|
|
29
|
+
readonly cols: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A frame not yet drawn: the painter calls it when it is about to put the
|
|
33
|
+
* frame on the output, so one that never shows a frame never pays to draw it.
|
|
34
|
+
*/
|
|
35
|
+
export type FrameSource = () => Segment[][];
|
|
36
|
+
/**
|
|
37
|
+
* What paints a run of frames on an output, from `take` to `handBack`.
|
|
38
|
+
*/
|
|
39
|
+
export interface Painter {
|
|
40
|
+
/** The budget a frame renders under, on a screen `screenRows` tall. */
|
|
41
|
+
height(screenRows: number): Height;
|
|
42
|
+
/** Take the output. */
|
|
43
|
+
take(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Paint `frame` over the last frame. `frame` is already shaped to its
|
|
46
|
+
* height by whoever set its budget. An empty frame erases the last one.
|
|
47
|
+
*/
|
|
48
|
+
paint(frame: FrameSource, screen: Screen, destination: Destination): void;
|
|
49
|
+
/**
|
|
50
|
+
* The bytes that write `text` while `frame` is the frame standing on the
|
|
51
|
+
* output, for the caller to write in one piece.
|
|
52
|
+
*/
|
|
53
|
+
around(text: string, frame: FrameSource, screen: Screen, destination: Destination): string;
|
|
54
|
+
/** Hand the output back. The next `take` starts from wherever it then is. */
|
|
55
|
+
handBack(): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Paints frames on one surface of a terminal. It holds the last frame's own
|
|
59
|
+
* rows on the terminal — what the next paint goes back over — from `take` to
|
|
60
|
+
* `handBack`.
|
|
61
|
+
*/
|
|
62
|
+
export declare class SurfacePainter implements Painter {
|
|
63
|
+
private readonly write;
|
|
64
|
+
private readonly geometry;
|
|
65
|
+
private rows;
|
|
66
|
+
constructor(surface: Surface, write: (bytes: string) => void);
|
|
67
|
+
height(screenRows: number): Height;
|
|
68
|
+
/** Take the terminal: the surface entered, the cursor hidden. */
|
|
69
|
+
take(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Paint `frame` over the last frame, leaving the cursor on its last row,
|
|
72
|
+
* and return it as painted. No row is wider than the screen, since one that
|
|
73
|
+
* soft-wrapped would push every row below it down a row the next paint does
|
|
74
|
+
* not go back over.
|
|
75
|
+
*/
|
|
76
|
+
paint(frame: FrameSource, screen: Screen, destination: Destination): Segment[][];
|
|
77
|
+
/**
|
|
78
|
+
* The frame erased, `text` written, and `frame` painted again on the line
|
|
79
|
+
* under it — in one string, so no terminal ever shows the frame gone. The
|
|
80
|
+
* frame fills whole lines, so text that stops mid-line is ended before it.
|
|
81
|
+
*/
|
|
82
|
+
around(text: string, frame: FrameSource, screen: Screen, destination: Destination): string;
|
|
83
|
+
private over;
|
|
84
|
+
/**
|
|
85
|
+
* Hand the terminal back: style reset, then the cursor shown on the
|
|
86
|
+
* program's next line, the surface left.
|
|
87
|
+
*/
|
|
88
|
+
handBack(): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Paints on an output that is not a terminal — a file, a pipe, a CI log — as
|
|
92
|
+
* Rich's `Live` does: nothing while the frames change, since every one would
|
|
93
|
+
* stay there for good with the escape sequences that move over it as literal
|
|
94
|
+
* bytes, and the last frame once, printed as plain lines through `print`, when
|
|
95
|
+
* the output is handed back. What is written around the frame goes out as it
|
|
96
|
+
* was given. No frame is drawn but the one handed back.
|
|
97
|
+
*/
|
|
98
|
+
export declare class FinalFramePainter implements Painter {
|
|
99
|
+
private readonly print;
|
|
100
|
+
private last;
|
|
101
|
+
constructor(print: (frame: Segment[][]) => void);
|
|
102
|
+
height(screenRows: number): Height;
|
|
103
|
+
take(): void;
|
|
104
|
+
paint(frame: FrameSource): void;
|
|
105
|
+
around(text: string): string;
|
|
106
|
+
handBack(): void;
|
|
107
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Painting frames over one another on a terminal: the bytes that take it,
|
|
3
|
+
* paint each frame over the last one in place, and hand it back — and, on an
|
|
4
|
+
* output that is not a terminal, the one frame that is left when it is handed
|
|
5
|
+
* back.
|
|
6
|
+
*
|
|
7
|
+
* [LAW:one-source-of-truth] Where the cursor rests between frames, and how
|
|
8
|
+
* many rows the next paint goes back over, is one fact, and a `SurfacePainter`
|
|
9
|
+
* is its only owner: `App` holds one, and so does `Live` on a terminal, so
|
|
10
|
+
* they cannot disagree about it. They once did: `Live` wrote a newline after its last row, and on a
|
|
11
|
+
* frame as tall as the terminal that newline scrolled the screen, so every
|
|
12
|
+
* refresh left the frame's top row behind in scrollback. The cursor rests on
|
|
13
|
+
* the last row, and the newline that ends it is written once, when the frame
|
|
14
|
+
* is handed back.
|
|
15
|
+
*/
|
|
16
|
+
import { segmentsToString } from "./render.js";
|
|
17
|
+
import { Segment } from "./segment.js";
|
|
18
|
+
const GEOMETRY = {
|
|
19
|
+
alternate: {
|
|
20
|
+
exact: true,
|
|
21
|
+
enter: "\x1b[?1049h",
|
|
22
|
+
// Leaving the buffer puts back the cursor the program had.
|
|
23
|
+
leave: "\x1b[?1049l",
|
|
24
|
+
home: () => "\x1b[H",
|
|
25
|
+
// The frame is every row of the screen, so there is nothing left under it
|
|
26
|
+
// — and after the terminal shrinks, painting the old count would scroll.
|
|
27
|
+
painted: (frameRows) => frameRows,
|
|
28
|
+
below: () => "",
|
|
29
|
+
},
|
|
30
|
+
inline: {
|
|
31
|
+
exact: false,
|
|
32
|
+
enter: "",
|
|
33
|
+
leave: "",
|
|
34
|
+
// After N rows written with N-1 newlines between them, the cursor is on
|
|
35
|
+
// the last. `ESC[0A` still moves a row on some terminals, so a one-row
|
|
36
|
+
// frame has no move at all.
|
|
37
|
+
home: (rows) => (rows > 1 ? `\x1b[${rows - 1}A` : ""),
|
|
38
|
+
// A shorter frame blanks the rows the last one left below it, as far as
|
|
39
|
+
// the screen still reaches after a shrink.
|
|
40
|
+
painted: (frameRows, lastRows, screenRows) => Math.max(frameRows, Math.min(lastRows, screenRows)),
|
|
41
|
+
// Where the program's next line belongs.
|
|
42
|
+
below: (rows) => (rows > 0 ? "\n" : ""),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
// Each row starts at its line's first cell and is erased as it is reached,
|
|
46
|
+
// rather than the frame cleared first, so no blank screen shows between two
|
|
47
|
+
// frames. The erase leads its row: after a row that fills the width, the
|
|
48
|
+
// cursor sits on its last cell, and an erase there would take it.
|
|
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";
|
|
59
|
+
const HIDE_CURSOR = "\x1b[?25l";
|
|
60
|
+
const SHOW_CURSOR = "\x1b[?25h";
|
|
61
|
+
const RESET_STYLE = "\x1b[0m";
|
|
62
|
+
const fitRows = (frame, screen) => frame.map((line) => Segment.adjustLineLength(line, screen.cols, undefined, false));
|
|
63
|
+
/**
|
|
64
|
+
* Paints frames on one surface of a terminal. It holds the last frame's own
|
|
65
|
+
* rows on the terminal — what the next paint goes back over — from `take` to
|
|
66
|
+
* `handBack`.
|
|
67
|
+
*/
|
|
68
|
+
export class SurfacePainter {
|
|
69
|
+
write;
|
|
70
|
+
geometry;
|
|
71
|
+
rows = 0;
|
|
72
|
+
constructor(surface, write) {
|
|
73
|
+
this.write = write;
|
|
74
|
+
this.geometry = GEOMETRY[surface];
|
|
75
|
+
}
|
|
76
|
+
height(screenRows) {
|
|
77
|
+
return { rows: screenRows, exact: this.geometry.exact };
|
|
78
|
+
}
|
|
79
|
+
/** Take the terminal: the surface entered, the cursor hidden. */
|
|
80
|
+
take() {
|
|
81
|
+
this.write(this.geometry.enter + HIDE_CURSOR);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Paint `frame` over the last frame, leaving the cursor on its last row,
|
|
85
|
+
* and return it as painted. No row is wider than the screen, since one that
|
|
86
|
+
* soft-wrapped would push every row below it down a row the next paint does
|
|
87
|
+
* not go back over.
|
|
88
|
+
*/
|
|
89
|
+
paint(frame, screen, destination) {
|
|
90
|
+
const rows = fitRows(frame(), screen);
|
|
91
|
+
this.write(SYNC_START + this.over(rows, screen, destination) + SYNC_END);
|
|
92
|
+
return rows;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The frame erased, `text` written, and `frame` painted again on the line
|
|
96
|
+
* under it — in one string, so no terminal ever shows the frame gone. The
|
|
97
|
+
* frame fills whole lines, so text that stops mid-line is ended before it.
|
|
98
|
+
*/
|
|
99
|
+
around(text, frame, screen, destination) {
|
|
100
|
+
// Drawn before `over` moves the row count, so a frame that throws leaves
|
|
101
|
+
// the painter matching the terminal it left untouched.
|
|
102
|
+
const rows = fitRows(frame(), screen);
|
|
103
|
+
const erase = this.over([], screen, destination);
|
|
104
|
+
const ended = text.endsWith("\n") ? text : `${text}\n`;
|
|
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;
|
|
108
|
+
}
|
|
109
|
+
// The bytes that paint `rows` over the last frame. They go out in one
|
|
110
|
+
// string, so whatever takes the last frame away arrives with the new one.
|
|
111
|
+
over(rows, screen, destination) {
|
|
112
|
+
const painted = this.geometry.painted(rows.length, this.rows, screen.rows);
|
|
113
|
+
const body = Array.from({ length: painted }, (_, row) => ROW_START + segmentsToString(rows[row] ?? [], destination)).join("\n");
|
|
114
|
+
// Rows blanked below the frame are not the frame's: the cursor goes back
|
|
115
|
+
// up to its last row, so the next frame and the program's next line start
|
|
116
|
+
// from the frame's own height.
|
|
117
|
+
const blanked = painted - Math.max(rows.length, 1);
|
|
118
|
+
const back = blanked > 0 ? `\x1b[${blanked}A` : "";
|
|
119
|
+
const bytes = this.geometry.home(this.rows) + body + back;
|
|
120
|
+
this.rows = rows.length;
|
|
121
|
+
return bytes;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Hand the terminal back: style reset, then the cursor shown on the
|
|
125
|
+
* program's next line, the surface left.
|
|
126
|
+
*/
|
|
127
|
+
handBack() {
|
|
128
|
+
this.write(RESET_STYLE + SHOW_CURSOR + this.geometry.below(this.rows) + this.geometry.leave);
|
|
129
|
+
this.rows = 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Paints on an output that is not a terminal — a file, a pipe, a CI log — as
|
|
134
|
+
* Rich's `Live` does: nothing while the frames change, since every one would
|
|
135
|
+
* stay there for good with the escape sequences that move over it as literal
|
|
136
|
+
* bytes, and the last frame once, printed as plain lines through `print`, when
|
|
137
|
+
* the output is handed back. What is written around the frame goes out as it
|
|
138
|
+
* was given. No frame is drawn but the one handed back.
|
|
139
|
+
*/
|
|
140
|
+
export class FinalFramePainter {
|
|
141
|
+
print;
|
|
142
|
+
last = () => [];
|
|
143
|
+
constructor(print) {
|
|
144
|
+
this.print = print;
|
|
145
|
+
}
|
|
146
|
+
height(screenRows) {
|
|
147
|
+
return { rows: screenRows, exact: false };
|
|
148
|
+
}
|
|
149
|
+
take() { }
|
|
150
|
+
paint(frame) {
|
|
151
|
+
this.last = frame;
|
|
152
|
+
}
|
|
153
|
+
around(text) {
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
handBack() {
|
|
157
|
+
const frame = this.last;
|
|
158
|
+
this.last = () => [];
|
|
159
|
+
this.print(frame());
|
|
160
|
+
}
|
|
161
|
+
}
|
package/dist/core/place.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* interface. [LAW:one-source-of-truth]
|
|
19
19
|
*/
|
|
20
20
|
import { Segment } from "./segment.js";
|
|
21
|
-
import {
|
|
21
|
+
import type { Renderable, RenderOptions } from "./protocol.js";
|
|
22
22
|
export type Alignment = "left" | "center" | "right";
|
|
23
23
|
/**
|
|
24
24
|
* `renderable`'s lines, the block they form placed by `align` in
|
package/dist/core/place.js
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { Segment } from "./segment.js";
|
|
21
21
|
import { Measurement } from "./measure.js";
|
|
22
|
-
import { isMeasurable } from "./protocol.js";
|
|
23
22
|
// The share of the spare cells that goes on the left; the rest go on the right.
|
|
24
23
|
// Right-alignment pads no right side at all, which is the reference's choice
|
|
25
24
|
// and not a zero: a line that ends at the edge carries no trailing blanks.
|
|
@@ -36,11 +35,10 @@ const PADS = {
|
|
|
36
35
|
* its full width, for whatever holds it to crop or keep.
|
|
37
36
|
*/
|
|
38
37
|
export function placeBlock(renderable, align, options) {
|
|
39
|
-
// A renderable that cannot say how wide it wants to be is offered all of it
|
|
40
|
-
// and the block is then only as wide as
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
: options.maxWidth;
|
|
38
|
+
// A renderable that cannot say how wide it wants to be is offered all of it
|
|
39
|
+
// (`Measurement.get` answers for it), and the block is then only as wide as
|
|
40
|
+
// what it actually drew.
|
|
41
|
+
const width = Measurement.get(options, renderable).maximum;
|
|
44
42
|
const drawn = Segment.splitLines(renderable.render({ ...options, maxWidth: width, height: undefined }));
|
|
45
43
|
const [blockWidth, height] = Segment.getShape(drawn);
|
|
46
44
|
const [left, right] = PADS[align](Math.max(0, options.maxWidth - blockWidth));
|