@promptctl/rich-js 0.24.1 → 0.25.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 -0
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +175 -10
- package/dist/core/animation.js +283 -15
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cells.d.ts +23 -13
- package/dist/core/cells.js +58 -48
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +21 -29
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +44 -39
- package/dist/core/easing.d.ts +30 -0
- package/dist/core/easing.js +65 -20
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +2 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +2 -2
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/pretty.js +9 -10
- package/dist/core/protocol.d.ts +100 -39
- package/dist/core/protocol.js +167 -21
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +25 -5
- package/dist/core/segment.d.ts +13 -5
- package/dist/core/segment.js +23 -9
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +13 -16
- package/dist/core/strip.js +119 -110
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +57 -28
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +116 -124
- package/dist/core/timeline.d.ts +30 -5
- package/dist/core/timeline.js +43 -7
- package/dist/core/tween.d.ts +1 -1
- package/dist/core/tween.js +5 -3
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +71 -37
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +24 -12
- package/dist/index.js +9 -4
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/align.d.ts +2 -3
- package/dist/renderables/align.js +0 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +3 -4
- package/dist/renderables/columns.js +2 -7
- package/dist/renderables/constrain.d.ts +2 -3
- package/dist/renderables/constrain.js +0 -3
- package/dist/renderables/effect.d.ts +132 -54
- package/dist/renderables/effect.js +188 -115
- package/dist/renderables/effects.d.ts +122 -11
- package/dist/renderables/effects.js +238 -94
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +16 -8
- package/dist/renderables/flexStrip.d.ts +2 -3
- package/dist/renderables/flexStrip.js +0 -4
- package/dist/renderables/frames.d.ts +35 -54
- package/dist/renderables/frames.js +43 -58
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +3 -4
- package/dist/renderables/group.js +0 -3
- package/dist/renderables/layout.d.ts +12 -12
- package/dist/renderables/layout.js +40 -46
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +4 -5
- package/dist/renderables/list-view.js +9 -13
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +88 -41
- package/dist/renderables/markdown-parse.d.ts +4 -4
- package/dist/renderables/markdown-parse.js +52 -46
- package/dist/renderables/markdown-url.js +11 -11
- package/dist/renderables/padding.d.ts +4 -5
- package/dist/renderables/padding.js +0 -3
- package/dist/renderables/panel.d.ts +14 -16
- package/dist/renderables/panel.js +6 -14
- package/dist/renderables/progress.d.ts +83 -34
- package/dist/renderables/progress.js +182 -95
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +32 -9
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.js +2 -1
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +35 -14
- package/dist/renderables/spinner.js +65 -25
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +3 -2
- package/dist/renderables/table.d.ts +34 -40
- package/dist/renderables/table.js +41 -63
- package/dist/renderables/traceback.d.ts +2 -2
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +27 -25
- package/dist/renderables/tree.js +104 -58
- package/dist/renderables/viewport.d.ts +75 -44
- package/dist/renderables/viewport.js +145 -82
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +5 -5
- package/dist/themes/ramp.d.ts +46 -3
- package/dist/themes/ramp.js +61 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +2 -0
- package/dist/widgets/focus-manager.js +6 -4
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +2 -3
- package/dist/widgets/static-item.js +0 -3
- package/dist/widgets/text-input.d.ts +28 -32
- package/dist/widgets/text-input.js +360 -346
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +14 -4
- package/dist/widgets/widget-app.js +36 -7
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -18
- package/package.json +8 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scroll — a slot of fixed size over content wider or taller than it, the
|
|
3
|
+
* content's offset in the slot moving with time: a long line panning as a
|
|
4
|
+
* marquee, a log tail scrolling, a panel sliding into its place.
|
|
5
|
+
*
|
|
6
|
+
* [LAW:one-type-per-behavior] A marquee, a scroll and a slide are one window
|
|
7
|
+
* and differ only in the motion handed in: the offset along each axis is an
|
|
8
|
+
* `Animation<number>`, so a marquee is a looping `Phase` scaled to the span
|
|
9
|
+
* it crosses and a slide-in is a `tween` to 0, and when the window next
|
|
10
|
+
* changes is `Animation.all`'s answer over the two. Nothing here knows which
|
|
11
|
+
* one it is drawing.
|
|
12
|
+
*
|
|
13
|
+
* The slot is what the window is. It measures as its width whatever the
|
|
14
|
+
* content, and draws exactly its rows of its width at every offset — cropped
|
|
15
|
+
* only to a narrower room it is given — so what sits beside it never moves.
|
|
16
|
+
* An offset before or past the content shows blank cells rather than stopping
|
|
17
|
+
* at an edge: that is what lets a marquee enter from the right and a slide-in
|
|
18
|
+
* arrive from outside the slot.
|
|
19
|
+
*
|
|
20
|
+
* The content is drawn at its own natural width and no height, its own
|
|
21
|
+
* length, because the content wider and taller than the slot is the thing
|
|
22
|
+
* being windowed. Content that should wrap to the slot instead, or that has
|
|
23
|
+
* no natural width because it fills whatever it is given, is
|
|
24
|
+
* `new Constrain(content, width)`, composed rather than asked for.
|
|
25
|
+
*
|
|
26
|
+
* [LAW:single-enforcer] A row is cut at the window's edges by
|
|
27
|
+
* `Segment.divide`, the one place a line is cut at a column: a wide glyph a
|
|
28
|
+
* cut falls in is blanked, never halved, and each cell's anchor still names
|
|
29
|
+
* its column in the content, so hit-testing finds the owner through a moving
|
|
30
|
+
* window.
|
|
31
|
+
*
|
|
32
|
+
* [LAW:no-ambient-temporal-coupling] Pure in `t`, nothing kept between
|
|
33
|
+
* frames; `at` hands over the frame alone, and placed, the scroll stamps
|
|
34
|
+
* the hold it is in at the frame's time (`placedRender`).
|
|
35
|
+
*/
|
|
36
|
+
import { Animation } from "../core/animation.js";
|
|
37
|
+
import { Measurement } from "../core/measure.js";
|
|
38
|
+
import { render, withBoundedWidth } from "../core/protocol.js";
|
|
39
|
+
import { Segment } from "../core/segment.js";
|
|
40
|
+
const NO_OFFSET = Animation.constant(0);
|
|
41
|
+
/** `content` seen through a slot of `width` by `height` cells, moved in it by `col` and `row` at each moment. */
|
|
42
|
+
export function scroll(content, options) {
|
|
43
|
+
const { width, height, col = NO_OFFSET, row = NO_OFFSET } = options;
|
|
44
|
+
for (const [name, n] of [["width", width], ["height", height]]) {
|
|
45
|
+
if (!(Number.isInteger(n) && n > 0))
|
|
46
|
+
throw new RangeError(`scroll: ${name} must be a positive whole number of cells, got ${n}`);
|
|
47
|
+
}
|
|
48
|
+
return Animation.map(Animation.all([col, row]), ([c, r]) => new ScrollFrame(content, width, height, cells("col", c), cells("row", r)));
|
|
49
|
+
}
|
|
50
|
+
/** `offset` rounded to the nearest cell. [LAW:no-silent-failure] One that is not finite has no cell to move to, and says so rather than drawing a blank slot. */
|
|
51
|
+
function cells(name, offset) {
|
|
52
|
+
if (!Number.isFinite(offset))
|
|
53
|
+
throw new RangeError(`scroll: ${name} must be a finite number of cells, got ${offset}`);
|
|
54
|
+
return Math.round(offset);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The width `content` is drawn at: its own natural one. [LAW:no-silent-failure]
|
|
58
|
+
* Content that takes whatever width it is offered — a `Rule`, a renderable
|
|
59
|
+
* with no `measure` — has none, and is refused by name rather than drawn at
|
|
60
|
+
* an unbounded width it cannot fill.
|
|
61
|
+
*/
|
|
62
|
+
function naturalWidth(content, options) {
|
|
63
|
+
const natural = Measurement.get({ ...options, maxWidth: Number.POSITIVE_INFINITY }, content).maximum;
|
|
64
|
+
if (!Number.isFinite(natural)) {
|
|
65
|
+
throw new RangeError("scroll: the content has no natural width — it fills whatever width it is given; give it one with new Constrain(content, width)");
|
|
66
|
+
}
|
|
67
|
+
return natural;
|
|
68
|
+
}
|
|
69
|
+
/** One frame of a scroll: `content`'s cells from column `col` and row `row`, `width` by `height` of them, blank where it has none. */
|
|
70
|
+
class ScrollFrame {
|
|
71
|
+
content;
|
|
72
|
+
width;
|
|
73
|
+
height;
|
|
74
|
+
col;
|
|
75
|
+
row;
|
|
76
|
+
constructor(content, width, height, col, row) {
|
|
77
|
+
this.content = content;
|
|
78
|
+
this.width = width;
|
|
79
|
+
this.height = height;
|
|
80
|
+
this.col = col;
|
|
81
|
+
this.row = row;
|
|
82
|
+
}
|
|
83
|
+
*render(rawOptions) {
|
|
84
|
+
const options = withBoundedWidth(rawOptions, this);
|
|
85
|
+
// The slot is cropped to the room it is given, as a `Panel` of a declared width is.
|
|
86
|
+
const width = Math.min(this.width, options.maxWidth);
|
|
87
|
+
const drawn = [...render(this.content, { ...options, maxWidth: naturalWidth(this.content, options), height: undefined })];
|
|
88
|
+
const lines = Segment.splitLines(drawn);
|
|
89
|
+
const blank = Math.min(width, Math.max(0, -this.col));
|
|
90
|
+
const start = Math.max(0, this.col);
|
|
91
|
+
// [LAW:one-source-of-truth] What the window shows is cut from the whole content, so it may change
|
|
92
|
+
// when anything in the content does — a row above the slot entering pushes every row down, and run
|
|
93
|
+
// backwards, leaving it pulls them up. The cut throws away the cells that hold rode on, so it is
|
|
94
|
+
// told to the frame (`RenderOptions.onHold`).
|
|
95
|
+
options.onHold?.({ lastChange: Segment.lastChange(drawn), nextChange: Segment.nextChange(drawn) });
|
|
96
|
+
for (let r = 0; r < this.height; r++) {
|
|
97
|
+
const line = lines[this.row + r] ?? [];
|
|
98
|
+
const shown = Segment.divide(line, [start, start + width - blank])[1] ?? [];
|
|
99
|
+
yield* Segment.adjustLineLength([new Segment(" ".repeat(blank)), ...shown], width);
|
|
100
|
+
yield Segment.line();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
measure() {
|
|
104
|
+
return { minimum: this.width, maximum: this.width };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the interval; the beat, the width every frame draws at and which frame `t`
|
|
9
9
|
* lands on are the frames primitive's, so a spinner is one more sequence on
|
|
10
10
|
* it and not a second way of turning frames. Custom frames are a `FrameSequence`
|
|
11
|
-
*
|
|
11
|
+
* placed directly. [LAW:one-source-of-truth]
|
|
12
12
|
*
|
|
13
13
|
* It is built once and asked for each frame, as Rich's `Spinner` is built
|
|
14
14
|
* once and has `render(time)` called on it: the name is parsed and the
|
|
@@ -19,13 +19,12 @@
|
|
|
19
19
|
* the beat, the width, each frame padded to it — is made there too, so a
|
|
20
20
|
* drawn frame costs only finding which one `t` lands on.
|
|
21
21
|
*
|
|
22
|
-
* [LAW:no-ambient-temporal-coupling]
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
* time, as `App` and `Task.time` hand it, where Rich's spinner counts from
|
|
22
|
+
* [LAW:no-ambient-temporal-coupling] Placed, a spinner draws at the time of
|
|
23
|
+
* the frame it is drawn in, and nothing here reads a clock. Whoever draws
|
|
24
|
+
* frames hands each one its time (`core/clock.ts`) — a `Progress` the
|
|
25
|
+
* moment its tasks are drawn at, a `Live` the frame's time on its clock — as
|
|
26
|
+
* Rich's `Spinner.render` is handed `console.get_time()` or `task.get_time()`. `t` is the clock's
|
|
27
|
+
* time, as a host writes it on the frame, where Rich's spinner counts from
|
|
29
28
|
* its first draw: here nothing is kept between frames, so the same `t` draws
|
|
30
29
|
* the same bytes at one redraw a second or sixty, two spinners on one clock
|
|
31
30
|
* agree at every `t`, a `Progress` on a hand-moved clock turns its spinners
|
|
@@ -35,10 +34,11 @@
|
|
|
35
34
|
* that rate, as Rich scales the time, `t × speed` into the beat: the frames
|
|
36
35
|
* are held for the interval the data gives, and the spinner is asked for the
|
|
37
36
|
* frame at the time the timeline maps the host's to. [LAW:one-source-of-truth]
|
|
38
|
-
* When the frame next changes (`Style.
|
|
37
|
+
* When the frame next changes (`Style.hold`) is a time on the host's
|
|
39
38
|
* timeline, and the sequence answers on its own, so the timeline carries the
|
|
40
|
-
* answer back
|
|
41
|
-
*
|
|
39
|
+
* answer back. `at` hands over the frame alone, as every animation's does;
|
|
40
|
+
* placed, a spinner draws at the frame's time and stamps its cells with that
|
|
41
|
+
* answer for the frames the output draws. Backwards, the frames count down from the last at `t = 0`,
|
|
42
42
|
* a negative time landing in the last frame of the pass before, where Rich's
|
|
43
43
|
* `int()` truncation holds frame 0 for a first interval and then counts
|
|
44
44
|
* down: a departure, made so a negative speed is the one timeline run
|
|
@@ -46,10 +46,11 @@
|
|
|
46
46
|
* frame forever, which a paused timeline is and a rate is not, and the
|
|
47
47
|
* timeline refuses it.
|
|
48
48
|
*/
|
|
49
|
-
import
|
|
49
|
+
import { Animation } from "../core/animation.js";
|
|
50
|
+
import { Segment } from "../core/segment.js";
|
|
50
51
|
import { Style } from "../core/style.js";
|
|
51
52
|
import { RichText } from "../core/text.js";
|
|
52
|
-
import type { Renderable, Measurable } from "../core/protocol.js";
|
|
53
|
+
import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
53
54
|
export interface SpinnerOptions {
|
|
54
55
|
/** Multiplies the spinner's rate; negative runs it backwards; zero is refused. Defaults to 1. */
|
|
55
56
|
speed?: number;
|
|
@@ -66,10 +67,19 @@ export declare class Spinner implements Animation<Renderable & Measurable> {
|
|
|
66
67
|
* whatever the console says — and a `RichText` changed in place draws as
|
|
67
68
|
* changed, as the `Text` Rich keeps does. [LAW:single-enforcer]
|
|
68
69
|
*/
|
|
69
|
-
text: string | RichText;
|
|
70
|
+
get text(): string | RichText;
|
|
71
|
+
set text(value: string | RichText);
|
|
72
|
+
private readonly _text;
|
|
70
73
|
private readonly _data;
|
|
71
74
|
private readonly _named;
|
|
72
75
|
private readonly _ascii;
|
|
76
|
+
/**
|
|
77
|
+
* The spinner drawn at the frame's time, its cells saying the hold it is in
|
|
78
|
+
* on the host's clock (`stampFrame`) — exactly, for the frames the output
|
|
79
|
+
* draws, where `nextChange` must answer for both. Under reduced motion it
|
|
80
|
+
* is the spinner reduced, which never turns (`shownAt`).
|
|
81
|
+
*/
|
|
82
|
+
private readonly _placed;
|
|
73
83
|
constructor(name?: string, text?: string | RichText, options?: SpinnerOptions);
|
|
74
84
|
get frames(): readonly string[];
|
|
75
85
|
/** Milliseconds each frame is held, as the spinner data gives it. */
|
|
@@ -79,8 +89,19 @@ export declare class Spinner implements Animation<Renderable & Measurable> {
|
|
|
79
89
|
* the line wrapped to the width and ended, as the reference's `Text` is —
|
|
80
90
|
* printed, stacked in a `Group`, or laid out in a `Progress` cell alike.
|
|
81
91
|
* The frames this output can draw are chosen where the output is known.
|
|
92
|
+
* The frame says nothing of when it turns — `t` may be a time a timeline
|
|
93
|
+
* mapped — so placed, the spinner itself stamps it.
|
|
82
94
|
*/
|
|
83
95
|
at(t: number): Renderable & Measurable;
|
|
96
|
+
/** The spinner with its motion taken out: its first frame and the label, which never turn, so any time answers. */
|
|
97
|
+
reduced(): Animation<Renderable & Measurable>;
|
|
98
|
+
render(options: RenderOptions): Iterable<Segment>;
|
|
99
|
+
measure(options: RenderOptions): {
|
|
100
|
+
minimum: number;
|
|
101
|
+
maximum: number;
|
|
102
|
+
};
|
|
103
|
+
/** The frame `choose` picks from the frames the output can draw, and the label, saying the hold it is in if `choose` says. */
|
|
104
|
+
private _frame;
|
|
84
105
|
/**
|
|
85
106
|
* When the spinner drawn at `t` next turns, on the host's clock: the
|
|
86
107
|
* sooner of its two sequences' answers, since which one is drawn is the
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the interval; the beat, the width every frame draws at and which frame `t`
|
|
9
9
|
* lands on are the frames primitive's, so a spinner is one more sequence on
|
|
10
10
|
* it and not a second way of turning frames. Custom frames are a `FrameSequence`
|
|
11
|
-
*
|
|
11
|
+
* placed directly. [LAW:one-source-of-truth]
|
|
12
12
|
*
|
|
13
13
|
* It is built once and asked for each frame, as Rich's `Spinner` is built
|
|
14
14
|
* once and has `render(time)` called on it: the name is parsed and the
|
|
@@ -19,13 +19,12 @@
|
|
|
19
19
|
* the beat, the width, each frame padded to it — is made there too, so a
|
|
20
20
|
* drawn frame costs only finding which one `t` lands on.
|
|
21
21
|
*
|
|
22
|
-
* [LAW:no-ambient-temporal-coupling]
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
* time, as `App` and `Task.time` hand it, where Rich's spinner counts from
|
|
22
|
+
* [LAW:no-ambient-temporal-coupling] Placed, a spinner draws at the time of
|
|
23
|
+
* the frame it is drawn in, and nothing here reads a clock. Whoever draws
|
|
24
|
+
* frames hands each one its time (`core/clock.ts`) — a `Progress` the
|
|
25
|
+
* moment its tasks are drawn at, a `Live` the frame's time on its clock — as
|
|
26
|
+
* Rich's `Spinner.render` is handed `console.get_time()` or `task.get_time()`. `t` is the clock's
|
|
27
|
+
* time, as a host writes it on the frame, where Rich's spinner counts from
|
|
29
28
|
* its first draw: here nothing is kept between frames, so the same `t` draws
|
|
30
29
|
* the same bytes at one redraw a second or sixty, two spinners on one clock
|
|
31
30
|
* agree at every `t`, a `Progress` on a hand-moved clock turns its spinners
|
|
@@ -35,10 +34,11 @@
|
|
|
35
34
|
* that rate, as Rich scales the time, `t × speed` into the beat: the frames
|
|
36
35
|
* are held for the interval the data gives, and the spinner is asked for the
|
|
37
36
|
* frame at the time the timeline maps the host's to. [LAW:one-source-of-truth]
|
|
38
|
-
* When the frame next changes (`Style.
|
|
37
|
+
* When the frame next changes (`Style.hold`) is a time on the host's
|
|
39
38
|
* timeline, and the sequence answers on its own, so the timeline carries the
|
|
40
|
-
* answer back
|
|
41
|
-
*
|
|
39
|
+
* answer back. `at` hands over the frame alone, as every animation's does;
|
|
40
|
+
* placed, a spinner draws at the frame's time and stamps its cells with that
|
|
41
|
+
* answer for the frames the output draws. Backwards, the frames count down from the last at `t = 0`,
|
|
42
42
|
* a negative time landing in the last frame of the pass before, where Rich's
|
|
43
43
|
* `int()` truncation holds frame 0 for a first interval and then counts
|
|
44
44
|
* down: a departure, made so a negative speed is the one timeline run
|
|
@@ -46,14 +46,17 @@
|
|
|
46
46
|
* frame forever, which a paused timeline is and a rate is not, and the
|
|
47
47
|
* timeline refuses it.
|
|
48
48
|
*/
|
|
49
|
+
import { Animation, frameTimeOf, holdOf, shownAt, stampFrame } from "../core/animation.js";
|
|
49
50
|
import { Segment } from "../core/segment.js";
|
|
50
51
|
import { NULL_STYLE } from "../core/style.js";
|
|
51
52
|
import { RichText } from "../core/text.js";
|
|
52
53
|
import { SPINNERS, DEFAULT_SPINNER, LINE_SPINNER } from "../core/spinnerData.js";
|
|
53
54
|
import { Timeline } from "../core/timeline.js";
|
|
54
|
-
import { drawable, render } from "../core/protocol.js";
|
|
55
|
+
import { drawable, measure, render } from "../core/protocol.js";
|
|
55
56
|
import { EmbeddedText } from "./embed.js";
|
|
56
|
-
import { FrameSequence
|
|
57
|
+
import { FrameSequence } from "./frames.js";
|
|
58
|
+
import { finiteTime } from "../core/easing.js";
|
|
59
|
+
import { State } from "../core/state.js";
|
|
57
60
|
const sequence = (data, style, timeline) => {
|
|
58
61
|
const frames = new FrameSequence(data.frames.map((frame) => ({ text: new RichText("", { end: "" }).append(frame, style), seconds: data.interval / 1000 })));
|
|
59
62
|
return { frames, played: Timeline.run(timeline, frames), glyphs: data.frames.join("") };
|
|
@@ -70,12 +73,26 @@ export class Spinner {
|
|
|
70
73
|
* whatever the console says — and a `RichText` changed in place draws as
|
|
71
74
|
* changed, as the `Text` Rich keeps does. [LAW:single-enforcer]
|
|
72
75
|
*/
|
|
73
|
-
text
|
|
76
|
+
get text() {
|
|
77
|
+
return this._text.get();
|
|
78
|
+
}
|
|
79
|
+
set text(value) {
|
|
80
|
+
this._text.set(value);
|
|
81
|
+
}
|
|
82
|
+
// Rich's `Spinner.update` sets the text after it is built.
|
|
83
|
+
_text;
|
|
74
84
|
_data;
|
|
75
85
|
// The named data's frames, and the `line` spinner's that stand in for them
|
|
76
86
|
// on an ASCII-only output: prepared once, which is drawn chosen at `at`.
|
|
77
87
|
_named;
|
|
78
88
|
_ascii;
|
|
89
|
+
/**
|
|
90
|
+
* The spinner drawn at the frame's time, its cells saying the hold it is in
|
|
91
|
+
* on the host's clock (`stampFrame`) — exactly, for the frames the output
|
|
92
|
+
* draws, where `nextChange` must answer for both. Under reduced motion it
|
|
93
|
+
* is the spinner reduced, which never turns (`shownAt`).
|
|
94
|
+
*/
|
|
95
|
+
_placed;
|
|
79
96
|
constructor(name, text = "", options) {
|
|
80
97
|
const spinnerName = name ?? DEFAULT_SPINNER;
|
|
81
98
|
const data = SPINNERS[spinnerName];
|
|
@@ -87,12 +104,17 @@ export class Spinner {
|
|
|
87
104
|
// refuses one that is not where it is built.
|
|
88
105
|
const timeline = Timeline.play(0, speed);
|
|
89
106
|
this.name = spinnerName;
|
|
90
|
-
this.
|
|
107
|
+
this._text = new State(text);
|
|
91
108
|
this.speed = speed;
|
|
92
109
|
this.style = options?.style ?? NULL_STYLE;
|
|
93
110
|
this._data = data;
|
|
94
111
|
this._named = sequence(data, this.style, timeline);
|
|
95
112
|
this._ascii = sequence(LINE_SPINNER, this.style, timeline);
|
|
113
|
+
this._placed = this._frame((played, options) => {
|
|
114
|
+
const t = frameTimeOf(options);
|
|
115
|
+
const shown = shownAt(played, t, options);
|
|
116
|
+
return { value: shown.value, hold: holdOf(shown, t) };
|
|
117
|
+
});
|
|
96
118
|
}
|
|
97
119
|
get frames() {
|
|
98
120
|
return this._data.frames;
|
|
@@ -106,21 +128,39 @@ export class Spinner {
|
|
|
106
128
|
* the line wrapped to the width and ended, as the reference's `Text` is —
|
|
107
129
|
* printed, stacked in a `Group`, or laid out in a `Progress` cell alike.
|
|
108
130
|
* The frames this output can draw are chosen where the output is known.
|
|
131
|
+
* The frame says nothing of when it turns — `t` may be a time a timeline
|
|
132
|
+
* mapped — so placed, the spinner itself stamps it.
|
|
109
133
|
*/
|
|
110
134
|
at(t) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
135
|
+
finiteTime("Spinner.at", t);
|
|
136
|
+
return this._frame((played) => ({ value: played.at(t) }));
|
|
137
|
+
}
|
|
138
|
+
/** The spinner with its motion taken out: its first frame and the label, which never turn, so any time answers. */
|
|
139
|
+
reduced() {
|
|
140
|
+
return Animation.constant(this._frame((played) => ({ value: played.reduced().at(0) })));
|
|
141
|
+
}
|
|
142
|
+
render(options) {
|
|
143
|
+
return render(this._placed, options);
|
|
144
|
+
}
|
|
145
|
+
measure(options) {
|
|
146
|
+
return measure(this._placed, options);
|
|
147
|
+
}
|
|
148
|
+
/** The frame `choose` picks from the frames the output can draw, and the label, saying the hold it is in if `choose` says. */
|
|
149
|
+
_frame(choose) {
|
|
114
150
|
const drawn = (options) => drawable(options, this._named, this._ascii, sequenceGlyphs);
|
|
115
151
|
return {
|
|
116
|
-
render: (options) =>
|
|
152
|
+
render: (options) => {
|
|
153
|
+
const { value, hold } = choose(drawn(options).played, options);
|
|
154
|
+
const segments = this._render(value, options);
|
|
155
|
+
return hold === undefined ? segments : stampFrame(segments, hold);
|
|
156
|
+
},
|
|
117
157
|
// Measured at the frames' one width, the widest frame's, so a column
|
|
118
158
|
// holding it never narrows as it turns, and never offered less than
|
|
119
159
|
// that frame: a frame is one picture, which the reference's `Text`
|
|
120
160
|
// measure would let wrap apart at its spaces.
|
|
121
161
|
measure: (options) => {
|
|
122
162
|
const { frames, played } = drawn(options);
|
|
123
|
-
const { minimum, maximum } = this._line(played,
|
|
163
|
+
const { minimum, maximum } = measure(this._line(choose(played, options).value, options), options);
|
|
124
164
|
return { minimum: Math.max(minimum, Math.min(frames.width, maximum)), maximum };
|
|
125
165
|
},
|
|
126
166
|
};
|
|
@@ -139,8 +179,8 @@ export class Spinner {
|
|
|
139
179
|
lastChange(t) {
|
|
140
180
|
return Math.max(this._named.played.lastChange(t), this._ascii.played.lastChange(t));
|
|
141
181
|
}
|
|
142
|
-
*_render(
|
|
143
|
-
yield* render(this._line(
|
|
182
|
+
*_render(frame, options) {
|
|
183
|
+
yield* render(this._line(frame, options), options);
|
|
144
184
|
yield Segment.line();
|
|
145
185
|
}
|
|
146
186
|
/**
|
|
@@ -150,9 +190,9 @@ export class Spinner {
|
|
|
150
190
|
* styles nothing — is no label, not a trailing space, as an empty `Text` is
|
|
151
191
|
* falsy there.
|
|
152
192
|
*/
|
|
153
|
-
_line(
|
|
154
|
-
// The frame
|
|
155
|
-
const line = new RichText("", { end: "" }).append(
|
|
193
|
+
_line(frame, options) {
|
|
194
|
+
// The frame alone, saying nothing of when it turns: placed, the spinner stamps it.
|
|
195
|
+
const line = new RichText("", { end: "" }).append(frame);
|
|
156
196
|
// Unhighlighted: Rich's label is a `Text` from the moment it is handed over, and a `Text` is never highlighted.
|
|
157
197
|
const label = new EmbeddedText(this.text).text({ ...options, highlight: false });
|
|
158
198
|
if (label.plain !== "")
|
|
@@ -20,7 +20,7 @@ export class Status {
|
|
|
20
20
|
speed: options?.speed,
|
|
21
21
|
style: options?.spinnerStyle ?? "status.spinner",
|
|
22
22
|
});
|
|
23
|
-
this._live = new Live(
|
|
23
|
+
this._live = new Live(this._spinner, {
|
|
24
24
|
console: this._console,
|
|
25
25
|
clock: options?.clock,
|
|
26
26
|
transient: true,
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Segment } from "../core/segment.js";
|
|
11
11
|
import { type Measurable, type Renderable, type RenderOptions } from "../core/protocol.js";
|
|
12
|
+
import { type Pattern } from "../core/pattern.js";
|
|
12
13
|
export interface SyntaxOptions {
|
|
13
14
|
lineNumbers?: boolean;
|
|
14
15
|
startLine?: number;
|
|
15
|
-
lineRange?: [number, number];
|
|
16
|
-
highlightLines?:
|
|
16
|
+
lineRange?: readonly [number, number];
|
|
17
|
+
highlightLines?: ReadonlySet<number>;
|
|
17
18
|
wordWrap?: boolean;
|
|
18
19
|
tabSize?: number;
|
|
19
20
|
}
|
|
@@ -24,11 +25,11 @@ export interface SyntaxOptions {
|
|
|
24
25
|
* render; `"text"` is the grammar with no rules.
|
|
25
26
|
*/
|
|
26
27
|
declare const GRAMMARS: {
|
|
27
|
-
javascript:
|
|
28
|
-
typescript:
|
|
29
|
-
python:
|
|
30
|
-
bash:
|
|
31
|
-
text:
|
|
28
|
+
readonly javascript: Pattern;
|
|
29
|
+
readonly typescript: Pattern;
|
|
30
|
+
readonly python: Pattern;
|
|
31
|
+
readonly bash: Pattern;
|
|
32
|
+
readonly text: Pattern;
|
|
32
33
|
};
|
|
33
34
|
export type SyntaxLanguage = keyof typeof GRAMMARS;
|
|
34
35
|
export declare class Syntax implements Renderable, Measurable {
|
|
@@ -36,8 +37,8 @@ export declare class Syntax implements Renderable, Measurable {
|
|
|
36
37
|
readonly language: SyntaxLanguage;
|
|
37
38
|
readonly lineNumbers: boolean;
|
|
38
39
|
readonly startLine: number;
|
|
39
|
-
readonly lineRange: [number, number] | undefined;
|
|
40
|
-
readonly highlightLines:
|
|
40
|
+
readonly lineRange: readonly [number, number] | undefined;
|
|
41
|
+
readonly highlightLines: ReadonlySet<number>;
|
|
41
42
|
readonly wordWrap: boolean;
|
|
42
43
|
readonly tabSize: number;
|
|
43
44
|
/** The code with its tabs expanded, as lines — what both render and measure read. */
|
|
@@ -12,6 +12,7 @@ import { Segment } from "../core/segment.js";
|
|
|
12
12
|
import { RichText } from "../core/text.js";
|
|
13
13
|
import { drawable, groundedStyle, getStyle, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
14
14
|
import { render } from "../core/protocol.js";
|
|
15
|
+
import { pattern } from "../core/pattern.js";
|
|
15
16
|
/**
|
|
16
17
|
* One regex per grammar, each token a named alternative. A single left-to-right
|
|
17
18
|
* scan is what keeps a `//` inside a string from starting a comment, and a
|
|
@@ -20,7 +21,7 @@ import { render } from "../core/protocol.js";
|
|
|
20
21
|
* `(?!)` never matches, so a grammar with no rules is still a regex.
|
|
21
22
|
*/
|
|
22
23
|
function grammar(rules) {
|
|
23
|
-
return new RegExp([...rules.map(([token, source]) => `(?<${token}>${source})`), "(?!)"].join("|")
|
|
24
|
+
return pattern(new RegExp([...rules.map(([token, source]) => `(?<${token}>${source})`), "(?!)"].join("|")));
|
|
24
25
|
}
|
|
25
26
|
// Not after a `.`: `map.get` and `node.type` are properties, not keywords.
|
|
26
27
|
const words = (list) => `(?<!\\.)\\b(?:${list.split(" ").join("|")})\\b`;
|
|
@@ -120,7 +121,7 @@ export class Syntax {
|
|
|
120
121
|
// Tokenized whole and then sliced, so a block comment or a triple-quoted
|
|
121
122
|
// string that spans lines is one token even when the range starts inside it.
|
|
122
123
|
const text = new RichText(this._lines.join("\n"), { end: "" });
|
|
123
|
-
for (const match of text.plain.matchAll(GRAMMARS[this.language])) {
|
|
124
|
+
for (const match of text.plain.matchAll(new RegExp(GRAMMARS[this.language].source, "g"))) {
|
|
124
125
|
const [token] = Object.entries(match.groups).find(([, value]) => value !== undefined);
|
|
125
126
|
text.stylize(`syntax.${token}`, match.index, match.index + match[0].length);
|
|
126
127
|
}
|
|
@@ -6,43 +6,42 @@ import { Style } from "../core/style.js";
|
|
|
6
6
|
import { Box } from "../core/box.js";
|
|
7
7
|
import { RichText } from "../core/text.js";
|
|
8
8
|
import type { PaddingDimensions } from "./padding.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type { Renderable, Measurable, OverflowMethod, RenderOptions } from "../core/protocol.js";
|
|
10
10
|
export interface ColumnOptions {
|
|
11
11
|
/** Anything a cell can hold, as Rich's `RenderableType` header is. */
|
|
12
|
-
header?: string | RichText | Renderable;
|
|
13
|
-
footer?: string | RichText | Renderable;
|
|
14
|
-
headerStyle?: string | Style;
|
|
15
|
-
footerStyle?: string | Style;
|
|
16
|
-
style?: string | Style;
|
|
17
|
-
justify?: "left" | "center" | "right" | "full";
|
|
18
|
-
width?: number;
|
|
19
|
-
minWidth?: number;
|
|
20
|
-
maxWidth?: number;
|
|
21
|
-
ratio?: number;
|
|
22
|
-
noWrap?: boolean;
|
|
23
|
-
overflow?: OverflowMethod;
|
|
12
|
+
readonly header?: string | RichText | Renderable;
|
|
13
|
+
readonly footer?: string | RichText | Renderable;
|
|
14
|
+
readonly headerStyle?: string | Style;
|
|
15
|
+
readonly footerStyle?: string | Style;
|
|
16
|
+
readonly style?: string | Style;
|
|
17
|
+
readonly justify?: "left" | "center" | "right" | "full";
|
|
18
|
+
readonly width?: number;
|
|
19
|
+
readonly minWidth?: number;
|
|
20
|
+
readonly maxWidth?: number;
|
|
21
|
+
readonly ratio?: number;
|
|
22
|
+
readonly noWrap?: boolean;
|
|
23
|
+
readonly overflow?: OverflowMethod;
|
|
24
24
|
/**
|
|
25
25
|
* Whether the column's string cells — its header and footer among them — are
|
|
26
26
|
* drawn with the console's highlighter, as Rich's `Column(highlight=)`.
|
|
27
27
|
* `false` for a `Column` built alone; `addColumn` takes the table's.
|
|
28
28
|
*/
|
|
29
|
-
highlight?: boolean;
|
|
29
|
+
readonly highlight?: boolean;
|
|
30
30
|
}
|
|
31
31
|
export declare class Column {
|
|
32
|
-
private _header;
|
|
33
|
-
private _footer;
|
|
34
|
-
headerStyle: string | Style;
|
|
35
|
-
footerStyle: string | Style;
|
|
36
|
-
style: string | Style;
|
|
37
|
-
justify: "left" | "center" | "right" | "full";
|
|
38
|
-
width: number | undefined;
|
|
39
|
-
minWidth: number | undefined;
|
|
40
|
-
maxWidth: number | undefined;
|
|
41
|
-
ratio: number | undefined;
|
|
42
|
-
noWrap: boolean;
|
|
43
|
-
overflow: OverflowMethod;
|
|
44
|
-
highlight: boolean;
|
|
45
|
-
private _cells;
|
|
32
|
+
private readonly _header;
|
|
33
|
+
private readonly _footer;
|
|
34
|
+
readonly headerStyle: string | Style;
|
|
35
|
+
readonly footerStyle: string | Style;
|
|
36
|
+
readonly style: string | Style;
|
|
37
|
+
readonly justify: "left" | "center" | "right" | "full";
|
|
38
|
+
readonly width: number | undefined;
|
|
39
|
+
readonly minWidth: number | undefined;
|
|
40
|
+
readonly maxWidth: number | undefined;
|
|
41
|
+
readonly ratio: number | undefined;
|
|
42
|
+
readonly noWrap: boolean;
|
|
43
|
+
readonly overflow: OverflowMethod;
|
|
44
|
+
readonly highlight: boolean;
|
|
46
45
|
constructor(options?: ColumnOptions);
|
|
47
46
|
/**
|
|
48
47
|
* The two stamped cells, stamped on assignment rather than at the constructor.
|
|
@@ -66,10 +65,6 @@ export declare class Column {
|
|
|
66
65
|
get footer(): Renderable & Partial<Measurable>;
|
|
67
66
|
set footer(content: ColumnOptions["footer"]);
|
|
68
67
|
get flexible(): boolean;
|
|
69
|
-
/** @internal */
|
|
70
|
-
addCell(cell: Renderable): void;
|
|
71
|
-
/** @internal */
|
|
72
|
-
getCells(): Renderable[];
|
|
73
68
|
copy(): Column;
|
|
74
69
|
}
|
|
75
70
|
export interface TableOptions {
|
|
@@ -94,13 +89,13 @@ export interface TableOptions {
|
|
|
94
89
|
captionJustify?: "left" | "center" | "right" | "full";
|
|
95
90
|
width?: number;
|
|
96
91
|
minWidth?: number;
|
|
97
|
-
rowStyles?: string[];
|
|
92
|
+
rowStyles?: readonly string[];
|
|
98
93
|
/** The `highlight` every column `addColumn` makes takes unless told otherwise (default: `false`). */
|
|
99
94
|
highlight?: boolean;
|
|
100
95
|
}
|
|
101
|
-
export declare class Table implements Renderable, Measurable
|
|
102
|
-
private _columns;
|
|
103
|
-
private _rows;
|
|
96
|
+
export declare class Table implements Renderable, Measurable {
|
|
97
|
+
private readonly _columns;
|
|
98
|
+
private readonly _rows;
|
|
104
99
|
readonly box: Box | null;
|
|
105
100
|
private readonly _title;
|
|
106
101
|
private readonly _caption;
|
|
@@ -109,7 +104,7 @@ export declare class Table implements Renderable, Measurable, Composite {
|
|
|
109
104
|
readonly showFooter: boolean;
|
|
110
105
|
readonly showLines: boolean;
|
|
111
106
|
readonly showEdge: boolean;
|
|
112
|
-
readonly padding: [number, number, number, number];
|
|
107
|
+
readonly padding: readonly [number, number, number, number];
|
|
113
108
|
readonly collapsePadding: boolean;
|
|
114
109
|
readonly padEdge: boolean;
|
|
115
110
|
readonly style: string | Style;
|
|
@@ -122,12 +117,12 @@ export declare class Table implements Renderable, Measurable, Composite {
|
|
|
122
117
|
readonly captionJustify: "left" | "center" | "right" | "full";
|
|
123
118
|
readonly tableWidth: number | undefined;
|
|
124
119
|
readonly minWidth: number | undefined;
|
|
125
|
-
readonly rowStyles: string[];
|
|
120
|
+
readonly rowStyles: readonly string[];
|
|
126
121
|
readonly highlight: boolean;
|
|
127
122
|
get title(): (Renderable & Measurable) | undefined;
|
|
128
123
|
get caption(): (Renderable & Measurable) | undefined;
|
|
129
124
|
constructor(options?: TableOptions);
|
|
130
|
-
get columns(): Column[];
|
|
125
|
+
get columns(): readonly Column[];
|
|
131
126
|
get rowCount(): number;
|
|
132
127
|
addColumn(header?: ColumnOptions["header"], options?: ColumnOptions): this;
|
|
133
128
|
addRow(...cells: unknown[]): this;
|
|
@@ -201,7 +196,6 @@ export declare class Table implements Renderable, Measurable, Composite {
|
|
|
201
196
|
*/
|
|
202
197
|
private _columnPadding;
|
|
203
198
|
private _rowPadding;
|
|
204
|
-
contents(): Iterable<Renderable>;
|
|
205
199
|
private _rowCells;
|
|
206
200
|
/**
|
|
207
201
|
* Every cell column `index` draws, in draw order.
|