@promptctl/rich-js 0.24.0 → 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/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +243 -0
- package/dist/core/animation.js +375 -0
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +40 -24
- package/dist/core/cells.js +151 -66
- 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 +48 -43
- package/dist/core/easing.d.ts +140 -10
- package/dist/core/easing.js +328 -50
- 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 +3 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +3 -3
- 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/place.js +2 -1
- package/dist/core/pretty.js +11 -12
- package/dist/core/protocol.d.ts +136 -21
- package/dist/core/protocol.js +203 -16
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +27 -6
- package/dist/core/segment.d.ts +31 -6
- package/dist/core/segment.js +68 -53
- 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 +11 -13
- package/dist/core/strip.js +126 -113
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +74 -34
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +133 -133
- package/dist/core/timeline.d.ts +109 -0
- package/dist/core/timeline.js +180 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +71 -0
- 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 +72 -38
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +30 -15
- package/dist/index.js +20 -6
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +1 -1
- package/dist/renderables/columns.js +3 -3
- package/dist/renderables/constrain.js +2 -1
- package/dist/renderables/effect.d.ts +155 -92
- package/dist/renderables/effect.js +203 -136
- package/dist/renderables/effects.d.ts +137 -24
- package/dist/renderables/effects.js +276 -113
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.js +2 -1
- package/dist/renderables/frames.d.ts +45 -43
- package/dist/renderables/frames.js +57 -45
- 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 +1 -1
- package/dist/renderables/group.js +2 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +10 -9
- package/dist/renderables/layout.js +42 -35
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +2 -2
- package/dist/renderables/list-view.js +11 -8
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +89 -42
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1427 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +5 -7
- package/dist/renderables/markdown.js +356 -683
- package/dist/renderables/padding.d.ts +7 -20
- package/dist/renderables/padding.js +29 -50
- package/dist/renderables/panel.d.ts +30 -32
- package/dist/renderables/panel.js +65 -109
- package/dist/renderables/progress.d.ts +83 -32
- package/dist/renderables/progress.js +186 -90
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +33 -10
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +25 -0
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +57 -24
- package/dist/renderables/spinner.js +101 -49
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +5 -3
- package/dist/renderables/table.d.ts +48 -46
- package/dist/renderables/table.js +170 -208
- package/dist/renderables/traceback.d.ts +3 -3
- package/dist/renderables/traceback.js +12 -10
- package/dist/renderables/tree.d.ts +28 -25
- package/dist/renderables/tree.js +108 -58
- package/dist/renderables/viewport.d.ts +77 -38
- package/dist/renderables/viewport.js +156 -72
- 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 +7 -6
- package/dist/themes/ramp.d.ts +62 -4
- package/dist/themes/ramp.js +82 -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 +23 -14
- package/dist/widgets/focus-manager.js +34 -33
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/index.d.ts +1 -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 +1 -1
- package/dist/widgets/static-item.js +6 -7
- package/dist/widgets/text-input.d.ts +53 -47
- package/dist/widgets/text-input.js +456 -399
- 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 +32 -6
- package/dist/widgets/widget-app.js +74 -27
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -21
- package/package.json +9 -5
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
* sized and coloured a source-code excerpt this renderer does not produce.
|
|
17
17
|
*/
|
|
18
18
|
import { Segment } from "../core/segment.js";
|
|
19
|
-
import type
|
|
19
|
+
import { type Renderable, type RenderOptions } from "../core/protocol.js";
|
|
20
20
|
export interface TracebackOptions {
|
|
21
|
-
suppress?: string[];
|
|
21
|
+
suppress?: readonly string[];
|
|
22
22
|
maxFrames?: number;
|
|
23
23
|
}
|
|
24
24
|
export declare class Traceback implements Renderable {
|
|
25
25
|
/** What was caught, as it was handed in. */
|
|
26
26
|
readonly error: unknown;
|
|
27
27
|
readonly maxFrames: number;
|
|
28
|
-
readonly suppress: string[];
|
|
28
|
+
readonly suppress: readonly string[];
|
|
29
29
|
private readonly caught;
|
|
30
30
|
constructor(error: unknown, options?: TracebackOptions);
|
|
31
31
|
render(options: RenderOptions): Iterable<Segment>;
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
import { Segment } from "../core/segment.js";
|
|
19
19
|
import { RichText } from "../core/text.js";
|
|
20
20
|
import { Pretty, UNSEEN_DATA_BOUNDS } from "../core/pretty.js";
|
|
21
|
-
import {
|
|
21
|
+
import { render } from "../core/protocol.js";
|
|
22
|
+
import { paddedContentWidth } from "./padding.js";
|
|
23
|
+
import { pattern } from "../core/pattern.js";
|
|
22
24
|
/**
|
|
23
25
|
* [LAW:parse-dont-validate] The checkpoint between whatever a `catch` caught —
|
|
24
26
|
* JavaScript lets a program throw any value — and the report. Anything with a
|
|
@@ -140,13 +142,13 @@ function parseStack(stack, name, message) {
|
|
|
140
142
|
* any name, so it is read and dropped; the lookahead keeps `at async (…)` — a
|
|
141
143
|
* function named `async` — a name.
|
|
142
144
|
*/
|
|
143
|
-
const V8_FRAME = /^at\s+(?:async\s+(?!\())?(.*)
|
|
145
|
+
const V8_FRAME = pattern(/^at\s+(?:async\s+(?!\())?(.*)$/);
|
|
144
146
|
/**
|
|
145
147
|
* SpiderMonkey and JavaScriptCore write `[fn]@location`, Firefox prefixing an
|
|
146
148
|
* awaited call's name with a cause up to a `*` (`async*`, `promise callback*`),
|
|
147
149
|
* which is dropped as V8's `async` is.
|
|
148
150
|
*/
|
|
149
|
-
const AT_SIGN_FRAME = /^(?:[^@]*\*)?([^@]*)@(.*)
|
|
151
|
+
const AT_SIGN_FRAME = pattern(/^(?:[^@]*\*)?([^@]*)@(.*)$/);
|
|
150
152
|
function parseFrame(text) {
|
|
151
153
|
const v8 = V8_FRAME.exec(text);
|
|
152
154
|
if (v8) {
|
|
@@ -187,9 +189,9 @@ function openingParen(body) {
|
|
|
187
189
|
* the code to it (`file line 8 > eval:1:1`).
|
|
188
190
|
*/
|
|
189
191
|
const LOCATIONS = [
|
|
190
|
-
/^eval at [^]*?\(([^()]+):(\d+):\d+\)
|
|
191
|
-
/^(.*?) line (\d+) > (?:eval|Function)
|
|
192
|
-
/^(.*?):(\d+)(?::\d+)
|
|
192
|
+
pattern(/^eval at [^]*?\(([^()]+):(\d+):\d+\)/),
|
|
193
|
+
pattern(/^(.*?) line (\d+) > (?:eval|Function)/),
|
|
194
|
+
pattern(/^(.*?):(\d+)(?::\d+)?$/),
|
|
193
195
|
];
|
|
194
196
|
function locate(fn, location) {
|
|
195
197
|
for (const shape of LOCATIONS) {
|
|
@@ -225,7 +227,7 @@ export class Traceback {
|
|
|
225
227
|
// width that is left on that line rather than overrunning it.
|
|
226
228
|
header.append(message({ ...options, maxWidth: Math.max(1, options.maxWidth - header.cellLength) }));
|
|
227
229
|
header.append("\n\n");
|
|
228
|
-
yield*
|
|
230
|
+
yield* render(header, options);
|
|
229
231
|
// A suppressed frame keeps its place and shows its file and line only: it
|
|
230
232
|
// is the same frame with no function name to show.
|
|
231
233
|
const frames = this.caught.frames.map((frame) => this.suppress.some((s) => frame.location.includes(s)) ? { ...frame, function: undefined } : frame);
|
|
@@ -253,10 +255,10 @@ export class Traceback {
|
|
|
253
255
|
// frame of its own. The width is divided as `Padding` divides it; the rows
|
|
254
256
|
// are not padded out on the right, as `Padding`'s are, because trailing
|
|
255
257
|
// blanks on every line of a crash report are bytes nobody asked for.
|
|
256
|
-
const {
|
|
257
|
-
const indent = new Segment("
|
|
258
|
+
const frameOptions = { ...options, maxWidth: paddedContentWidth(options.maxWidth, 2, 0) };
|
|
259
|
+
const indent = new Segment(" ");
|
|
258
260
|
for (const entry of entries) {
|
|
259
|
-
for (const line of Segment.splitLines(
|
|
261
|
+
for (const line of Segment.splitLines(render(entry, frameOptions))) {
|
|
260
262
|
yield indent;
|
|
261
263
|
yield* line;
|
|
262
264
|
yield Segment.line();
|
|
@@ -13,13 +13,16 @@ export interface TreeOptions {
|
|
|
13
13
|
}
|
|
14
14
|
export declare class Tree implements Renderable, Measurable {
|
|
15
15
|
readonly label: Renderable;
|
|
16
|
-
readonly
|
|
17
|
-
|
|
16
|
+
private readonly _children;
|
|
17
|
+
private readonly _expanded;
|
|
18
18
|
readonly hideRoot: boolean;
|
|
19
19
|
readonly guideStyle: string | Style;
|
|
20
20
|
readonly style: string | Style;
|
|
21
21
|
constructor(label: string | RichText | Renderable, options?: TreeOptions);
|
|
22
22
|
add(label: string | RichText | Renderable, options?: TreeOptions): Tree;
|
|
23
|
+
get children(): readonly Tree[];
|
|
24
|
+
get expanded(): boolean;
|
|
25
|
+
set expanded(value: boolean);
|
|
23
26
|
render(rawOptions: RenderOptions): Iterable<Segment>;
|
|
24
27
|
/**
|
|
25
28
|
* The rows `render` and `measure` both read, entered in exactly one place so
|
|
@@ -29,7 +32,8 @@ export declare class Tree implements Renderable, Measurable {
|
|
|
29
32
|
*/
|
|
30
33
|
private _walk;
|
|
31
34
|
/**
|
|
32
|
-
*
|
|
35
|
+
* This node's subtree walked into the rows it emits, one row per label,
|
|
36
|
+
* guides first, each guide column counted from this node.
|
|
33
37
|
*
|
|
34
38
|
* [LAW:one-source-of-truth] The walk that decides which rows exist and what
|
|
35
39
|
* leads them is here and only here. `render` turns each row into segments and
|
|
@@ -38,35 +42,34 @@ export declare class Tree implements Renderable, Measurable {
|
|
|
38
42
|
* them run — which they would the moment a second walk existed.
|
|
39
43
|
*
|
|
40
44
|
* [LAW:dataflow-not-control-flow] Every node, at every depth, is walked by
|
|
41
|
-
* this one recursion:
|
|
42
|
-
*
|
|
43
|
-
* style and its guide style are handed down the same way,
|
|
44
|
-
* on the stack its ancestors built.
|
|
45
|
+
* this one recursion: each child's rows get a branch column of their own,
|
|
46
|
+
* settled below the first of them drawn, so no depth is drawn by a rule of
|
|
47
|
+
* its own. A node's style and its guide style are handed down the same way,
|
|
48
|
+
* as one more entry on the stack its ancestors built.
|
|
49
|
+
*
|
|
50
|
+
* The branch is chosen once every child is drawn: a subtree that draws no row
|
|
51
|
+
* takes no place among its siblings, so the last one that draws a row ends
|
|
52
|
+
* the branch, and the branch stands on a subtree's first drawn row — a child
|
|
53
|
+
* that drew nothing above children that did leaves them on its branch, not
|
|
54
|
+
* the sibling's before it. Rich chooses it from the children alone, and
|
|
55
|
+
* draws a fork over a last child that drew nothing — an element not yet
|
|
56
|
+
* arrived, or gone.
|
|
45
57
|
*/
|
|
46
58
|
private _rows;
|
|
59
|
+
/** Each of this node's children walked into its rows, `columns` of guide before each child's own. */
|
|
60
|
+
private _subtrees;
|
|
47
61
|
/**
|
|
48
|
-
* One row of the tree:
|
|
49
|
-
* the label's lines led by the guides.
|
|
50
|
-
*
|
|
51
|
-
* [LAW:single-enforcer] The row's width is divided in exactly one place, here.
|
|
52
|
-
* Before this, each call site emitted its guides and then handed the label
|
|
53
|
-
* `options` unchanged — the label was told it had the whole outer width while
|
|
54
|
-
* the guides had already spent four cells of it, so a tree at maxWidth 5
|
|
55
|
-
* emitted rows of 9. That is the same defect Panel and Table were fixed for,
|
|
56
|
-
* and the same fix: divide the requested width once and let every part read
|
|
57
|
-
* its share off the division. `options` arrives parsed from `render`.
|
|
62
|
+
* One row of the tree: each of the label's lines led by the guides.
|
|
58
63
|
*
|
|
59
|
-
* [LAW:single-enforcer] Tree
|
|
64
|
+
* [LAW:single-enforcer] Tree owns its row boundaries here rather than
|
|
60
65
|
* depending on label renderables to invent trailing newlines: the label's
|
|
61
66
|
* output is cut into lines and every line ends where Tree ends it. A label
|
|
62
|
-
* that emits nothing
|
|
63
|
-
* its own gets the blank row that line is, as in
|
|
64
|
-
* width when the label is justified.
|
|
67
|
+
* that emits nothing — one left no column, say — has no row, and one
|
|
68
|
+
* that ends in a newline of its own gets the blank row that line is, as in
|
|
69
|
+
* Rich — padded to the label's width when the label is justified.
|
|
65
70
|
*
|
|
66
|
-
* The node's
|
|
67
|
-
*
|
|
68
|
-
* guides, so a row with a background is filled from its first guide to the
|
|
69
|
-
* end of its label.
|
|
71
|
+
* The node's background alone reaches under the guides, so a row with a
|
|
72
|
+
* background is filled from its first guide to the end of its label.
|
|
70
73
|
*/
|
|
71
74
|
private _renderRow;
|
|
72
75
|
measure(options: RenderOptions): {
|
package/dist/renderables/tree.js
CHANGED
|
@@ -5,8 +5,9 @@ import { Segment } from "../core/segment.js";
|
|
|
5
5
|
import { cellFit, cellLen, asCellCol } from "../core/cells.js";
|
|
6
6
|
import { Style, NULL_STYLE } from "../core/style.js";
|
|
7
7
|
import { embed } from "./embed.js";
|
|
8
|
-
import { drawable, getStyle, stackedHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
8
|
+
import { drawable, getStyle, render, stackedHeight, withBoundedWidth, withCellWidth } from "../core/protocol.js";
|
|
9
9
|
import { Measurement } from "../core/measure.js";
|
|
10
|
+
import { ListState, State } from "../core/state.js";
|
|
10
11
|
/** Every glyph of every set is this wide, so a row's guides are as wide however they are drawn. */
|
|
11
12
|
const GUIDE_CELLS = 4;
|
|
12
13
|
const LIGHT_GUIDES = { fork: "├── ", end: "└── ", continue: "│ ", space: " " };
|
|
@@ -28,21 +29,38 @@ function glyphsFor(options, style) {
|
|
|
28
29
|
const weight = style.bold === true ? HEAVY_GUIDES : style.underline2 === true ? DOUBLE_GUIDES : LIGHT_GUIDES;
|
|
29
30
|
return drawable(options, weight, ASCII_GUIDES, guideGlyphs);
|
|
30
31
|
}
|
|
32
|
+
/** A row a label is known to draw, unread. */
|
|
33
|
+
const A_ROW = [[]];
|
|
34
|
+
/** The cells a row with `columns` of guide leaves its label. */
|
|
35
|
+
const labelRoom = (options, columns) => Math.max(0, options.maxWidth - columns * GUIDE_CELLS);
|
|
36
|
+
/**
|
|
37
|
+
* `label` drawn in `width` cells, as both `render` and `measure` draw it, so
|
|
38
|
+
* whether it draws a row is answered once. Cropped, so a label that ignores the
|
|
39
|
+
* width it is handed cannot push the row past the offer the guides were fitted
|
|
40
|
+
* into. No highlighter: a label is drawn plain, as Rich's `Tree` hands its
|
|
41
|
+
* labels `highlight=False`.
|
|
42
|
+
*/
|
|
43
|
+
function drawLabel(options, label, width) {
|
|
44
|
+
const labelOptions = { ...options, highlight: false, maxWidth: width, height: stackedHeight(options.height) };
|
|
45
|
+
return Segment.cropLines(render(label, labelOptions), width);
|
|
46
|
+
}
|
|
31
47
|
/** How wide a label wants to be. */
|
|
32
48
|
function labelWidth(options, label) {
|
|
33
49
|
return Measurement.get(options, label).maximum;
|
|
34
50
|
}
|
|
51
|
+
/** Whether a row is drawn: a subtree with one takes a place among its siblings. */
|
|
52
|
+
const isDrawn = (row) => row.lines.length > 0;
|
|
35
53
|
export class Tree {
|
|
36
54
|
label;
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
// Rich's Tree grows by `add` and is expanded or collapsed after it is built.
|
|
56
|
+
_children = new ListState();
|
|
57
|
+
_expanded;
|
|
39
58
|
hideRoot;
|
|
40
59
|
guideStyle;
|
|
41
60
|
style;
|
|
42
61
|
constructor(label, options) {
|
|
43
62
|
this.label = embed(label);
|
|
44
|
-
this.
|
|
45
|
-
this.expanded = options?.expanded !== false;
|
|
63
|
+
this._expanded = new State(options?.expanded !== false);
|
|
46
64
|
this.hideRoot = options?.hideRoot ?? false;
|
|
47
65
|
this.guideStyle = options?.guide_style ?? NULL_STYLE;
|
|
48
66
|
this.style = options?.style ?? NULL_STYLE;
|
|
@@ -53,13 +71,35 @@ export class Tree {
|
|
|
53
71
|
style: options?.style,
|
|
54
72
|
expanded: options?.expanded,
|
|
55
73
|
});
|
|
56
|
-
this.
|
|
74
|
+
this._children.push(child);
|
|
57
75
|
return child;
|
|
58
76
|
}
|
|
77
|
+
get children() {
|
|
78
|
+
return this._children.get();
|
|
79
|
+
}
|
|
80
|
+
get expanded() {
|
|
81
|
+
return this._expanded.get();
|
|
82
|
+
}
|
|
83
|
+
set expanded(value) {
|
|
84
|
+
this._expanded.set(value);
|
|
85
|
+
}
|
|
59
86
|
*render(rawOptions) {
|
|
60
87
|
const options = withBoundedWidth(rawOptions, this);
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
const resolve = (styles) => Style.combine(styles.map((style) => getStyle(options, style)));
|
|
89
|
+
// [LAW:single-enforcer] The row's width is divided in exactly one place,
|
|
90
|
+
// here. Before this, each call site emitted its guides and then handed the
|
|
91
|
+
// label `options` unchanged — the label was told it had the whole outer
|
|
92
|
+
// width while the guides had already spent four cells of it, so a tree at
|
|
93
|
+
// maxWidth 5 emitted rows of 9. That is the same defect Panel and Table
|
|
94
|
+
// were fixed for, and the same fix: divide the requested width once and
|
|
95
|
+
// let every part read its share off the division.
|
|
96
|
+
//
|
|
97
|
+
// The guides are cropped by `cellFit`, the label by `drawLabel`. The
|
|
98
|
+
// node's style sits under the label's own, as Rich's `Styled` puts it, so
|
|
99
|
+
// markup in the label still wins.
|
|
100
|
+
const draw = (label, styles, columns) => Segment.splitLines(Segment.applyStyle(drawLabel(options, label, labelRoom(options, columns)), resolve(styles)));
|
|
101
|
+
for (const row of this._walk(draw)) {
|
|
102
|
+
yield* this._renderRow(options, resolve, row);
|
|
63
103
|
}
|
|
64
104
|
}
|
|
65
105
|
/**
|
|
@@ -68,12 +108,14 @@ export class Tree {
|
|
|
68
108
|
* and the column its children hang from with it, as Rich does — its children
|
|
69
109
|
* stand at the left edge, and its guide style still reaches their guides.
|
|
70
110
|
*/
|
|
71
|
-
_walk() {
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
_walk(draw) {
|
|
112
|
+
return this.hideRoot
|
|
113
|
+
? this._subtrees(0, draw, [this.guideStyle], [this.style]).flat()
|
|
114
|
+
: this._rows(0, draw, [], []);
|
|
74
115
|
}
|
|
75
116
|
/**
|
|
76
|
-
*
|
|
117
|
+
* This node's subtree walked into the rows it emits, one row per label,
|
|
118
|
+
* guides first, each guide column counted from this node.
|
|
77
119
|
*
|
|
78
120
|
* [LAW:one-source-of-truth] The walk that decides which rows exist and what
|
|
79
121
|
* leads them is here and only here. `render` turns each row into segments and
|
|
@@ -82,50 +124,54 @@ export class Tree {
|
|
|
82
124
|
* them run — which they would the moment a second walk existed.
|
|
83
125
|
*
|
|
84
126
|
* [LAW:dataflow-not-control-flow] Every node, at every depth, is walked by
|
|
85
|
-
* this one recursion:
|
|
86
|
-
*
|
|
87
|
-
* style and its guide style are handed down the same way,
|
|
88
|
-
* on the stack its ancestors built.
|
|
127
|
+
* this one recursion: each child's rows get a branch column of their own,
|
|
128
|
+
* settled below the first of them drawn, so no depth is drawn by a rule of
|
|
129
|
+
* its own. A node's style and its guide style are handed down the same way,
|
|
130
|
+
* as one more entry on the stack its ancestors built.
|
|
131
|
+
*
|
|
132
|
+
* The branch is chosen once every child is drawn: a subtree that draws no row
|
|
133
|
+
* takes no place among its siblings, so the last one that draws a row ends
|
|
134
|
+
* the branch, and the branch stands on a subtree's first drawn row — a child
|
|
135
|
+
* that drew nothing above children that did leaves them on its branch, not
|
|
136
|
+
* the sibling's before it. Rich chooses it from the children alone, and
|
|
137
|
+
* draws a fork over a last child that drew nothing — an element not yet
|
|
138
|
+
* arrived, or gone.
|
|
89
139
|
*/
|
|
90
|
-
|
|
140
|
+
_rows(columns, draw, inheritedGuideStyles, inheritedStyles) {
|
|
91
141
|
const styles = [...inheritedStyles, this.style];
|
|
92
|
-
yield { guides, label: this.label, styles };
|
|
93
|
-
const children = this.expanded ? this.children : [];
|
|
94
142
|
const guideStyles = [...inheritedGuideStyles, this.guideStyle];
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
143
|
+
const own = { guides: [], label: this.label, styles, lines: draw(this.label, styles, columns) };
|
|
144
|
+
const subtrees = this._subtrees(columns + 1, draw, guideStyles, styles);
|
|
145
|
+
const firsts = subtrees.map((rows) => rows.findIndex(isDrawn));
|
|
146
|
+
const last = firsts.reduce((found, first, i) => (first >= 0 ? i : found), -1);
|
|
147
|
+
const branched = subtrees.flatMap((rows, i) => {
|
|
148
|
+
const branch = i < last
|
|
149
|
+
? { first: "fork", rest: "continue", styles: guideStyles }
|
|
150
|
+
: { first: "end", rest: "space", styles: guideStyles };
|
|
151
|
+
const settled = { ...branch, first: branch.rest };
|
|
152
|
+
return rows.map((row, r) => ({ ...row, guides: [r === firsts[i] ? branch : settled, ...row.guides] }));
|
|
153
|
+
});
|
|
154
|
+
return [own, ...branched];
|
|
155
|
+
}
|
|
156
|
+
/** Each of this node's children walked into its rows, `columns` of guide before each child's own. */
|
|
157
|
+
_subtrees(columns, draw, guideStyles, styles) {
|
|
158
|
+
const children = this.expanded ? this.children : [];
|
|
159
|
+
return children.map((child) => child._rows(columns, draw, guideStyles, styles));
|
|
102
160
|
}
|
|
103
161
|
/**
|
|
104
|
-
* One row of the tree:
|
|
105
|
-
* the label's lines led by the guides.
|
|
106
|
-
*
|
|
107
|
-
* [LAW:single-enforcer] The row's width is divided in exactly one place, here.
|
|
108
|
-
* Before this, each call site emitted its guides and then handed the label
|
|
109
|
-
* `options` unchanged — the label was told it had the whole outer width while
|
|
110
|
-
* the guides had already spent four cells of it, so a tree at maxWidth 5
|
|
111
|
-
* emitted rows of 9. That is the same defect Panel and Table were fixed for,
|
|
112
|
-
* and the same fix: divide the requested width once and let every part read
|
|
113
|
-
* its share off the division. `options` arrives parsed from `render`.
|
|
162
|
+
* One row of the tree: each of the label's lines led by the guides.
|
|
114
163
|
*
|
|
115
|
-
* [LAW:single-enforcer] Tree
|
|
164
|
+
* [LAW:single-enforcer] Tree owns its row boundaries here rather than
|
|
116
165
|
* depending on label renderables to invent trailing newlines: the label's
|
|
117
166
|
* output is cut into lines and every line ends where Tree ends it. A label
|
|
118
|
-
* that emits nothing
|
|
119
|
-
* its own gets the blank row that line is, as in
|
|
120
|
-
* width when the label is justified.
|
|
167
|
+
* that emits nothing — one left no column, say — has no row, and one
|
|
168
|
+
* that ends in a newline of its own gets the blank row that line is, as in
|
|
169
|
+
* Rich — padded to the label's width when the label is justified.
|
|
121
170
|
*
|
|
122
|
-
* The node's
|
|
123
|
-
*
|
|
124
|
-
* guides, so a row with a background is filled from its first guide to the
|
|
125
|
-
* end of its label.
|
|
171
|
+
* The node's background alone reaches under the guides, so a row with a
|
|
172
|
+
* background is filled from its first guide to the end of its label.
|
|
126
173
|
*/
|
|
127
|
-
*_renderRow(options, row) {
|
|
128
|
-
const resolve = (styles) => Style.combine(styles.map((style) => getStyle(options, style)));
|
|
174
|
+
*_renderRow(options, resolve, row) {
|
|
129
175
|
const style = resolve(row.styles);
|
|
130
176
|
const drawn = row.guides.map((guide) => {
|
|
131
177
|
const guideStyle = resolve(guide.styles);
|
|
@@ -134,15 +180,7 @@ export class Tree {
|
|
|
134
180
|
style: style.backgroundStyle.add(guideStyle).add(REMOVE_GUIDE_STYLES),
|
|
135
181
|
};
|
|
136
182
|
});
|
|
137
|
-
const
|
|
138
|
-
// The guides are cropped by `cellFit`; the label is cropped too, so a label
|
|
139
|
-
// that ignores the width it is handed cannot push the row past the offer the
|
|
140
|
-
// guides were fitted into. No highlighter: a label is drawn plain, as
|
|
141
|
-
// Rich's `Tree` hands its labels `highlight=False`.
|
|
142
|
-
const labelOptions = { ...options, highlight: false, maxWidth: width, height: stackedHeight(options.height) };
|
|
143
|
-
const lines = Segment.splitLines(Segment.applyStyle(Segment.cropLines(row.label.render(labelOptions), width), style));
|
|
144
|
-
if (lines.length === 0)
|
|
145
|
-
lines.push([]);
|
|
183
|
+
const lines = row.lines;
|
|
146
184
|
for (let i = 0; i < lines.length; i++) {
|
|
147
185
|
let left = options.maxWidth;
|
|
148
186
|
for (let g = 0; g < row.guides.length; g++) {
|
|
@@ -164,10 +202,22 @@ export class Tree {
|
|
|
164
202
|
// The widest row the walk emits, not the offer. Reported as the offer, a
|
|
165
203
|
// tree told any parent that asked "I want all of it" — so `Panel` in fit
|
|
166
204
|
// mode drew a 40-cell frame around nine cells of tree, and an unbounded
|
|
167
|
-
// offer came back unbounded.
|
|
205
|
+
// offer came back unbounded. A row the walk does not draw wants nothing.
|
|
206
|
+
// A label that wants cells draws them, so only one that wants none can
|
|
207
|
+
// draw no row, and only that one is drawn to ask — in the one column it
|
|
208
|
+
// wants, as its row does, because under one column nothing draws. A label
|
|
209
|
+
// of no width that does draw is a blank row, as wide as its guides and that
|
|
210
|
+
// column: the width the tree, drawn at what this reports, hands it.
|
|
211
|
+
const widths = new Map();
|
|
212
|
+
const widthOf = (label) => {
|
|
213
|
+
const width = widths.get(label) ?? labelWidth(parsed, label);
|
|
214
|
+
widths.set(label, width);
|
|
215
|
+
return width;
|
|
216
|
+
};
|
|
217
|
+
const draw = (label, _styles, columns) => widthOf(label) > 0 ? A_ROW : Segment.splitLines(drawLabel(parsed, label, Math.min(labelRoom(parsed, columns), 1)));
|
|
168
218
|
let natural = 0;
|
|
169
|
-
for (const row of this._walk()) {
|
|
170
|
-
natural = Math.max(natural, row.guides.length * GUIDE_CELLS +
|
|
219
|
+
for (const row of this._walk(draw).filter(isDrawn)) {
|
|
220
|
+
natural = Math.max(natural, row.guides.length * GUIDE_CELLS + Math.max(1, widthOf(row.label)));
|
|
171
221
|
}
|
|
172
222
|
const maximum = Math.min(natural, ceiling);
|
|
173
223
|
// A row is its guides plus at least one cell of label — but never more than
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Viewport — a fixed number of rows onto
|
|
3
|
-
* offset that cannot leave the
|
|
2
|
+
* Viewport — a scroll position: a fixed number of rows onto taller content,
|
|
3
|
+
* scrolled by an offset that cannot leave it. It holds the position, not the
|
|
4
|
+
* content: content is drawn through it with `showing` (or `showingLines`), so
|
|
5
|
+
* a view rebuilt every frame keeps one viewport under content it builds anew.
|
|
6
|
+
* [LAW:one-source-of-truth] That is the one way to draw through it, so no
|
|
7
|
+
* viewport is built around content it never draws.
|
|
4
8
|
*
|
|
5
9
|
* Its rows, under the `Height` contract in `../core/protocol.ts`: the region's
|
|
6
10
|
* when it is given one; otherwise its own configured rows, or the content's
|
|
@@ -19,11 +23,18 @@
|
|
|
19
23
|
* against exists before a render: the rows come from the budget the render is
|
|
20
24
|
* handed, and the content's length from rendering it at the width it is handed.
|
|
21
25
|
* So `scrollTo`, `scrollBy` and `ensureVisible` do not move anything when they
|
|
22
|
-
* are called. Each queues a move, and the next render
|
|
23
|
-
* call order, clamping after every move, against the
|
|
24
|
-
* render found. Calling an operation before the first render, or three of them
|
|
26
|
+
* are called. Each queues a move, and the next render that draws the content
|
|
27
|
+
* resolves the queue in call order, clamping after every move, against the
|
|
28
|
+
* rows and length that render found. Calling an operation before the first render, or three of them
|
|
25
29
|
* between two renders, is the same code path as calling one.
|
|
26
30
|
*
|
|
31
|
+
* A render changes nothing (`State`, `../core/state.ts`), so the render draws
|
|
32
|
+
* the offset it resolved without keeping it, and hands the keeping to its pass
|
|
33
|
+
* (`afterPass`): once the frame has been drawn whole, the offset, the rows and
|
|
34
|
+
* length it was clamped against, and the followed range become the viewport's,
|
|
35
|
+
* and the moves it resolved leave the queue. A measurement, which draws
|
|
36
|
+
* nothing anyone sees, keeps nothing.
|
|
37
|
+
*
|
|
27
38
|
* Every cell of its rows, gutter included, is stamped as its own
|
|
28
39
|
* (`Segment.anchorLines`), around whatever anchors its content drew. So the
|
|
29
40
|
* composed frame names the viewport under any of its cells however deep it is
|
|
@@ -34,6 +45,24 @@
|
|
|
34
45
|
import { Segment } from "../core/segment.js";
|
|
35
46
|
import type { Style } from "../core/style.js";
|
|
36
47
|
import type { Measurable, Renderable, RenderOptions, Scrollable } from "../core/protocol.js";
|
|
48
|
+
/** Lines `start` up to but not including `end` of the content. */
|
|
49
|
+
interface Range {
|
|
50
|
+
readonly start: number;
|
|
51
|
+
readonly end: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Content a viewport shows by the line: laid out at a width, it says how many
|
|
55
|
+
* lines it is and draws only the ones asked for. What a view too long to draw
|
|
56
|
+
* whole every frame — a textarea's rows — is shown as (`showingLines`).
|
|
57
|
+
*/
|
|
58
|
+
export interface Lines extends Measurable {
|
|
59
|
+
layout(options: RenderOptions): LaidLines;
|
|
60
|
+
}
|
|
61
|
+
/** Content laid out at one width: its length, and its lines `start` up to but not including `end`. */
|
|
62
|
+
export interface LaidLines {
|
|
63
|
+
readonly length: number;
|
|
64
|
+
lines(start: number, end: number): Segment[][];
|
|
65
|
+
}
|
|
37
66
|
/** How one part of a scrollbar draws each row it covers. */
|
|
38
67
|
export interface ScrollbarPart {
|
|
39
68
|
readonly glyph: string;
|
|
@@ -60,22 +89,15 @@ export interface ViewportOptions {
|
|
|
60
89
|
/** A scrollbar down the right edge. Absent, the content has the full width. */
|
|
61
90
|
scrollbar?: Scrollbar;
|
|
62
91
|
}
|
|
63
|
-
export declare class Viewport implements
|
|
64
|
-
/**
|
|
65
|
-
* What the viewport shows. Replace it to show new content from the same
|
|
66
|
-
* scroll position — a view rebuilt every frame keeps one `Viewport`.
|
|
67
|
-
*/
|
|
68
|
-
content: Renderable;
|
|
92
|
+
export declare class Viewport implements Scrollable {
|
|
69
93
|
readonly rows: number | undefined;
|
|
70
94
|
readonly scrollbar: Scrollbar;
|
|
71
|
-
private
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
private _followed;
|
|
75
|
-
constructor(content: Renderable, options?: ViewportOptions);
|
|
95
|
+
private readonly _scroll;
|
|
96
|
+
private readonly _moves;
|
|
97
|
+
constructor(options?: ViewportOptions);
|
|
76
98
|
/**
|
|
77
|
-
* The first content line the last
|
|
78
|
-
* not reflected until the next
|
|
99
|
+
* The first content line the last frame showed. A move requested since is
|
|
100
|
+
* not reflected until the next frame resolves it.
|
|
79
101
|
*/
|
|
80
102
|
get offset(): number;
|
|
81
103
|
/** Scroll so `line` is the first line shown. */
|
|
@@ -84,7 +106,7 @@ export declare class Viewport implements Renderable, Measurable, Scrollable {
|
|
|
84
106
|
scrollBy(lines: number): void;
|
|
85
107
|
/**
|
|
86
108
|
* Whether `scrollBy(lines)` would move the viewport from where the moves
|
|
87
|
-
* queued so far leave it, against the rows and length the last
|
|
109
|
+
* queued so far leave it, against the rows and length the last frame
|
|
88
110
|
* found.
|
|
89
111
|
*/
|
|
90
112
|
canScrollBy(lines: number): boolean;
|
|
@@ -96,16 +118,30 @@ export declare class Viewport implements Renderable, Measurable, Scrollable {
|
|
|
96
118
|
*/
|
|
97
119
|
ensureVisible(start: number, end: number): void;
|
|
98
120
|
/**
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
121
|
+
* `content` shown through this viewport from its scroll position. What it
|
|
122
|
+
* draws is anchored to this viewport, so the wheel finds it.
|
|
123
|
+
*
|
|
124
|
+
* `follow` is the lines of `content` to keep in view — a selection's — and
|
|
125
|
+
* is brought into view with `ensureVisible` when it differs from the range
|
|
126
|
+
* the last frame followed: when the selection moves, not on every frame,
|
|
127
|
+
* which would undo on its very render each scroll the mouse wheel queues. A
|
|
128
|
+
* followed range the last frame showed whole stays shown when the row count
|
|
129
|
+
* changes: the same lines can fall off the bottom of a viewport that got
|
|
130
|
+
* shorter. One the wheel had scrolled away from stays away. Absent, the
|
|
131
|
+
* range last followed stands.
|
|
132
|
+
*
|
|
133
|
+
* [LAW:effects-at-boundaries] What a frame drawing it resolves becomes the
|
|
134
|
+
* viewport's once that frame has been drawn whole, so it may be built and
|
|
135
|
+
* drawn inside a render — `ListView` draws its items to learn their lines
|
|
136
|
+
* and shows them this way.
|
|
137
|
+
*/
|
|
138
|
+
showing(content: Renderable, follow?: Range): Renderable & Measurable;
|
|
139
|
+
/**
|
|
140
|
+
* `content` shown through this viewport as `showing` shows a renderable,
|
|
141
|
+
* drawing only the lines the frame shows: a frame costs the rows in view,
|
|
142
|
+
* not the content's whole length.
|
|
107
143
|
*/
|
|
108
|
-
|
|
144
|
+
showingLines(content: Lines, follow?: Range): Renderable & Measurable;
|
|
109
145
|
/**
|
|
110
146
|
* The width its content renders at, out of `width`: what the scrollbar's
|
|
111
147
|
* gutter leaves. Lines handed to `ensureVisible` are lines at this width.
|
|
@@ -113,18 +149,20 @@ export declare class Viewport implements Renderable, Measurable, Scrollable {
|
|
|
113
149
|
contentWidth(width: number): number;
|
|
114
150
|
/** The cells the scrollbar's gutter takes beside the content. */
|
|
115
151
|
get gutter(): number;
|
|
116
|
-
|
|
152
|
+
/** The scroll, and the moves queued against it, as they stand. */
|
|
153
|
+
private asked;
|
|
117
154
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* [LAW:
|
|
121
|
-
*
|
|
155
|
+
* The offset `settled` resolves, with the scroll it leaves kept once the frame
|
|
156
|
+
* is drawn and the `resolved` moves gone from the queue.
|
|
157
|
+
* [LAW:effects-at-boundaries] Kept once the frame is drawn, never while it is.
|
|
158
|
+
* The resolved moves leave by identity, not by count: another frame drawing
|
|
159
|
+
* this viewport in the same pass may already have taken them, and a move
|
|
160
|
+
* queued since stays.
|
|
122
161
|
*/
|
|
123
|
-
private
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
};
|
|
162
|
+
private keep;
|
|
163
|
+
/** `content`, laid out by `layout`, drawn from the scroll and the moves queued against it, with `follow` followed. */
|
|
164
|
+
private draw;
|
|
165
|
+
private measureOf;
|
|
128
166
|
}
|
|
129
167
|
/**
|
|
130
168
|
* One viewport per thing a pane shows, named by a key. The same viewport comes
|
|
@@ -142,3 +180,4 @@ export declare class ViewportPerKey {
|
|
|
142
180
|
of(key: string): Viewport;
|
|
143
181
|
private fresh;
|
|
144
182
|
}
|
|
183
|
+
export {};
|