@promptctl/rich-js 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/clock.d.ts +9 -1
- package/dist/core/clock.js +22 -2
- package/dist/core/color.d.ts +10 -0
- package/dist/core/color.js +36 -1
- package/dist/core/easing.d.ts +31 -0
- package/dist/core/easing.js +106 -1
- package/dist/core/segment.d.ts +10 -0
- package/dist/core/segment.js +15 -0
- package/dist/core/strip.d.ts +108 -31
- package/dist/core/strip.js +240 -131
- package/dist/core/style.d.ts +18 -1
- package/dist/core/style.js +32 -3
- package/dist/core/text.js +4 -1
- package/dist/host/app.js +2 -4
- package/dist/host/index.d.ts +1 -1
- package/dist/host/input.d.ts +37 -13
- package/dist/host/input.js +146 -56
- package/dist/host/terminal-host.d.ts +16 -0
- package/dist/host/terminal-host.js +1 -0
- package/dist/index.d.ts +11 -9
- package/dist/index.js +5 -4
- package/dist/node/terminal-host.d.ts +8 -1
- package/dist/node/terminal-host.js +2 -0
- package/dist/renderables/effect.d.ts +91 -18
- package/dist/renderables/effect.js +158 -46
- package/dist/renderables/effects.d.ts +73 -26
- package/dist/renderables/effects.js +412 -92
- package/dist/renderables/embed.d.ts +10 -0
- package/dist/renderables/embed.js +15 -7
- package/dist/renderables/frames.d.ts +95 -0
- package/dist/renderables/frames.js +106 -0
- package/dist/renderables/live.d.ts +23 -7
- package/dist/renderables/live.js +68 -34
- package/dist/renderables/markdown.d.ts +5 -2
- package/dist/renderables/markdown.js +210 -19
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +3 -1
- package/dist/renderables/progressBar.js +3 -3
- package/dist/renderables/spinner.d.ts +62 -28
- package/dist/renderables/spinner.js +99 -52
- package/dist/renderables/status.d.ts +8 -3
- package/dist/renderables/status.js +8 -5
- package/dist/template-bindings/animation-funcs.d.ts +37 -0
- package/dist/template-bindings/animation-funcs.js +83 -0
- package/dist/template-bindings/color-funcs.d.ts +13 -0
- package/dist/template-bindings/color-funcs.js +1 -1
- package/dist/template-bindings/helpers.d.ts +6 -0
- package/dist/template-bindings/helpers.js +12 -3
- package/dist/template-bindings/index.d.ts +6 -2
- package/dist/template-bindings/index.js +6 -2
- package/package.json +1 -1
|
@@ -82,6 +82,16 @@ export declare class InlineLabel {
|
|
|
82
82
|
*/
|
|
83
83
|
text(options: RenderOptions, base?: TextStyle): RichText;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* `text`, changed in place into a fragment of a line it shares, as Rich's
|
|
87
|
+
* `_title` reads one: its own end is not drawn, newlines become spaces so it
|
|
88
|
+
* stays on its line, and tabs are widened to their stops before anything
|
|
89
|
+
* measures it, so a cut to a border and the width a label asks for count
|
|
90
|
+
* the cells it will draw. [LAW:single-enforcer] The one reading of "set into
|
|
91
|
+
* a line", for a label read through the crossing above and for text a
|
|
92
|
+
* caller hands over already read (`Frames`).
|
|
93
|
+
*/
|
|
94
|
+
export declare function setIntoLine(text: RichText): RichText;
|
|
85
95
|
/**
|
|
86
96
|
* A label cut to `width` cells as Rich's `Text.truncate` cuts one: its plain
|
|
87
97
|
* text set to the width less `marker` — a wide character the cut splits leaves
|
|
@@ -114,15 +114,23 @@ export class InlineLabel {
|
|
|
114
114
|
* caller's cut. A string reads with `base` as its style, as `EmbeddedText.text` reads one.
|
|
115
115
|
*/
|
|
116
116
|
text(options, base) {
|
|
117
|
-
|
|
118
|
-
// A fragment of the line, so its own end is not drawn, as Rich's `_title` clears it.
|
|
119
|
-
text.end = "";
|
|
120
|
-
text.plain = text.plain.replaceAll("\n", " ");
|
|
121
|
-
// Tabs widened before anything measures it, so a cut to the border and
|
|
122
|
-
// the width a title asks for count the cells the label will draw.
|
|
123
|
-
return text.expandTabs();
|
|
117
|
+
return setIntoLine(this._content.text(options, base));
|
|
124
118
|
}
|
|
125
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* `text`, changed in place into a fragment of a line it shares, as Rich's
|
|
122
|
+
* `_title` reads one: its own end is not drawn, newlines become spaces so it
|
|
123
|
+
* stays on its line, and tabs are widened to their stops before anything
|
|
124
|
+
* measures it, so a cut to a border and the width a label asks for count
|
|
125
|
+
* the cells it will draw. [LAW:single-enforcer] The one reading of "set into
|
|
126
|
+
* a line", for a label read through the crossing above and for text a
|
|
127
|
+
* caller hands over already read (`Frames`).
|
|
128
|
+
*/
|
|
129
|
+
export function setIntoLine(text) {
|
|
130
|
+
text.end = "";
|
|
131
|
+
text.plain = text.plain.replaceAll("\n", " ");
|
|
132
|
+
return text.expandTabs();
|
|
133
|
+
}
|
|
126
134
|
/**
|
|
127
135
|
* A label cut to `width` cells as Rich's `Text.truncate` cuts one: its plain
|
|
128
136
|
* text set to the width less `marker` — a wide character the cut splits leaves
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frames — content that changes on a beat: a sequence of frames, each held
|
|
3
|
+
* for its own seconds and looping, drawn as the one in progress at a time
|
|
4
|
+
* the host hands in.
|
|
5
|
+
*
|
|
6
|
+
* An effect moves a cell's colours and may never change a glyph
|
|
7
|
+
* (`./effect.ts`: "It changes how cells look, never which cells exist or who
|
|
8
|
+
* drew them"). A frame is the other kind of motion — what the element shows
|
|
9
|
+
* changes — so it is its own primitive and not a wider `Effect`, and the two
|
|
10
|
+
* compose by nesting: `new Effected(new Frames(…), …)` lights whichever frame
|
|
11
|
+
* is up.
|
|
12
|
+
*
|
|
13
|
+
* [LAW:no-ambient-temporal-coupling] `t` is an argument, as
|
|
14
|
+
* `Effected`'s is, never a clock read: whoever draws frames hands each one
|
|
15
|
+
* its time (`core/clock.ts`), so the same `t` draws the same bytes at one
|
|
16
|
+
* redraw a second or sixty. The host's rate is the host's. At sixty a frame
|
|
17
|
+
* shows for exactly its hold; at one a second a hold shorter than a second is
|
|
18
|
+
* skipped and a period of whole seconds freezes on one frame, and nothing
|
|
19
|
+
* here adapts to that — the beat is `core/easing.ts`'s, the one timing
|
|
20
|
+
* vocabulary, and a draw costs the same at `t = 0` and `t = 10⁶`.
|
|
21
|
+
*
|
|
22
|
+
* The sequence and the moment are two values, because only one of them
|
|
23
|
+
* changes from frame to frame. A `FrameSequence` is the frames prepared once
|
|
24
|
+
* — the beat, the one width, each frame set into a line and padded — and a
|
|
25
|
+
* `Frames` is that sequence at a `t`, whose work is one frame's — finding
|
|
26
|
+
* which, and copying it: a host holds the sequence and asks it for a frame,
|
|
27
|
+
* as `Spinner` holds its two, so a draw costs the same over ninety frames as
|
|
28
|
+
* over one. The sequence is a value the host keeps, not a memo here: nothing
|
|
29
|
+
* is looked up by key.
|
|
30
|
+
*
|
|
31
|
+
* The frame drawn is drawn until the next begins, and its cells say so: they
|
|
32
|
+
* are stamped with that moment (`Style.nextChange`), which rides up through
|
|
33
|
+
* every container to whoever holds the composed frame (`Segment.nextChange`),
|
|
34
|
+
* so a host drawing at `t` knows when to draw again, and one drawing slower
|
|
35
|
+
* than the frames turn can see that it is skipping them.
|
|
36
|
+
*
|
|
37
|
+
* Every frame is drawn at one width, the widest frame's, each padded on the
|
|
38
|
+
* right to it, so whatever sits beside the frames never moves as they turn;
|
|
39
|
+
* and the frame is a fragment of a line (`setIntoLine`), ending no line of
|
|
40
|
+
* its own, so it sits among other cells. Frames are `RichText`, each free to
|
|
41
|
+
* carry its own style, and read once, here: markup is a string's reading
|
|
42
|
+
* under the console that draws it, which a frame handed to a template
|
|
43
|
+
* binding has no console for, so the frame arrives read, as a `Group`'s
|
|
44
|
+
* children do.
|
|
45
|
+
*/
|
|
46
|
+
import { type Beat } from "../core/easing.js";
|
|
47
|
+
import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
|
|
48
|
+
import type { Segment } from "../core/segment.js";
|
|
49
|
+
import type { RichText } from "../core/text.js";
|
|
50
|
+
/** One frame: what it shows, and how long it is held, in seconds. */
|
|
51
|
+
export interface Frame {
|
|
52
|
+
readonly text: RichText;
|
|
53
|
+
readonly seconds: number;
|
|
54
|
+
}
|
|
55
|
+
/** A sequence of frames prepared to be drawn: built once, asked for the frame at a time. */
|
|
56
|
+
export declare class FrameSequence {
|
|
57
|
+
/** Which frame is in progress when: the holds are the frames' seconds, in order. */
|
|
58
|
+
readonly beat: Beat;
|
|
59
|
+
/** The width every frame draws at: the widest frame's, in cells. */
|
|
60
|
+
readonly width: number;
|
|
61
|
+
private readonly _shown;
|
|
62
|
+
constructor(frames: readonly Frame[]);
|
|
63
|
+
/**
|
|
64
|
+
* The frame in progress at `t`, at `width` cells, ending no line: a copy,
|
|
65
|
+
* the caller's to change, its every cell — padding included — stamped with
|
|
66
|
+
* when it gives way to the next, `beat.end(t)`, so a blank frame among
|
|
67
|
+
* drawn ones still turns. [LAW:one-source-of-truth] The stamp is the one
|
|
68
|
+
* answer to when the frame changes, read off the cells
|
|
69
|
+
* (`Segment.nextChange`): a sequence of nothing has no cell to carry one,
|
|
70
|
+
* and a field saying a time would disagree with cells saying never.
|
|
71
|
+
* `beat.at` refuses a `t` that is not finite.
|
|
72
|
+
*/
|
|
73
|
+
text(t: number): RichText;
|
|
74
|
+
}
|
|
75
|
+
export interface FramesOptions {
|
|
76
|
+
/** The moment to draw, in seconds. */
|
|
77
|
+
readonly t: number;
|
|
78
|
+
}
|
|
79
|
+
/** A sequence's frame in progress at one moment, as a renderable. */
|
|
80
|
+
export declare class Frames implements Renderable, Measurable {
|
|
81
|
+
readonly sequence: FrameSequence;
|
|
82
|
+
readonly t: number;
|
|
83
|
+
private readonly _shown;
|
|
84
|
+
constructor(sequence: FrameSequence, options: FramesOptions);
|
|
85
|
+
render(options: RenderOptions): Iterable<Segment>;
|
|
86
|
+
/**
|
|
87
|
+
* One width both ways: a frame is one picture, never wrapped apart at a
|
|
88
|
+
* space or narrowed as the frames turn. Fitting it to the room is
|
|
89
|
+
* `Measurement.get`'s, as for every measurable.
|
|
90
|
+
*/
|
|
91
|
+
measure(): {
|
|
92
|
+
minimum: number;
|
|
93
|
+
maximum: number;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frames — content that changes on a beat: a sequence of frames, each held
|
|
3
|
+
* for its own seconds and looping, drawn as the one in progress at a time
|
|
4
|
+
* the host hands in.
|
|
5
|
+
*
|
|
6
|
+
* An effect moves a cell's colours and may never change a glyph
|
|
7
|
+
* (`./effect.ts`: "It changes how cells look, never which cells exist or who
|
|
8
|
+
* drew them"). A frame is the other kind of motion — what the element shows
|
|
9
|
+
* changes — so it is its own primitive and not a wider `Effect`, and the two
|
|
10
|
+
* compose by nesting: `new Effected(new Frames(…), …)` lights whichever frame
|
|
11
|
+
* is up.
|
|
12
|
+
*
|
|
13
|
+
* [LAW:no-ambient-temporal-coupling] `t` is an argument, as
|
|
14
|
+
* `Effected`'s is, never a clock read: whoever draws frames hands each one
|
|
15
|
+
* its time (`core/clock.ts`), so the same `t` draws the same bytes at one
|
|
16
|
+
* redraw a second or sixty. The host's rate is the host's. At sixty a frame
|
|
17
|
+
* shows for exactly its hold; at one a second a hold shorter than a second is
|
|
18
|
+
* skipped and a period of whole seconds freezes on one frame, and nothing
|
|
19
|
+
* here adapts to that — the beat is `core/easing.ts`'s, the one timing
|
|
20
|
+
* vocabulary, and a draw costs the same at `t = 0` and `t = 10⁶`.
|
|
21
|
+
*
|
|
22
|
+
* The sequence and the moment are two values, because only one of them
|
|
23
|
+
* changes from frame to frame. A `FrameSequence` is the frames prepared once
|
|
24
|
+
* — the beat, the one width, each frame set into a line and padded — and a
|
|
25
|
+
* `Frames` is that sequence at a `t`, whose work is one frame's — finding
|
|
26
|
+
* which, and copying it: a host holds the sequence and asks it for a frame,
|
|
27
|
+
* as `Spinner` holds its two, so a draw costs the same over ninety frames as
|
|
28
|
+
* over one. The sequence is a value the host keeps, not a memo here: nothing
|
|
29
|
+
* is looked up by key.
|
|
30
|
+
*
|
|
31
|
+
* The frame drawn is drawn until the next begins, and its cells say so: they
|
|
32
|
+
* are stamped with that moment (`Style.nextChange`), which rides up through
|
|
33
|
+
* every container to whoever holds the composed frame (`Segment.nextChange`),
|
|
34
|
+
* so a host drawing at `t` knows when to draw again, and one drawing slower
|
|
35
|
+
* than the frames turn can see that it is skipping them.
|
|
36
|
+
*
|
|
37
|
+
* Every frame is drawn at one width, the widest frame's, each padded on the
|
|
38
|
+
* right to it, so whatever sits beside the frames never moves as they turn;
|
|
39
|
+
* and the frame is a fragment of a line (`setIntoLine`), ending no line of
|
|
40
|
+
* its own, so it sits among other cells. Frames are `RichText`, each free to
|
|
41
|
+
* carry its own style, and read once, here: markup is a string's reading
|
|
42
|
+
* under the console that draws it, which a frame handed to a template
|
|
43
|
+
* binding has no console for, so the frame arrives read, as a `Group`'s
|
|
44
|
+
* children do.
|
|
45
|
+
*/
|
|
46
|
+
import { beat } from "../core/easing.js";
|
|
47
|
+
import { Style } from "../core/style.js";
|
|
48
|
+
import { setIntoLine } from "./embed.js";
|
|
49
|
+
/** A sequence of frames prepared to be drawn: built once, asked for the frame at a time. */
|
|
50
|
+
export class FrameSequence {
|
|
51
|
+
/** Which frame is in progress when: the holds are the frames' seconds, in order. */
|
|
52
|
+
beat;
|
|
53
|
+
/** The width every frame draws at: the widest frame's, in cells. */
|
|
54
|
+
width;
|
|
55
|
+
// Each frame as it draws: a line fragment padded to `width`, made once.
|
|
56
|
+
_shown;
|
|
57
|
+
constructor(frames) {
|
|
58
|
+
this.beat = beat(frames.map((frame) => frame.seconds));
|
|
59
|
+
const texts = frames.map((frame) => setIntoLine(frame.text.copy()));
|
|
60
|
+
const widths = texts.map((text) => text.cellLength);
|
|
61
|
+
this.width = Math.max(...widths);
|
|
62
|
+
this._shown = texts.map((text, i) => {
|
|
63
|
+
// One picture: a slot narrower than the frame crops it, never folds
|
|
64
|
+
// or wraps it onto a second line, which `Frames.measure` says it cannot take.
|
|
65
|
+
text.noWrap = true;
|
|
66
|
+
text.overflow = "crop";
|
|
67
|
+
return text.padRight(this.width - widths[i]);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The frame in progress at `t`, at `width` cells, ending no line: a copy,
|
|
72
|
+
* the caller's to change, its every cell — padding included — stamped with
|
|
73
|
+
* when it gives way to the next, `beat.end(t)`, so a blank frame among
|
|
74
|
+
* drawn ones still turns. [LAW:one-source-of-truth] The stamp is the one
|
|
75
|
+
* answer to when the frame changes, read off the cells
|
|
76
|
+
* (`Segment.nextChange`): a sequence of nothing has no cell to carry one,
|
|
77
|
+
* and a field saying a time would disagree with cells saying never.
|
|
78
|
+
* `beat.at` refuses a `t` that is not finite.
|
|
79
|
+
*/
|
|
80
|
+
text(t) {
|
|
81
|
+
return this._shown[this.beat.at(t)].copy().stylize(new Style({ nextChange: this.beat.end(t) }));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** A sequence's frame in progress at one moment, as a renderable. */
|
|
85
|
+
export class Frames {
|
|
86
|
+
sequence;
|
|
87
|
+
t;
|
|
88
|
+
// The frame `t` lands on, found once, here, so a bad time fails at construction.
|
|
89
|
+
_shown;
|
|
90
|
+
constructor(sequence, options) {
|
|
91
|
+
this.sequence = sequence;
|
|
92
|
+
this.t = options.t;
|
|
93
|
+
this._shown = sequence.text(options.t);
|
|
94
|
+
}
|
|
95
|
+
render(options) {
|
|
96
|
+
return this._shown.render(options);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* One width both ways: a frame is one picture, never wrapped apart at a
|
|
100
|
+
* space or narrowed as the frames turn. Fitting it to the room is
|
|
101
|
+
* `Measurement.get`'s, as for every measurable.
|
|
102
|
+
*/
|
|
103
|
+
measure() {
|
|
104
|
+
return { minimum: this.sequence.width, maximum: this.sequence.width };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -27,10 +27,26 @@
|
|
|
27
27
|
import { type Clock } from "../core/clock.js";
|
|
28
28
|
import { Console } from "../core/console.js";
|
|
29
29
|
import { type Renderable } from "../core/protocol.js";
|
|
30
|
+
/**
|
|
31
|
+
* What a Live draws: a renderable, or a view asked for one each frame with
|
|
32
|
+
* the frame's time in seconds on the Live's clock, as an `App`'s view is
|
|
33
|
+
* (`host/app.ts`). A renderable is the view that is the same at every `t` —
|
|
34
|
+
* a table mutated in place between frames — and a function is what a frame
|
|
35
|
+
* drawn for a time needs: `(t) => spinner.at(t)`.
|
|
36
|
+
* [LAW:no-ambient-temporal-coupling] The Live reads its clock once a frame
|
|
37
|
+
* and hands the time down as data, so nothing it draws reads one.
|
|
38
|
+
*/
|
|
39
|
+
export type LiveView = Renderable | ((t: number) => Renderable);
|
|
30
40
|
export interface LiveOptions {
|
|
31
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* How often the auto-refresh draws on its own, in frames a second:
|
|
43
|
+
* positive and finite, fractional included. The next frame comes a frame
|
|
44
|
+
* interval after the last one drawn, a `refresh()` of your own included,
|
|
45
|
+
* or sooner when that frame's content says it changes sooner
|
|
46
|
+
* (`Segment.nextChange`).
|
|
47
|
+
*/
|
|
32
48
|
refreshPerSecond?: number;
|
|
33
|
-
/** What the auto-refresh
|
|
49
|
+
/** What the auto-refresh waits on. The platform's, unless given. */
|
|
34
50
|
clock?: Clock;
|
|
35
51
|
autoRefresh?: boolean;
|
|
36
52
|
transient?: boolean;
|
|
@@ -42,7 +58,7 @@ export interface LiveOptions {
|
|
|
42
58
|
altScreen?: boolean;
|
|
43
59
|
}
|
|
44
60
|
export declare class Live {
|
|
45
|
-
private
|
|
61
|
+
private _view;
|
|
46
62
|
private _console;
|
|
47
63
|
private readonly _rate;
|
|
48
64
|
private readonly _clock;
|
|
@@ -50,21 +66,21 @@ export declare class Live {
|
|
|
50
66
|
private _transient;
|
|
51
67
|
private _verticalOverflow;
|
|
52
68
|
private readonly _painter;
|
|
53
|
-
private
|
|
69
|
+
private _waking;
|
|
54
70
|
private _held;
|
|
55
|
-
constructor(
|
|
71
|
+
constructor(view?: LiveView, options?: LiveOptions);
|
|
56
72
|
/**
|
|
57
73
|
* The console to print through while this Live runs: what it prints lands
|
|
58
74
|
* above an inline frame. The alternate screen is all frame, and paints over it.
|
|
59
75
|
*/
|
|
60
76
|
get console(): Console;
|
|
61
|
-
get renderable(): Renderable | undefined;
|
|
62
77
|
start(): void;
|
|
63
78
|
stop(): void;
|
|
64
|
-
update(
|
|
79
|
+
update(view?: LiveView, options?: {
|
|
65
80
|
refresh?: boolean;
|
|
66
81
|
}): void;
|
|
67
82
|
refresh(): void;
|
|
83
|
+
private _wake;
|
|
68
84
|
private _frame;
|
|
69
85
|
private _stepAround;
|
|
70
86
|
private _paint;
|
package/dist/renderables/live.js
CHANGED
|
@@ -29,8 +29,12 @@ import { Console } from "../core/console.js";
|
|
|
29
29
|
import { Segment } from "../core/segment.js";
|
|
30
30
|
import { FinalFramePainter, SurfacePainter } from "../core/paint.js";
|
|
31
31
|
import { fitHeight } from "../core/protocol.js";
|
|
32
|
+
// [LAW:parse-dont-validate] The one shape Live holds: a renderable becomes
|
|
33
|
+
// the view that answers it at every t, where it is given, so no frame asks
|
|
34
|
+
// which it was.
|
|
35
|
+
const viewOf = (view) => view === undefined || typeof view === "function" ? view : () => view;
|
|
32
36
|
export class Live {
|
|
33
|
-
|
|
37
|
+
_view;
|
|
34
38
|
_console;
|
|
35
39
|
_rate;
|
|
36
40
|
_clock;
|
|
@@ -38,13 +42,14 @@ export class Live {
|
|
|
38
42
|
_transient;
|
|
39
43
|
_verticalOverflow;
|
|
40
44
|
_painter;
|
|
41
|
-
|
|
45
|
+
// The one wait for the next auto-refresh frame, armed after each frame.
|
|
46
|
+
_waking;
|
|
42
47
|
// [LAW:one-source-of-truth] The console's live region, held from `start()`
|
|
43
48
|
// to the hand-back — which is what "started" means, so there is no separate
|
|
44
49
|
// flag to disagree with it.
|
|
45
50
|
_held;
|
|
46
|
-
constructor(
|
|
47
|
-
this.
|
|
51
|
+
constructor(view, options) {
|
|
52
|
+
this._view = viewOf(view);
|
|
48
53
|
this._console = options?.console ?? new Console();
|
|
49
54
|
// [LAW:parse-dont-validate] Parsed whether or not it auto-refreshes, so a
|
|
50
55
|
// rate that could never tick is refused where it is given.
|
|
@@ -69,9 +74,6 @@ export class Live {
|
|
|
69
74
|
get console() {
|
|
70
75
|
return this._console;
|
|
71
76
|
}
|
|
72
|
-
get renderable() {
|
|
73
|
-
return this._renderable;
|
|
74
|
-
}
|
|
75
77
|
start() {
|
|
76
78
|
if (this._held !== null)
|
|
77
79
|
return;
|
|
@@ -80,21 +82,9 @@ export class Live {
|
|
|
80
82
|
const held = this._console.claimLiveRegion({ around: (text) => this._stepAround(text) });
|
|
81
83
|
this._held = held;
|
|
82
84
|
this._painter.take();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// handed back first, and the error then goes on to wherever an uncaught
|
|
87
|
-
// one goes.
|
|
88
|
-
this._stopTicking = this._clock.every(this._rate, () => {
|
|
89
|
-
try {
|
|
90
|
-
this.refresh();
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
this._release(held);
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
85
|
+
// Nothing is drawn yet, so nothing has said when it changes: the first
|
|
86
|
+
// auto-refresh frame comes at the rate, from now.
|
|
87
|
+
this._wake(held, { t: this._clock.now(), nextChange: Number.POSITIVE_INFINITY });
|
|
98
88
|
}
|
|
99
89
|
stop() {
|
|
100
90
|
const held = this._held;
|
|
@@ -114,9 +104,9 @@ export class Live {
|
|
|
114
104
|
this._release(held);
|
|
115
105
|
}
|
|
116
106
|
}
|
|
117
|
-
update(
|
|
118
|
-
if (
|
|
119
|
-
this.
|
|
107
|
+
update(view, options) {
|
|
108
|
+
if (view !== undefined) {
|
|
109
|
+
this._view = viewOf(view);
|
|
120
110
|
}
|
|
121
111
|
if (options?.refresh) {
|
|
122
112
|
this.refresh();
|
|
@@ -126,13 +116,57 @@ export class Live {
|
|
|
126
116
|
// A frame is drawn only while this Live holds the terminal, as in Rich:
|
|
127
117
|
// before `start()` or after the hand-back it would land on a screen that
|
|
128
118
|
// belongs to someone else — the main buffer, under the user's own output.
|
|
129
|
-
|
|
119
|
+
const held = this._held;
|
|
120
|
+
if (held === null)
|
|
121
|
+
return;
|
|
122
|
+
// [LAW:no-ambient-temporal-coupling] The frame says when what it shows
|
|
123
|
+
// next changes; the next auto-refresh frame is then, or at the rate,
|
|
124
|
+
// whichever is sooner. The painter decides whether this frame is drawn
|
|
125
|
+
// now — a final-frame painter draws nothing before the hand-back — and a
|
|
126
|
+
// frame not drawn has said nothing.
|
|
127
|
+
let said;
|
|
128
|
+
this._paint(() => {
|
|
129
|
+
const { t, lines } = this._frame();
|
|
130
|
+
said = { t, nextChange: lines.reduce((soonest, row) => Math.min(soonest, Segment.nextChange(row)), Number.POSITIVE_INFINITY) };
|
|
131
|
+
return lines;
|
|
132
|
+
});
|
|
133
|
+
this._wake(held, said ?? { t: this._clock.now(), nextChange: Number.POSITIVE_INFINITY });
|
|
134
|
+
}
|
|
135
|
+
// Arm the one wait for the next auto-refresh frame while `held`: at the
|
|
136
|
+
// frame's `nextChange` when that is before the rate's next frame, else at
|
|
137
|
+
// the rate — both measured from the frame's own `t`, never from a second
|
|
138
|
+
// reading of the clock, so the rate does not stretch by what a frame cost
|
|
139
|
+
// to draw, and a change that fell due while it was drawn is drawn at once
|
|
140
|
+
// (`Clock.at` fires now for a time already past). Content that changes
|
|
141
|
+
// every frame answers `t` itself, and is drawn at the rate.
|
|
142
|
+
_wake(held, { t, nextChange }) {
|
|
143
|
+
this._waking?.();
|
|
144
|
+
this._waking = undefined;
|
|
145
|
+
if (!this._autoRefresh)
|
|
130
146
|
return;
|
|
131
|
-
|
|
147
|
+
const at = Math.min(t + this._rate.interval, nextChange > t ? nextChange : Number.POSITIVE_INFINITY);
|
|
148
|
+
// No caller is on the stack to run `stop()` for a frame that throws
|
|
149
|
+
// here, and in Node the throw ends the process — so the terminal is
|
|
150
|
+
// handed back first, and the error then goes on to wherever an uncaught
|
|
151
|
+
// one goes.
|
|
152
|
+
this._waking = this._clock.at(at, () => {
|
|
153
|
+
try {
|
|
154
|
+
this.refresh();
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
this._release(held);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
132
161
|
}
|
|
162
|
+
// The frame at the clock's now, with that time: the one the view was handed, and the one the next wake is measured from.
|
|
133
163
|
_frame() {
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
// [LAW:effects-at-boundaries] One read of the clock a frame, handed to
|
|
165
|
+
// the view as the frame's time.
|
|
166
|
+
const t = this._clock.now();
|
|
167
|
+
if (!this._view)
|
|
168
|
+
return { t, lines: [] };
|
|
169
|
+
const renderable = this._view(t);
|
|
136
170
|
// [LAW:dataflow-not-control-flow] Both modes render under the terminal's
|
|
137
171
|
// rows and differ only in what those rows are: the alternate screen is a
|
|
138
172
|
// region the frame stands in, and an inline frame keeps its natural height
|
|
@@ -140,14 +174,14 @@ export class Live {
|
|
|
140
174
|
// back — its overflow policy, then `fitHeight`, which pads only a region.
|
|
141
175
|
const options = this._console.options;
|
|
142
176
|
const height = this._painter.height(options.height.rows);
|
|
143
|
-
const lines = Segment.splitLines(
|
|
144
|
-
return fitHeight(this._overflow(lines, height.rows), height);
|
|
177
|
+
const lines = Segment.splitLines(renderable.render({ ...options, height }));
|
|
178
|
+
return { t, lines: fitHeight(this._overflow(lines, height.rows), height) };
|
|
145
179
|
}
|
|
146
180
|
// What the console writes goes where the frame was, and the frame is painted
|
|
147
181
|
// again under it, as Rich's render hook does — in the bytes the console
|
|
148
182
|
// writes, so a render that throws writes nothing.
|
|
149
183
|
_stepAround(text) {
|
|
150
|
-
return this._painter.around(text, () => this._frame(), this._screen(), this._console.destination);
|
|
184
|
+
return this._painter.around(text, () => this._frame().lines, this._screen(), this._console.destination);
|
|
151
185
|
}
|
|
152
186
|
// [LAW:effects-at-boundaries] The frame is fully rendered before a byte
|
|
153
187
|
// reaches the terminal, and whatever takes the last frame away goes out in
|
|
@@ -177,8 +211,8 @@ export class Live {
|
|
|
177
211
|
_release(held) {
|
|
178
212
|
held();
|
|
179
213
|
this._held = null;
|
|
180
|
-
this.
|
|
181
|
-
this.
|
|
214
|
+
this._waking?.();
|
|
215
|
+
this._waking = undefined;
|
|
182
216
|
this._painter.handBack();
|
|
183
217
|
}
|
|
184
218
|
}
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* Block structure is read as CommonMark reads it: a blockquote and a list
|
|
6
6
|
* item are both containers, their lines tokenized as Markdown of their own
|
|
7
7
|
* once the container's marker or indentation is cut off, and drawn beside a
|
|
8
|
-
* gutter — the quote's bar, the item's marker then its hang.
|
|
9
|
-
*
|
|
8
|
+
* gutter — the quote's bar, the item's marker then its hang. A table, which
|
|
9
|
+
* CommonMark has none of, and an HTML block are read as markdown-it — Rich's
|
|
10
|
+
* parser — reads them. Where CommonMark leaves the drawing open, Rich's is
|
|
11
|
+
* followed: HTML, a block of it or a tag inline, is drawn as Rich draws it,
|
|
12
|
+
* not at all.
|
|
10
13
|
*/
|
|
11
14
|
import { Segment } from "../core/segment.js";
|
|
12
15
|
import { Style } from "../core/style.js";
|