@promptctl/rich-js 0.27.0 → 0.29.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 +7 -6
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +65 -46
- package/dist/core/animation.js +91 -67
- package/dist/core/ansi.d.ts +19 -12
- package/dist/core/ansi.js +114 -36
- package/dist/core/bidi-data.d.ts +14 -0
- package/dist/core/bidi-data.js +385 -0
- package/dist/core/bidi.d.ts +89 -0
- package/dist/core/bidi.js +615 -0
- package/dist/core/box.d.ts +9 -1
- package/dist/core/box.js +11 -0
- package/dist/core/cell-widths.d.ts +3 -2
- package/dist/core/cell-widths.js +93 -93
- package/dist/core/cells.d.ts +20 -0
- package/dist/core/cells.js +94 -6
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/color.d.ts +56 -57
- package/dist/core/color.js +79 -81
- package/dist/core/console.d.ts +24 -7
- package/dist/core/console.js +27 -8
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/easing.d.ts +40 -40
- package/dist/core/easing.js +40 -40
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +5 -4
- package/dist/core/export-lines.d.ts +13 -11
- package/dist/core/export-lines.js +19 -15
- package/dist/core/export-svg.d.ts +3 -3
- package/dist/core/export-svg.js +10 -9
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +5 -4
- package/dist/core/memo.d.ts +1 -1
- package/dist/core/memo.js +1 -1
- package/dist/core/oklch.d.ts +14 -14
- package/dist/core/oklch.js +10 -10
- package/dist/core/orient.d.ts +68 -0
- package/dist/core/orient.js +224 -0
- package/dist/core/osc8.d.ts +12 -5
- package/dist/core/osc8.js +14 -7
- package/dist/core/paint.d.ts +9 -9
- package/dist/core/paint.js +3 -3
- package/dist/core/pretty.d.ts +1 -1
- package/dist/core/pretty.js +23 -19
- package/dist/core/protocol.d.ts +47 -26
- package/dist/core/protocol.js +31 -23
- package/dist/core/render.d.ts +19 -14
- package/dist/core/render.js +30 -17
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +12 -4
- package/dist/core/segment.js +16 -6
- package/dist/core/strip.d.ts +83 -83
- package/dist/core/strip.js +70 -70
- package/dist/core/style.d.ts +46 -18
- package/dist/core/style.js +92 -56
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +12 -2
- package/dist/core/text.js +136 -90
- package/dist/core/timeline.d.ts +5 -5
- package/dist/core/timeline.js +11 -11
- package/dist/core/tween.d.ts +6 -6
- package/dist/core/tween.js +10 -10
- package/dist/core/view.d.ts +5 -5
- package/dist/core/view.js +15 -14
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +50 -9
- package/dist/host/app.js +120 -15
- package/dist/host/host-environment.d.ts +2 -2
- package/dist/host/host-environment.js +2 -2
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +42 -9
- package/dist/host/input.js +91 -7
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +4 -3
- package/dist/host/terminal-host.js +1 -1
- package/dist/index.d.ts +17 -16
- package/dist/index.js +11 -11
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.js +1 -1
- package/dist/node/terminal-host.d.ts +1 -1
- package/dist/node/traceback.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +6 -8
- package/dist/renderables/effect.d.ts +88 -87
- package/dist/renderables/effect.js +124 -114
- package/dist/renderables/effects.d.ts +105 -76
- package/dist/renderables/effects.js +309 -176
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +2 -2
- package/dist/renderables/flexStrip.d.ts +7 -7
- package/dist/renderables/flexStrip.js +15 -15
- package/dist/renderables/frames.d.ts +7 -16
- package/dist/renderables/frames.js +12 -27
- package/dist/renderables/glyph-effect.d.ts +22 -23
- package/dist/renderables/glyph-effect.js +43 -41
- package/dist/renderables/glyph-effects.d.ts +33 -14
- package/dist/renderables/glyph-effects.js +90 -22
- package/dist/renderables/html-entities.d.ts +1 -1
- package/dist/renderables/html-entities.js +1 -1
- package/dist/renderables/lifecycle.d.ts +8 -10
- package/dist/renderables/lifecycle.js +17 -18
- package/dist/renderables/live.d.ts +2 -2
- package/dist/renderables/live.js +7 -7
- package/dist/renderables/markdown-parse.js +3 -3
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +7 -5
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +1 -1
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +21 -18
- package/dist/renderables/prompt.d.ts +1 -1
- package/dist/renderables/scroll.d.ts +1 -1
- package/dist/renderables/scroll.js +3 -3
- package/dist/renderables/spinner.d.ts +4 -4
- package/dist/renderables/spinner.js +10 -10
- package/dist/renderables/syntax.d.ts +2 -2
- package/dist/renderables/syntax.js +9 -4
- package/dist/renderables/table.js +5 -4
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +1 -1
- package/dist/renderables/tree.js +3 -3
- package/dist/template-bindings/animation-funcs.d.ts +60 -29
- package/dist/template-bindings/animation-funcs.js +126 -43
- package/dist/template-bindings/color-funcs.d.ts +14 -14
- package/dist/template-bindings/color-funcs.js +25 -25
- package/dist/template-bindings/index.d.ts +16 -14
- package/dist/template-bindings/index.js +17 -14
- package/dist/template-bindings/palette-funcs.d.ts +2 -2
- package/dist/template-bindings/palette-funcs.js +4 -4
- package/dist/template-bindings/style-funcs.d.ts +5 -5
- package/dist/template-bindings/style-funcs.js +16 -16
- package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
- package/dist/themes/{transpose.js → adjust.js} +28 -28
- package/dist/themes/buildPalette.d.ts +6 -5
- package/dist/themes/buildPalette.js +22 -22
- package/dist/themes/colorMath.d.ts +16 -16
- package/dist/themes/colorMath.js +32 -32
- package/dist/themes/data/ansi-tables.d.ts +2 -2
- package/dist/themes/data/ansi-tables.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/data/types.d.ts +4 -4
- package/dist/themes/data/types.js +2 -2
- package/dist/themes/palette.d.ts +12 -12
- package/dist/themes/palette.js +16 -16
- package/dist/themes/ramp.d.ts +17 -17
- package/dist/themes/ramp.js +12 -12
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +6 -8
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +3 -3
- package/dist/widgets/button.js +8 -8
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +3 -3
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/event-router.d.ts +6 -6
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/focus-manager.d.ts +3 -3
- package/dist/widgets/focus-manager.js +3 -3
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/ink.d.ts +3 -3
- package/dist/widgets/ink.js +4 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/static-item.d.ts +2 -2
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +5 -5
- package/dist/widgets/themed-widget.js +4 -4
- package/dist/widgets/toggle.d.ts +4 -4
- package/dist/widgets/toggle.js +6 -6
- package/dist/widgets/types.d.ts +6 -6
- package/dist/widgets/types.js +2 -2
- package/dist/widgets/widget-app.d.ts +10 -10
- package/dist/widgets/widget-app.js +8 -8
- package/dist/widgets/widget-base.d.ts +2 -2
- package/package.json +5 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Effected — a renderable whose finished cells' looks —
|
|
2
|
+
* Effected — a renderable whose finished cells' looks — colors and text
|
|
3
3
|
* attributes — pass through an effect at a moment `t`.
|
|
4
4
|
*
|
|
5
5
|
* An effect is, for what an element draws in a frame, an `Animation`
|
|
@@ -11,26 +11,27 @@
|
|
|
11
11
|
* function `at(t)` returns, but for those a wrapper keeps back — `confined`
|
|
12
12
|
* hands over only the cells its owners drew, so an effect never counts on
|
|
13
13
|
* seeing them all — and for a strip's join cells painted from the items beside
|
|
14
|
-
* them, which are no one's to paint: a seam's
|
|
14
|
+
* them, which are no one's to paint: a seam's colors are its neighbours'
|
|
15
15
|
* edges, so once the effect has painted every other cell, each such cell is
|
|
16
16
|
* resolved again from the cells beside it as the effect left them
|
|
17
17
|
* (`JoinedCell`, `../core/strip.ts`), the way the joiner drew it. Shimmer,
|
|
18
18
|
* pulse, fade, dissolve and a blinking attribute are all this one shape,
|
|
19
19
|
* differing by what the function does with its inputs
|
|
20
|
-
* [LAW:one-type-per-behavior]; two effects stack
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* [LAW:one-type-per-behavior]; two effects stack as layers of one effect
|
|
21
|
+
* (`layered`, `./effects.ts`), each moving what the last left and recognising
|
|
22
|
+
* a cell by what the element drew (`EffectCell.pair`), or by nesting one
|
|
23
|
+
* wrapper in another, which measures the inner one's output instead.
|
|
23
24
|
*
|
|
24
|
-
* A cell's look is one value, its
|
|
25
|
-
* (`CellLook`), rather than an attribute stage beside the
|
|
26
|
-
* everything that takes an `Effect` — `confined`, `
|
|
25
|
+
* A cell's look is one value, its colors and its attributes together
|
|
26
|
+
* (`CellLook`), rather than an attribute stage beside the color one, so
|
|
27
|
+
* everything that takes an `Effect` — `confined`, `sampleAndHold`,
|
|
27
28
|
* `Animation.all`, nesting over a glyph effect — takes one that moves
|
|
28
29
|
* attributes with no second copy of itself. `reverse` is the one attribute
|
|
29
30
|
* a look leaves out (`CellAttributes`): it decides which of a style's slots
|
|
30
|
-
* a
|
|
31
|
+
* a color on screen is in, and a swap of the two is already a color move.
|
|
31
32
|
*
|
|
32
33
|
* What the element draws (`Drawn`) — the cells the effect will be handed,
|
|
33
|
-
* and the
|
|
34
|
+
* and the colors of its words — is measured here, in the render, off the
|
|
34
35
|
* same reading of each segment its cells are drawn from, and handed to the
|
|
35
36
|
* effect once a frame. It is what an effect keeping words legible is settled
|
|
36
37
|
* against (`onColors`, `./effects.ts`), so no caller derives it a second time
|
|
@@ -50,18 +51,18 @@
|
|
|
50
51
|
* to the effect and is never cut, and a run cut out of a segment keeps that
|
|
51
52
|
* segment's link and meta and carries its anchor shifted to the run's first
|
|
52
53
|
* cell (`Style.shiftedBy`), so hit testing still finds the owner.
|
|
53
|
-
* - A cell whose
|
|
54
|
+
* - A cell whose colors come back unchanged keeps its original `ColorSpec`,
|
|
54
55
|
* one whose attributes come back as its style set them keeps them, and a
|
|
55
56
|
* segment whose every cell is unchanged is yielded with its look as
|
|
56
57
|
* it was, so the identity effect is byte-identical; what an `Effected` gains is when
|
|
57
58
|
* the frame next changes (`Style.hold`), which writes no byte.
|
|
58
59
|
* Neighbouring cells the output depth
|
|
59
60
|
* writes alike stay one segment: a segment is cut only where the written
|
|
60
|
-
*
|
|
61
|
+
* colors or the attributes change, so two colors that land on one index
|
|
61
62
|
* are one run.
|
|
62
|
-
* - New
|
|
63
|
+
* - New colors are built with `ColorSpec.fromRgba`, never parsed from a
|
|
63
64
|
* string, so no frame adds an entry to a parse cache.
|
|
64
|
-
* - With no
|
|
65
|
+
* - With no color output (`colorSystem: null`) no style is written at all,
|
|
65
66
|
* attributes included, so there is nothing for an effect to change, and
|
|
66
67
|
* the child's segments pass through untouched.
|
|
67
68
|
*
|
|
@@ -69,60 +70,67 @@
|
|
|
69
70
|
* what the child drew, so a sweep crosses the element, not the screen.
|
|
70
71
|
*/
|
|
71
72
|
import { drawnBy, shiftAnchor } from "../core/anchor.js";
|
|
72
|
-
import { Animation,
|
|
73
|
+
import { Animation, validityOf, placedRender, shownAt } from "../core/animation.js";
|
|
73
74
|
import { cellLen, graphemes } from "../core/cells.js";
|
|
74
|
-
import { ColorDepth, ColorSpec, SURFACE_BLACK } from "../core/color.js";
|
|
75
|
+
import { ColorDepth, ColorSpec, resolveTerminal, SURFACE_BLACK } from "../core/color.js";
|
|
75
76
|
import { fnv1a } from "../core/fnv1a.js";
|
|
76
77
|
import { Measurement } from "../core/measure.js";
|
|
77
78
|
import { render } from "../core/protocol.js";
|
|
78
79
|
import { Segment } from "../core/segment.js";
|
|
79
80
|
import { JOIN, joinedCell } from "../core/strip.js";
|
|
80
|
-
import { ATTRIBUTE_NAMES, Style } from "../core/style.js";
|
|
81
|
+
import { ATTRIBUTE_NAMES, NEVER_CHANGES, Style } from "../core/style.js";
|
|
81
82
|
import { finiteTime } from "../core/easing.js";
|
|
82
|
-
/**
|
|
83
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The hold an effect drawn at `t` tells: its animation's there, or held for
|
|
85
|
+
* good over no cell, since it changes how cells look and never which exist.
|
|
86
|
+
* What drew none says for itself when it will (a lifecycle not yet entered).
|
|
87
|
+
*/
|
|
88
|
+
export function validityOver(anyCell, shown, t) {
|
|
89
|
+
return anyCell ? validityOf(shown, t) : NEVER_CHANGES;
|
|
90
|
+
}
|
|
84
91
|
/**
|
|
85
92
|
* An `Effected` at `t`, or an element through an effect at a time an
|
|
86
93
|
* animation's `at` was handed — a lifecycle's time since it entered: `renderable`
|
|
87
|
-
* with its cells' looks passed through `effect` at `t
|
|
88
|
-
* when that changes, since
|
|
89
|
-
*
|
|
94
|
+
* with its cells' looks passed through `effect` at `t`. Its cells say nothing
|
|
95
|
+
* of when that changes, since `t` is not the frame's clock: it tells the hold
|
|
96
|
+
* it drew in (`RenderOptions.onReads`), and whatever placed the animation
|
|
97
|
+
* carries that onto the frame's and stamps it. [LAW:one-type-per-behavior]
|
|
90
98
|
*/
|
|
91
99
|
export class Recolored {
|
|
92
100
|
renderable;
|
|
93
101
|
effect;
|
|
94
|
-
|
|
102
|
+
_t;
|
|
95
103
|
key;
|
|
96
|
-
theme;
|
|
97
104
|
constructor(renderable, effect, options) {
|
|
98
105
|
this.renderable = renderable;
|
|
99
106
|
this.effect = effect;
|
|
100
|
-
this.
|
|
107
|
+
this._t = finiteTime("Effected", options.t);
|
|
101
108
|
this.key = options.key;
|
|
102
|
-
this.theme = options.theme;
|
|
103
109
|
}
|
|
104
110
|
render(options) {
|
|
105
|
-
return this.moved(options).segments;
|
|
106
|
-
}
|
|
107
|
-
/** The frame the effect draws at `t`, and the hold it says that is in. */
|
|
108
|
-
moved(options) {
|
|
109
111
|
const segments = render(this.renderable, options);
|
|
110
|
-
// With no
|
|
112
|
+
// With no color output nothing the effect does is written, so the child
|
|
111
113
|
// is what is shown, and it changes when the child says it does.
|
|
112
|
-
if (options.colorSystem === null)
|
|
113
|
-
|
|
114
|
+
if (options.colorSystem === null) {
|
|
115
|
+
options.onReads?.(this._t, this.key, NEVER_CHANGES);
|
|
116
|
+
return segments;
|
|
117
|
+
}
|
|
114
118
|
// No depth named is no downgrade, as `drawnColors` reads it.
|
|
115
119
|
const depth = options.colorSystem ?? ColorDepth.TRUECOLOR;
|
|
120
|
+
// [LAW:one-source-of-truth] Every color below resolves on the frame's
|
|
121
|
+
// terminal, so two effects in a frame cannot disagree.
|
|
122
|
+
const terminal = resolveTerminal(options.terminal, depth);
|
|
116
123
|
// [LAW:one-source-of-truth] The frame is measured before any cell is
|
|
117
124
|
// handed to the effect, off the same looks its cells are drawn from, and
|
|
118
125
|
// the one measure is what the effect's animation is made from.
|
|
119
|
-
const looked = looks(segments, depth,
|
|
126
|
+
const looked = looks(segments, depth, terminal);
|
|
120
127
|
const walked = walk(looked);
|
|
121
|
-
const
|
|
122
|
-
const shown = shownAt(
|
|
123
|
-
|
|
128
|
+
const drawn = measured(looked, () => walked, depth, terminal);
|
|
129
|
+
const shown = shownAt(this.effect(drawn), this._t, options);
|
|
130
|
+
options.onReads?.(this._t, this.key, validityOver(walked.some(({ glyphs }) => glyphs.some(({ cell }) => cell !== undefined)), shown, this._t));
|
|
131
|
+
return this._recolored(walked, shown.value, drawn);
|
|
124
132
|
}
|
|
125
|
-
*_recolored(walked, frame,
|
|
133
|
+
*_recolored(walked, frame, drawn) {
|
|
126
134
|
// [LAW:one-source-of-truth] Every cell but a join cell that follows its
|
|
127
135
|
// neighbours is painted first, so that cell is resolved from the cells
|
|
128
136
|
// beside it as the effect left them, wherever in the frame they fall.
|
|
@@ -133,8 +141,7 @@ export class Recolored {
|
|
|
133
141
|
if (joined?.follows === true)
|
|
134
142
|
return { joined };
|
|
135
143
|
const from = { fg: look.pair.fg, bg: look.pair.bg, attributes: look.attributes };
|
|
136
|
-
|
|
137
|
-
return { painted: glyphs.map(({ cell }) => (cell === undefined ? from : returned(frame(from, { ...cell, seed: cellSeed(this.key, cell.row, cell.col), terminal, reverse })))) };
|
|
144
|
+
return { painted: glyphs.map(({ cell }) => (cell === undefined ? from : returned(frame(from, { ...cell, seed: cellSeed(this.key, cell.row, cell.col), pair: look.pair })))) };
|
|
138
145
|
});
|
|
139
146
|
const beside = edgesBeside(walked, drawings);
|
|
140
147
|
for (const [i, { segment, look, glyphs }] of walked.entries()) {
|
|
@@ -142,16 +149,16 @@ export class Recolored {
|
|
|
142
149
|
if (look === undefined || drawing === undefined)
|
|
143
150
|
yield segment;
|
|
144
151
|
else if ("joined" in drawing)
|
|
145
|
-
yield this._rejoined(segment, look, drawing.joined, glyphs, beside, frame,
|
|
152
|
+
yield this._rejoined(segment, look, drawing.joined, glyphs, beside, frame, drawn);
|
|
146
153
|
else
|
|
147
|
-
yield* this._repainted(segment, look, glyphs, drawing.painted,
|
|
154
|
+
yield* this._repainted(segment, look, glyphs, drawing.painted, drawn);
|
|
148
155
|
}
|
|
149
156
|
}
|
|
150
157
|
/** `segment`'s cells drawn in the looks the effect painted them. */
|
|
151
|
-
*_repainted(segment, look, glyphs, moved,
|
|
158
|
+
*_repainted(segment, look, glyphs, moved, drawn) {
|
|
152
159
|
const { style: base, wire, slots, pair, attributes } = look;
|
|
153
160
|
// [LAW:one-source-of-truth] A run is cells the wire writes alike: each
|
|
154
|
-
// cell is drawn at the output depth first and merged on the
|
|
161
|
+
// cell is drawn at the output depth first and merged on the colors that
|
|
155
162
|
// writes and on its attributes, so two truecolors landing on one index
|
|
156
163
|
// stay one run. A cell the effect gave its neighbour's look is drawn as
|
|
157
164
|
// that neighbour was.
|
|
@@ -161,7 +168,7 @@ export class Recolored {
|
|
|
161
168
|
// Back from the screen to the style's slots: the same swap undoes itself.
|
|
162
169
|
const colors = onScreen(now, pair.reverse);
|
|
163
170
|
const last = runs[runs.length - 1];
|
|
164
|
-
const writes = last !== undefined && sameColors(last.colors, colors) ? last.drawn : this.respec(wire, slots, colors,
|
|
171
|
+
const writes = last !== undefined && sameColors(last.colors, colors) ? last.drawn : this.respec(wire, slots, colors, drawn);
|
|
165
172
|
if (last !== undefined && writesAlike(last.drawn, writes) && sameAttributes(last.attributes, now.attributes)) {
|
|
166
173
|
last.text += glyph;
|
|
167
174
|
last.cells += cellLen(glyph);
|
|
@@ -191,30 +198,30 @@ export class Recolored {
|
|
|
191
198
|
* nothing of keeps the edge the cell was drawn from, so with neither moved
|
|
192
199
|
* the cell is as it was drawn.
|
|
193
200
|
*/
|
|
194
|
-
_rejoined(segment, look, joined, glyphs, beside, frame,
|
|
201
|
+
_rejoined(segment, look, joined, glyphs, beside, frame, measure) {
|
|
202
|
+
const { depth, terminal } = measure;
|
|
195
203
|
const cells = glyphs.flatMap(({ cell }) => (cell === undefined ? [] : [cell]));
|
|
196
204
|
// A join cell that draws no cell shows nothing an effect could move.
|
|
197
205
|
const [at] = cells;
|
|
198
206
|
if (at === undefined)
|
|
199
207
|
return segment;
|
|
200
|
-
const { theme } = this;
|
|
201
208
|
// [LAW:one-source-of-truth] A moved edge is the spec the depth writes its
|
|
202
209
|
// slot in, the spec the joiner reads of an item drawn so: a palette slot
|
|
203
210
|
// stays that slot, which the lift tells apart by number, never an RGB read
|
|
204
211
|
// back through a palette the writer did not use. Only the terminal's own
|
|
205
|
-
//
|
|
212
|
+
// color names none, and the terminal the effect draws on says what it is.
|
|
206
213
|
// Each such spec is kept, so the writer below knows it by identity: an
|
|
207
|
-
// RGB a blend or another
|
|
214
|
+
// RGB a blend or another color lands on is not the terminal's own.
|
|
208
215
|
const terminals = new Set();
|
|
209
216
|
const edgeOf = (spec, foreground) => {
|
|
210
217
|
if (!spec?.isDefault)
|
|
211
218
|
return spec;
|
|
212
|
-
const own = ColorSpec.fromRgba(spec.getTruecolor(
|
|
219
|
+
const own = ColorSpec.fromRgba(spec.getTruecolor(terminal, foreground));
|
|
213
220
|
terminals.add(own);
|
|
214
221
|
return own;
|
|
215
222
|
};
|
|
216
223
|
const moved = (from, to) => {
|
|
217
|
-
const specs = movedSpecs(from, to, depth,
|
|
224
|
+
const specs = movedSpecs(from, to, depth, terminal);
|
|
218
225
|
return specs.fg === undefined && specs.bg === undefined ? undefined : { color: edgeOf(specs.fg, true), bgcolor: edgeOf(specs.bg, false) };
|
|
219
226
|
};
|
|
220
227
|
// [LAW:one-source-of-truth] A neighbour the frame shows no cell of beside
|
|
@@ -228,9 +235,9 @@ export class Recolored {
|
|
|
228
235
|
// comes here [LAW:no-silent-failure].
|
|
229
236
|
const unseen = (edge) => {
|
|
230
237
|
const wire = edge.drawnColors(depth);
|
|
231
|
-
const slots = { fg: (wire.color ?? DEFAULT).getTruecolor(
|
|
232
|
-
const
|
|
233
|
-
return moved(slots, returned(frame({ ...slots, attributes: cellAttributes(edge) }, { ...at, seed: cellSeed(this.key, at.row, at.col), terminal, reverse: false })));
|
|
238
|
+
const slots = { fg: (wire.color ?? DEFAULT).getTruecolor(terminal, true), bg: (wire.bgcolor ?? DEFAULT).getTruecolor(terminal, false) };
|
|
239
|
+
const own = { fg: wire.color?.isDefault ?? true, bg: wire.bgcolor?.isDefault ?? true };
|
|
240
|
+
return moved(slots, returned(frame({ ...slots, attributes: cellAttributes(edge) }, { ...at, seed: cellSeed(this.key, at.row, at.col), pair: { ...slots, terminal: own, reverse: false } })));
|
|
234
241
|
};
|
|
235
242
|
const moveOf = (side) => {
|
|
236
243
|
const read = joined.reads[side];
|
|
@@ -246,9 +253,9 @@ export class Recolored {
|
|
|
246
253
|
// [LAW:one-source-of-truth] Each slot is written as the depth writes what
|
|
247
254
|
// it resolved to, by the writer the lift measured it with
|
|
248
255
|
// (`Style.drawnColors`), so a lifted ink stands off its ground in the bytes
|
|
249
|
-
// as it did when measured. A slot resolved to the
|
|
256
|
+
// as it did when measured. A slot resolved to the color it was before
|
|
250
257
|
// keeps the spec it was written in, as a painted cell's unmoved slot does.
|
|
251
|
-
// The terminal's own
|
|
258
|
+
// The terminal's own colors, which stand in a moved edge as the spec of
|
|
252
259
|
// the RGB the effect's terminal gives them and reach a slot unchanged or
|
|
253
260
|
// not at all, are written by the writer that wrote them on the item
|
|
254
261
|
// beside, on that terminal, which writes its own ground below truecolor as
|
|
@@ -256,12 +263,12 @@ export class Recolored {
|
|
|
256
263
|
const was = joined.style;
|
|
257
264
|
const drawn = colors.drawnColors(depth);
|
|
258
265
|
const { wire, slots } = look;
|
|
259
|
-
const rounded = this.respec(wire, slots, { fg: colors.color?.getTruecolor(
|
|
266
|
+
const rounded = this.respec(wire, slots, { fg: colors.color?.getTruecolor(terminal, true) ?? slots.fg, bg: colors.bgcolor?.getTruecolor(terminal, false) ?? slots.bg }, measure);
|
|
260
267
|
const written = (now, before, wrote, drew, round) => {
|
|
261
268
|
if (sameSpec(now, before))
|
|
262
269
|
return wrote;
|
|
263
270
|
// [LAW:no-silent-failure] Unreachable: a move only fills an edge in, so
|
|
264
|
-
// a slot that resolved to a
|
|
271
|
+
// a slot that resolved to a color when drawn resolves to one after.
|
|
265
272
|
if (now === undefined)
|
|
266
273
|
throw new Error(`a seam cell resolved to ${before?.name ?? "nothing"} when drawn and to nothing after an effect moved its neighbours`);
|
|
267
274
|
return terminals.has(now) ? round : drew;
|
|
@@ -276,46 +283,43 @@ export class Recolored {
|
|
|
276
283
|
return Measurement.get(options, this.renderable);
|
|
277
284
|
}
|
|
278
285
|
/**
|
|
279
|
-
* The specs that draw a cell's slot
|
|
286
|
+
* The specs that draw a cell's slot colors `to` where the segment drew
|
|
280
287
|
* `from` with `wire`: each moved slot in the spec `movedSpecs` writes it
|
|
281
288
|
* in, the rest as `wire` wrote them.
|
|
282
289
|
*/
|
|
283
|
-
respec(wire, from, to, depth) {
|
|
284
|
-
const moved = movedSpecs(from, to, depth,
|
|
290
|
+
respec(wire, from, to, { depth, terminal }) {
|
|
291
|
+
const moved = movedSpecs(from, to, depth, terminal);
|
|
285
292
|
return { color: moved.fg ?? wire.color, bgcolor: moved.bg ?? wire.bgcolor };
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
/**
|
|
289
296
|
* `renderable` under `effect` over time: an `Animation<Renderable>` whose
|
|
290
297
|
* value at `t` is the element through the effect at `t`, placed wherever a
|
|
291
|
-
* renderable goes.
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* depth (`
|
|
296
|
-
*
|
|
297
|
-
* host sleeps.
|
|
298
|
+
* renderable goes. An `Effect` is made from what the element draws, so with
|
|
299
|
+
* nothing drawn its answer to when it changes is `t`, at every time: early,
|
|
300
|
+
* never false. Placed, on its own or as a part of any combinator, it says
|
|
301
|
+
* exactly: the frame's drawing told the effect's hold at that time and the
|
|
302
|
+
* output's depth (`Reads`), so at a depth that shows no motion a loop says it
|
|
303
|
+
* never changes there and the host sleeps.
|
|
298
304
|
*/
|
|
299
305
|
export class Effected {
|
|
300
306
|
renderable;
|
|
301
307
|
effect;
|
|
302
308
|
key;
|
|
303
|
-
theme;
|
|
304
309
|
constructor(renderable, effect, options) {
|
|
305
310
|
this.renderable = renderable;
|
|
306
311
|
this.effect = effect;
|
|
307
312
|
this.key = options.key;
|
|
308
|
-
this.theme = options.theme;
|
|
309
313
|
}
|
|
310
314
|
/** The element through the effect at `t`, unstamped. */
|
|
311
315
|
at(t) {
|
|
312
|
-
return new Recolored(this.renderable, this.effect, { t, key: this.key
|
|
316
|
+
return new Recolored(this.renderable, this.effect, { t, key: this.key });
|
|
313
317
|
}
|
|
314
|
-
|
|
315
|
-
return t;
|
|
318
|
+
validUntil(t, moves) {
|
|
319
|
+
return moves?.(t, this.key)?.validUntil ?? t;
|
|
316
320
|
}
|
|
317
|
-
|
|
318
|
-
return t;
|
|
321
|
+
validFrom(t, moves) {
|
|
322
|
+
return moves?.(t, this.key)?.validFrom ?? t;
|
|
319
323
|
}
|
|
320
324
|
/** The element under the effect with its motion taken out. */
|
|
321
325
|
reduced() {
|
|
@@ -324,45 +328,44 @@ export class Effected {
|
|
|
324
328
|
}
|
|
325
329
|
/** Drawn at the frame's time, its cells stamped with the effect's exact hold there. */
|
|
326
330
|
render(options) {
|
|
327
|
-
|
|
328
|
-
return stampFrame(segments, hold);
|
|
331
|
+
return placedRender(this, options);
|
|
329
332
|
}
|
|
330
333
|
measure(options) {
|
|
331
334
|
return Measurement.get(options, this.renderable);
|
|
332
335
|
}
|
|
333
336
|
/**
|
|
334
|
-
* The
|
|
335
|
-
* both as the screen shows them, at `depth` on `
|
|
336
|
-
* moving a pair's
|
|
337
|
+
* The colors the screen shows for `pair` once it is drawn moved to `to`,
|
|
338
|
+
* both as the screen shows them, at `depth` on `terminal`: what an effect
|
|
339
|
+
* moving a pair's colors leaves its words reading in.
|
|
337
340
|
* [LAW:one-source-of-truth] Read through `movedSpecs`, the one writer a
|
|
338
341
|
* moved slot is written with.
|
|
339
342
|
*/
|
|
340
|
-
static shows(pair, to, depth,
|
|
343
|
+
static shows(pair, to, depth, terminal) {
|
|
341
344
|
const from = onScreen(pair, pair.reverse);
|
|
342
|
-
const moved = movedSpecs(from, onScreen(to, pair.reverse), depth,
|
|
343
|
-
return onScreen({ fg: moved.fg?.getTruecolor(
|
|
345
|
+
const moved = movedSpecs(from, onScreen(to, pair.reverse), depth, terminal);
|
|
346
|
+
return onScreen({ fg: moved.fg?.getTruecolor(terminal, true) ?? from.fg, bg: moved.bg?.getTruecolor(terminal, false) ?? from.bg }, pair.reverse);
|
|
344
347
|
}
|
|
345
348
|
}
|
|
346
349
|
/**
|
|
347
350
|
* A cell's seed: FNV-1a of the element's key and the cell's position, scaled
|
|
348
|
-
* into [0, 1). [LAW:one-source-of-truth] One rule for a
|
|
351
|
+
* into [0, 1). [LAW:one-source-of-truth] One rule for a color effect's seeds
|
|
349
352
|
* and a glyph effect's, so one element under both twinkles from one seed.
|
|
350
353
|
*/
|
|
351
354
|
export function cellSeed(key, row, col) {
|
|
352
355
|
return Number.parseInt(fnv1a(`${key}:${row}:${col}`), 16) / 0x1_0000_0000;
|
|
353
356
|
}
|
|
354
357
|
const DEFAULT = ColorSpec.default();
|
|
355
|
-
/** Each of `segments` with how it is drawn at `depth` on `
|
|
356
|
-
function looks(segments, depth,
|
|
358
|
+
/** Each of `segments` with how it is drawn at `depth` on `terminal`; a control segment draws nothing. */
|
|
359
|
+
function looks(segments, depth, terminal) {
|
|
357
360
|
return [...segments].map((segment) => {
|
|
358
361
|
if (segment.isControl)
|
|
359
362
|
return { segment, look: undefined };
|
|
360
363
|
const style = segment.style ?? Style.null();
|
|
361
364
|
const wire = style.drawnColors(depth);
|
|
362
|
-
const slots = { fg: (wire.color ?? DEFAULT).getTruecolor(
|
|
365
|
+
const slots = { fg: (wire.color ?? DEFAULT).getTruecolor(terminal, true), bg: (wire.bgcolor ?? DEFAULT).getTruecolor(terminal, false) };
|
|
363
366
|
const reverse = style.reverse === true;
|
|
364
|
-
const
|
|
365
|
-
return { segment, look: { style, wire, slots, pair: { ...onScreen(slots, reverse), terminal, reverse }, attributes: cellAttributes(style), seam: drawnBy(style.anchor, JOIN) } };
|
|
367
|
+
const own = onScreen({ fg: wire.color?.isDefault ?? true, bg: wire.bgcolor?.isDefault ?? true }, reverse);
|
|
368
|
+
return { segment, look: { style, wire, slots, pair: { ...onScreen(slots, reverse), terminal: own, reverse }, attributes: cellAttributes(style), seam: drawnBy(style.anchor, JOIN) } };
|
|
366
369
|
});
|
|
367
370
|
}
|
|
368
371
|
/**
|
|
@@ -371,7 +374,7 @@ function looks(segments, depth, theme) {
|
|
|
371
374
|
* column, and its anchor shifted to that column as `Style.shiftedBy` shifts
|
|
372
375
|
* a run's. A control segment draws nothing and has none.
|
|
373
376
|
* [LAW:one-source-of-truth] The one walk: what `Drawn.cells` lists, what the
|
|
374
|
-
* render hands a
|
|
377
|
+
* render hands a color effect, and what a glyph effect (`./glyph-effect.ts`)
|
|
375
378
|
* is handed are all read off it.
|
|
376
379
|
*/
|
|
377
380
|
export function walkGlyphs(segments) {
|
|
@@ -381,10 +384,11 @@ export function walkGlyphs(segments) {
|
|
|
381
384
|
if (segment.isControl)
|
|
382
385
|
return [];
|
|
383
386
|
const anchor = segment.style?.anchor;
|
|
387
|
+
const boxFrame = segment.style?.boxFrame;
|
|
384
388
|
// Cells into the segment, as `base.shiftedBy` counts them in the render.
|
|
385
389
|
let into = 0;
|
|
386
390
|
return [...graphemes(segment.text)].map((glyph) => {
|
|
387
|
-
const cell = glyph === "\n" ? undefined : { row, col, anchor: anchor && shiftAnchor(anchor, into) };
|
|
391
|
+
const cell = glyph === "\n" ? undefined : { row, col, anchor: anchor && shiftAnchor(anchor, into), boxFrame };
|
|
388
392
|
const width = cellLen(glyph);
|
|
389
393
|
[row, col] = glyph === "\n" ? [row + 1, 0] : [row, col + width];
|
|
390
394
|
into += width;
|
|
@@ -394,7 +398,7 @@ export function walkGlyphs(segments) {
|
|
|
394
398
|
}
|
|
395
399
|
/**
|
|
396
400
|
* Whether `text` draws ink: anything but blanks. [LAW:one-source-of-truth]
|
|
397
|
-
* The one test, so the words a
|
|
401
|
+
* The one test, so the words a color effect lights are the words a glyph
|
|
398
402
|
* effect (`./glyph-effects.ts`) scrambles and its particles pass behind.
|
|
399
403
|
*/
|
|
400
404
|
export function inked(text) {
|
|
@@ -446,17 +450,17 @@ function edgesBeside(walked, drawings) {
|
|
|
446
450
|
}
|
|
447
451
|
/** Whether `style` draws in its ink on a blank glyph: a line under, through or over it, or a frame round it. */
|
|
448
452
|
function drawsLines(style) {
|
|
449
|
-
return [style.underline, style.
|
|
453
|
+
return [style.underline, style.doubleUnderline, style.strike, style.overline, style.frame, style.encircle].some((on) => on === true);
|
|
450
454
|
}
|
|
451
455
|
/**
|
|
452
456
|
* What `looked` draws: `Drawn`'s measure, taken once per frame. Its `all`
|
|
453
457
|
* and `cells` are listed off `walked` the first time either is asked for,
|
|
454
|
-
* since
|
|
458
|
+
* since an effect on attributes alone never asks, and
|
|
455
459
|
* a glyph walk is what measuring the words alone, a segment at a time, is
|
|
456
460
|
* spared (`drawnOf`).
|
|
457
461
|
*/
|
|
458
|
-
function measured(looked, walked, depth,
|
|
459
|
-
const ground =
|
|
462
|
+
function measured(looked, walked, depth, terminal) {
|
|
463
|
+
const ground = terminal.backgroundColor.hex;
|
|
460
464
|
// [LAW:one-source-of-truth] One listing, each cell with whether it shows; `all` and `cells` are read off it.
|
|
461
465
|
let everything;
|
|
462
466
|
const list = () => walked().flatMap(({ look, glyphs }) => {
|
|
@@ -471,7 +475,7 @@ function measured(looked, walked, depth, theme) {
|
|
|
471
475
|
let shown;
|
|
472
476
|
const words = looked.flatMap(({ segment, look }) => (look !== undefined && inked(segment.text) && !look.seam ? [look.pair] : []));
|
|
473
477
|
const pairs = [...new Map(words.map((pair) => [`${pair.fg.hex}/${pair.bg.hex}/${pair.terminal.fg}/${pair.terminal.bg}/${pair.reverse}`, pair])).values()];
|
|
474
|
-
// [LAW:one-source-of-truth] A slot the terminal fills is no
|
|
478
|
+
// [LAW:one-source-of-truth] A slot the terminal fills is no color of the element's, whatever its hex.
|
|
475
479
|
const own = (slot) => pairs.flatMap((pair) => (pair.terminal[slot] ? [] : [pair[slot].hex]));
|
|
476
480
|
return {
|
|
477
481
|
all: () => (all ??= listed().map(({ cell }) => cell)),
|
|
@@ -480,7 +484,7 @@ function measured(looked, walked, depth, theme) {
|
|
|
480
484
|
colors: new Set([...own("fg"), ...own("bg")]),
|
|
481
485
|
fills: new Set(own("bg")),
|
|
482
486
|
depth,
|
|
483
|
-
|
|
487
|
+
terminal,
|
|
484
488
|
};
|
|
485
489
|
}
|
|
486
490
|
/**
|
|
@@ -503,43 +507,49 @@ export function confined(effect, ...owners) {
|
|
|
503
507
|
return false;
|
|
504
508
|
};
|
|
505
509
|
// The frame as the effect sees it: the owners' cells alone. [LAW:one-source-of-truth] One narrowing, handed to both stages.
|
|
506
|
-
const narrowed = (drawn) =>
|
|
510
|
+
const narrowed = (drawn) => {
|
|
511
|
+
let all;
|
|
512
|
+
let cells;
|
|
513
|
+
return { ...drawn, all: () => (all ??= drawn.all().filter(owned)), cells: () => (cells ??= drawn.cells().filter(owned)) };
|
|
514
|
+
};
|
|
507
515
|
// The owners' cells change when the effect's frame does.
|
|
508
516
|
return (drawn) => Animation.map(effect(narrowed(drawn)), (frame) => (look, cell) => (owned(cell) ? frame(look, cell) : look));
|
|
509
517
|
}
|
|
510
518
|
/**
|
|
511
|
-
* What `segments`, drawn at `depth` on `
|
|
512
|
-
* as `Effected` measures its child for every frame
|
|
513
|
-
*
|
|
519
|
+
* What `segments`, drawn at `depth` on `terminal`, draw their words in, measured
|
|
520
|
+
* as `Effected` measures its child for every frame, so `options.terminal` means
|
|
521
|
+
* here what it means on a frame. Measure an element's output under an effect to
|
|
522
|
+
* read the contrast its words are left at.
|
|
514
523
|
*/
|
|
515
|
-
export function drawnOf(segments, depth,
|
|
516
|
-
const
|
|
517
|
-
|
|
524
|
+
export function drawnOf(segments, depth, terminal) {
|
|
525
|
+
const on = resolveTerminal(terminal, depth);
|
|
526
|
+
const looked = looks(segments, depth, on);
|
|
527
|
+
return measured(looked, () => walk(looked), depth, on);
|
|
518
528
|
}
|
|
519
529
|
/**
|
|
520
|
-
* A style's
|
|
530
|
+
* A style's color slots as the screen shows them, or the reverse: `reverse`
|
|
521
531
|
* swaps glyph and ground, and swapping twice is where you started.
|
|
522
532
|
*/
|
|
523
533
|
function onScreen(slots, reverse) {
|
|
524
534
|
return reverse ? { fg: slots.bg, bg: slots.fg } : slots;
|
|
525
535
|
}
|
|
526
536
|
/**
|
|
527
|
-
* The specs a cell's slots are written in once their
|
|
537
|
+
* The specs a cell's slots are written in once their colors move from
|
|
528
538
|
* `from`, what the written specs draw, to `to`: each laid on what is beneath
|
|
529
539
|
* it — the ground on `SURFACE_BLACK`, the glyph on that ground, as the writer
|
|
530
|
-
* lays any translucent
|
|
540
|
+
* lays any translucent color (`Style.drawnColors`) — then matched at `depth`
|
|
531
541
|
* (`ColorSpec.matchMove`). A slot the depth shows no move in has none, and
|
|
532
542
|
* keeps the spec it was written in.
|
|
533
543
|
*/
|
|
534
|
-
function movedSpecs(from, to, depth,
|
|
535
|
-
// [LAW:one-source-of-truth] The writer's surface, so one translucent
|
|
544
|
+
function movedSpecs(from, to, depth, terminal) {
|
|
545
|
+
// [LAW:one-source-of-truth] The writer's surface, so one translucent color draws one way in a frame.
|
|
536
546
|
const ground = to.bg.compositeOver(SURFACE_BLACK);
|
|
537
547
|
const glyph = to.fg.compositeOver(ground);
|
|
538
|
-
return { fg: ColorSpec.matchMove(from.fg, glyph, depth,
|
|
548
|
+
return { fg: ColorSpec.matchMove(from.fg, glyph, depth, terminal, true), bg: ColorSpec.matchMove(from.bg, ground, depth, terminal, false) };
|
|
539
549
|
}
|
|
540
550
|
/**
|
|
541
|
-
* Whether two cells'
|
|
542
|
-
* the default
|
|
551
|
+
* Whether two cells' colors are written alike. A slot with no color is not
|
|
552
|
+
* the default color: it writes nothing, so a style a container lays beneath
|
|
543
553
|
* (`Segment.applyStyle`) still shows through it, where `39`/`49` would not.
|
|
544
554
|
*/
|
|
545
555
|
function writesAlike(a, b) {
|
|
@@ -577,7 +587,7 @@ export function cellAttributes(of) {
|
|
|
577
587
|
function sameAttributes(a, b) {
|
|
578
588
|
return a === b || MOVED.every((name) => a[name] === b[name]);
|
|
579
589
|
}
|
|
580
|
-
/** Whether two specs are one
|
|
590
|
+
/** Whether two specs are one color as written: the same kind, naming the same color. */
|
|
581
591
|
function sameSpec(a, b) {
|
|
582
592
|
return a === b || (a !== undefined && b !== undefined && a.type === b.type && a.name === b.name);
|
|
583
593
|
}
|