@promptctl/rich-js 0.17.0 → 0.18.1
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 +13 -26
- package/dist/core/anchor.d.ts +45 -0
- package/dist/core/anchor.js +39 -0
- package/dist/core/ansi.d.ts +65 -0
- package/dist/core/ansi.js +231 -0
- package/dist/core/box.d.ts +0 -1
- package/dist/core/box.js +6 -18
- package/dist/core/cells.d.ts +16 -5
- package/dist/core/cells.js +31 -10
- package/dist/core/color.d.ts +4 -8
- package/dist/core/color.js +28 -19
- package/dist/core/console.d.ts +13 -4
- package/dist/core/console.js +91 -36
- package/dist/core/export-html.d.ts +40 -5
- package/dist/core/export-html.js +111 -17
- package/dist/core/markup.d.ts +27 -5
- package/dist/core/markup.js +282 -170
- package/dist/core/measure.d.ts +1 -1
- package/dist/core/measure.js +2 -1
- package/dist/core/place.d.ts +30 -0
- package/dist/core/place.js +51 -0
- package/dist/core/pretty.d.ts +3 -0
- package/dist/core/pretty.js +5 -1
- package/dist/core/protocol.d.ts +136 -6
- package/dist/core/protocol.js +64 -2
- package/dist/core/render.d.ts +2 -2
- package/dist/core/render.js +2 -2
- package/dist/core/segment.d.ts +23 -4
- package/dist/core/segment.js +75 -16
- package/dist/core/spinnerData.d.ts +2 -0
- package/dist/core/spinnerData.js +3 -1
- package/dist/core/strip.d.ts +3 -7
- package/dist/core/strip.js +32 -35
- package/dist/core/style.d.ts +25 -0
- package/dist/core/style.js +61 -89
- package/dist/core/text.d.ts +16 -15
- package/dist/core/text.js +62 -76
- package/dist/host/app.d.ts +121 -0
- package/dist/host/app.js +255 -0
- package/dist/host/host-environment.d.ts +26 -0
- package/dist/host/host-environment.js +44 -0
- package/dist/host/index.d.ts +4 -2
- package/dist/host/index.js +2 -1
- package/dist/host/terminal-host.d.ts +43 -7
- package/dist/host/terminal-host.js +57 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -1
- package/dist/node/terminal-host.d.ts +31 -2
- package/dist/node/terminal-host.js +69 -2
- package/dist/node/traceback.d.ts +16 -6
- package/dist/node/traceback.js +30 -8
- package/dist/renderables/align.d.ts +2 -2
- package/dist/renderables/align.js +6 -19
- package/dist/renderables/columns.js +4 -20
- package/dist/renderables/embed.d.ts +50 -0
- package/dist/renderables/embed.js +71 -0
- package/dist/renderables/group.js +3 -1
- package/dist/renderables/layout.js +31 -34
- package/dist/renderables/live.d.ts +6 -3
- package/dist/renderables/live.js +89 -45
- package/dist/renderables/markdown.d.ts +1 -1
- package/dist/renderables/markdown.js +6 -4
- package/dist/renderables/padding.js +3 -2
- package/dist/renderables/panel.js +26 -41
- package/dist/renderables/progress.js +5 -8
- package/dist/renderables/progressBar.js +6 -5
- package/dist/renderables/rule.d.ts +3 -2
- package/dist/renderables/rule.js +9 -9
- package/dist/renderables/spinner.d.ts +5 -3
- package/dist/renderables/spinner.js +14 -8
- package/dist/renderables/syntax.d.ts +1 -1
- package/dist/renderables/syntax.js +2 -1
- package/dist/renderables/table.d.ts +26 -14
- package/dist/renderables/table.js +239 -141
- package/dist/renderables/traceback.js +8 -9
- package/dist/renderables/tree.d.ts +17 -3
- package/dist/renderables/tree.js +62 -71
- package/dist/renderables/viewport.d.ts +114 -0
- package/dist/renderables/viewport.js +198 -0
- package/dist/template-bindings/color-funcs.js +11 -13
- package/dist/template-bindings/index.js +4 -2
- package/dist/template-bindings/palette-funcs.js +6 -16
- package/dist/template-bindings/style-funcs.js +4 -0
- package/dist/themes/data/ansi-tables.d.ts +14 -0
- package/dist/themes/data/ansi-tables.js +49 -0
- package/dist/themes/data/atom-one-dark.js +19 -0
- package/dist/themes/data/atom-one-light.js +19 -0
- package/dist/themes/data/catppuccin-frappe.js +19 -0
- package/dist/themes/data/catppuccin-latte.js +19 -0
- package/dist/themes/data/catppuccin-macchiato.js +19 -0
- package/dist/themes/data/catppuccin-mocha.js +19 -0
- package/dist/themes/data/cyberpunk.js +2 -0
- package/dist/themes/data/default.js +2 -0
- package/dist/themes/data/dracula.js +19 -0
- package/dist/themes/data/flexoki.js +19 -0
- package/dist/themes/data/gruvbox.js +19 -0
- package/dist/themes/data/monokai.js +19 -0
- package/dist/themes/data/nord.js +19 -0
- package/dist/themes/data/rose-pine-dawn.js +19 -0
- package/dist/themes/data/rose-pine-moon.js +19 -0
- package/dist/themes/data/rose-pine.js +19 -0
- package/dist/themes/data/solarized-dark.js +19 -0
- package/dist/themes/data/solarized-light.js +19 -0
- package/dist/themes/data/svg-export.js +19 -0
- package/dist/themes/data/textual-ansi.js +2 -0
- package/dist/themes/data/textual-dark.js +2 -0
- package/dist/themes/data/textual-light.js +2 -0
- package/dist/themes/data/tokyo-night.js +19 -0
- package/dist/themes/data/types.d.ts +16 -0
- package/dist/themes/data/types.js +10 -1
- package/dist/themes/registry.d.ts +3 -2
- package/dist/themes/registry.js +13 -1
- package/dist/themes/terminalThemes.d.ts +2 -7
- package/dist/themes/terminalThemes.js +4 -9
- package/dist/widgets/button.d.ts +1 -1
- package/dist/widgets/button.js +4 -2
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +5 -3
- package/dist/widgets/dropdown.d.ts +2 -2
- package/dist/widgets/dropdown.js +13 -16
- package/dist/widgets/event-router.d.ts +21 -28
- package/dist/widgets/event-router.js +80 -73
- package/dist/widgets/focus-manager.d.ts +25 -6
- package/dist/widgets/focus-manager.js +63 -65
- package/dist/widgets/hit.d.ts +64 -0
- package/dist/widgets/hit.js +113 -0
- package/dist/widgets/index.d.ts +5 -7
- package/dist/widgets/index.js +3 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/slider.js +7 -7
- package/dist/widgets/static-item.d.ts +9 -15
- package/dist/widgets/static-item.js +9 -15
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +17 -14
- package/dist/widgets/toggle.d.ts +1 -1
- package/dist/widgets/toggle.js +3 -2
- package/dist/widgets/types.d.ts +14 -35
- package/dist/widgets/types.js +0 -1
- package/dist/widgets/widget-app.d.ts +63 -0
- package/dist/widgets/widget-app.js +199 -0
- package/dist/widgets/widget-base.d.ts +4 -6
- package/dist/widgets/widget-base.js +22 -30
- package/package.json +12 -4
- package/dist/host/host-stream.d.ts +0 -30
- package/dist/host/host-stream.js +0 -35
- package/dist/widgets/screen.d.ts +0 -98
- package/dist/widgets/screen.js +0 -530
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* place — a renderable's output set down as one block inside a wider width.
|
|
3
|
+
*
|
|
4
|
+
* The block is placed, not its lines. The renderable is measured, drawn at the
|
|
5
|
+
* width it asked for and at its own height — a region's height is dropped, as
|
|
6
|
+
* the reference's `Align` drops it — and every line is padded to the widest, so the block is a
|
|
7
|
+
* rectangle; only then is the rectangle padded to the offered width. A
|
|
8
|
+
* multi-line paragraph keeps its own shape under `"center"` — `hi` over `hello`
|
|
9
|
+
* stays a 5-cell block, centred as one — where padding each line to the full
|
|
10
|
+
* width on its own would centre `hi` against the whole terminal. That is
|
|
11
|
+
* Rich's `Align`, and `test/core/console.test.ts` pins it against the
|
|
12
|
+
* reference's bytes.
|
|
13
|
+
*
|
|
14
|
+
* It lives in `core/` because `Console.print` places with it: Rich's `print`
|
|
15
|
+
* wraps what it prints in `Align` for the three alignments, and `console.ts`
|
|
16
|
+
* reaching for `renderables/align.ts` would be a third upward edge out of this
|
|
17
|
+
* layer. [LAW:one-way-deps] `Align` is this function behind the `Renderable`
|
|
18
|
+
* interface. [LAW:one-source-of-truth]
|
|
19
|
+
*/
|
|
20
|
+
import { Segment } from "./segment.js";
|
|
21
|
+
import { Measurement } from "./measure.js";
|
|
22
|
+
import { isMeasurable } from "./protocol.js";
|
|
23
|
+
// The share of the spare cells that goes on the left; the rest go on the right.
|
|
24
|
+
// Right-alignment pads no right side at all, which is the reference's choice
|
|
25
|
+
// and not a zero: a line that ends at the edge carries no trailing blanks.
|
|
26
|
+
const PADS = {
|
|
27
|
+
left: (spare) => [0, spare],
|
|
28
|
+
center: (spare) => [Math.floor(spare / 2), spare - Math.floor(spare / 2)],
|
|
29
|
+
right: (spare) => [spare, 0],
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* `renderable`'s lines, the block they form placed by `align` in
|
|
33
|
+
* `options.maxWidth`. `options.maxWidth` must be finite: the spare cells are
|
|
34
|
+
* written out as spaces. A block wider than the offer — a line with nowhere to
|
|
35
|
+
* break, drawn by a renderable that does not cut its own — is left unpadded at
|
|
36
|
+
* its full width, for whatever holds it to crop or keep.
|
|
37
|
+
*/
|
|
38
|
+
export function placeBlock(renderable, align, options) {
|
|
39
|
+
// A renderable that cannot say how wide it wants to be is offered all of it,
|
|
40
|
+
// and the block is then only as wide as what it actually drew.
|
|
41
|
+
const width = isMeasurable(renderable)
|
|
42
|
+
? Measurement.get(options, renderable).maximum
|
|
43
|
+
: options.maxWidth;
|
|
44
|
+
const drawn = Segment.splitLines(renderable.render({ ...options, maxWidth: width, height: undefined }));
|
|
45
|
+
const [blockWidth, height] = Segment.getShape(drawn);
|
|
46
|
+
const [left, right] = PADS[align](Math.max(0, options.maxWidth - blockWidth));
|
|
47
|
+
return Segment.setShape(drawn, blockWidth, height).map((line) => [...pad(left), ...line, ...pad(right)]);
|
|
48
|
+
}
|
|
49
|
+
// [LAW:dataflow-not-control-flow] no spare room is a pad of no segments, not a
|
|
50
|
+
// step skipped.
|
|
51
|
+
const pad = (cells) => (cells > 0 ? [new Segment(" ".repeat(cells))] : []);
|
package/dist/core/pretty.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* times.
|
|
18
18
|
*/
|
|
19
19
|
import { Segment } from "./segment.js";
|
|
20
|
+
import { RichText } from "./text.js";
|
|
20
21
|
import type { Highlighter } from "./highlighter.js";
|
|
21
22
|
import type { Renderable, Measurable, RenderOptions } from "./protocol.js";
|
|
22
23
|
export interface PrettyOptions {
|
|
@@ -56,6 +57,8 @@ export declare class Pretty implements Renderable, Measurable {
|
|
|
56
57
|
readonly highlighter: Highlighter;
|
|
57
58
|
constructor(data: unknown, options?: PrettyOptions);
|
|
58
59
|
render(options: RenderOptions): Iterable<Segment>;
|
|
60
|
+
/** The value laid out for `options.maxWidth` and highlighted: the text this renders. */
|
|
61
|
+
toText(options: RenderOptions): RichText;
|
|
59
62
|
measure(_options: RenderOptions): {
|
|
60
63
|
minimum: number;
|
|
61
64
|
maximum: number;
|
package/dist/core/pretty.js
CHANGED
|
@@ -163,13 +163,17 @@ export class Pretty {
|
|
|
163
163
|
this.highlighter = options?.highlighter ?? reprHighlighter;
|
|
164
164
|
}
|
|
165
165
|
*render(options) {
|
|
166
|
+
yield* this.toText(options).render(options);
|
|
167
|
+
}
|
|
168
|
+
/** The value laid out for `options.maxWidth` and highlighted: the text this renders. */
|
|
169
|
+
toText(options) {
|
|
166
170
|
const formatted = this._format(this.data, rootFrame(options.maxWidth));
|
|
167
171
|
const text = new RichText(formatted, { end: "" });
|
|
168
172
|
this.highlighter.highlight(text);
|
|
169
173
|
if (this.indentGuides) {
|
|
170
174
|
this._addIndentGuides(text);
|
|
171
175
|
}
|
|
172
|
-
|
|
176
|
+
return text;
|
|
173
177
|
}
|
|
174
178
|
measure(_options) {
|
|
175
179
|
const formatted = this._format(this.data, rootFrame(_options.maxWidth));
|
package/dist/core/protocol.d.ts
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
import type { Segment } from "./segment.js";
|
|
6
6
|
import { type Style, type StyleSyntaxError, type Theme } from "./style.js";
|
|
7
7
|
import type { ColorDepth } from "./color.js";
|
|
8
|
+
/**
|
|
9
|
+
* What becomes of a line still wider than its width once wrapping is done.
|
|
10
|
+
* `"fold"` breaks it across lines, `"crop"` cuts it at the edge, `"ellipsis"`
|
|
11
|
+
* cuts it and marks the cut. `"ignore"` is not a way of cutting: the line has
|
|
12
|
+
* no edge, so nothing wraps it and nothing cuts it, and it leaves at its
|
|
13
|
+
* natural width for whatever drew it to crop or not.
|
|
14
|
+
*
|
|
15
|
+
* `noWrap` is the other half of Rich's pair and is not `"ignore"`: it stops the
|
|
16
|
+
* wrapping and leaves the edge, so a line too wide is cut by the method here.
|
|
17
|
+
*/
|
|
18
|
+
export type OverflowMethod = "fold" | "crop" | "ellipsis" | "ignore";
|
|
8
19
|
export interface RenderOptions {
|
|
9
20
|
/**
|
|
10
21
|
* The cells this renderable may occupy. A count of cells, so a non-negative
|
|
@@ -15,14 +26,20 @@ export interface RenderOptions {
|
|
|
15
26
|
*/
|
|
16
27
|
maxWidth: number;
|
|
17
28
|
minWidth?: number;
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The rows this renderable may occupy. Absent, nothing limits them.
|
|
31
|
+
*/
|
|
32
|
+
height?: Height;
|
|
20
33
|
isTerminal?: boolean;
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The output can draw only ASCII. Every glyph a renderable chooses — box
|
|
36
|
+
* lines, tree guides, a check mark — is resolved through `drawable`. Text
|
|
37
|
+
* the caller hands in is drawn as given. Absent means the output draws any
|
|
38
|
+
* glyph.
|
|
39
|
+
*/
|
|
23
40
|
asciiOnly?: boolean;
|
|
24
41
|
justify?: "left" | "center" | "right" | "full";
|
|
25
|
-
overflow?:
|
|
42
|
+
overflow?: OverflowMethod;
|
|
26
43
|
noWrap?: boolean;
|
|
27
44
|
highlight?: unknown;
|
|
28
45
|
markup?: unknown;
|
|
@@ -46,7 +63,95 @@ export interface RenderOptions {
|
|
|
46
63
|
* the render strict: the error leaves `render`.
|
|
47
64
|
*/
|
|
48
65
|
onStyleError?: StyleErrorHandler;
|
|
66
|
+
/**
|
|
67
|
+
* Told of each owner that stamps its output (`core/anchor`) as it starts to
|
|
68
|
+
* render, so the calls arrive in document order: a container's children in
|
|
69
|
+
* the order it renders them, an owner before the owners nested in it. A
|
|
70
|
+
* container forwards it by passing its options on, which every container
|
|
71
|
+
* does. An owner rendered twice is told twice. A render made to measure is
|
|
72
|
+
* not drawing and is not told (`measuring`).
|
|
73
|
+
*/
|
|
74
|
+
onDraw?: (owner: object) => void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `chosen` as this output can draw it: `ascii` in its place when the output is
|
|
78
|
+
* ASCII-only and `chosen` spends a glyph outside ASCII, otherwise `chosen`
|
|
79
|
+
* itself. Choosing a glyph that is already ASCII (a rule of `=`, a MARKDOWN box)
|
|
80
|
+
* picks a frame the terminal can draw, so it is kept. A set of glyphs that must
|
|
81
|
+
* change together — a box grid, a spinner's frames, a scrollbar — passes
|
|
82
|
+
* `glyphs` to say which characters it spends, and is replaced whole.
|
|
83
|
+
*
|
|
84
|
+
* [LAW:single-enforcer] The one place that decides what `asciiOnly` replaces.
|
|
85
|
+
* `test/renderables/ascii-only.test.ts` renders every exported renderable to
|
|
86
|
+
* hold each of them to it.
|
|
87
|
+
*/
|
|
88
|
+
export declare function drawable(options: {
|
|
89
|
+
readonly asciiOnly?: boolean;
|
|
90
|
+
}, chosen: string, ascii: string): string;
|
|
91
|
+
export declare function drawable<T>(options: {
|
|
92
|
+
readonly asciiOnly?: boolean;
|
|
93
|
+
}, chosen: T, ascii: T, glyphs: (value: T) => string): T;
|
|
94
|
+
/**
|
|
95
|
+
* A vertical budget: a count of rows, and whether they are a region the
|
|
96
|
+
* renderable stands in or a ceiling it stays under.
|
|
97
|
+
*
|
|
98
|
+
* `exact: true` is a region — a `Layout` pane, a full-screen frame. The output
|
|
99
|
+
* is exactly `rows` tall. A renderable may fill the region — a layout divides
|
|
100
|
+
* it, a frame can stretch to its last row — or ignore it and emit its natural
|
|
101
|
+
* height; either way, whoever set the region shapes what comes back to it
|
|
102
|
+
* (`fitHeight`). Setting a region is a promise to shape, so no renderable pads
|
|
103
|
+
* or crops itself to one.
|
|
104
|
+
*
|
|
105
|
+
* `exact: false` is a ceiling — the terminal an inline print lands on. Content
|
|
106
|
+
* keeps its natural height beneath it, and nothing pads up to the ceiling.
|
|
107
|
+
* Output taller than the ceiling is its setter's to handle: an inline `Live`
|
|
108
|
+
* applies its `verticalOverflow`, and a print lets the terminal scroll it.
|
|
109
|
+
*
|
|
110
|
+
* A renderable passing its whole space to one child must forward the budget
|
|
111
|
+
* less the rows it draws itself, and of the same kind (`insetHeight`). One
|
|
112
|
+
* stacking several children must hand each the rows as a ceiling
|
|
113
|
+
* (`stackedHeight`): any one of them may use all of it, and none may claim it
|
|
114
|
+
* as its region. Forwarded unchanged, a layout nested in a panel fills the
|
|
115
|
+
* panel's whole region and the region's crop takes the panel's bottom border.
|
|
116
|
+
*
|
|
117
|
+
* [LAW:types-are-the-program] One field, because the fact is one count and one
|
|
118
|
+
* bit about it. As two numbers, `height` and `maxHeight`, it admitted a region
|
|
119
|
+
* taller than its ceiling and a region with no ceiling at all, and the one
|
|
120
|
+
* reader in the library resolved them `maxHeight ?? height` — the ceiling ahead
|
|
121
|
+
* of the region it contains.
|
|
122
|
+
*
|
|
123
|
+
* There is no vertical `Measurable`. A parent chooses widths from its
|
|
124
|
+
* children's measurements before any of them renders; nothing chooses rows
|
|
125
|
+
* that way, because a renderable's height is a consequence of the width it
|
|
126
|
+
* renders at. Rendering and counting the lines is the measurement.
|
|
127
|
+
*/
|
|
128
|
+
export interface Height {
|
|
129
|
+
readonly rows: number;
|
|
130
|
+
readonly exact: boolean;
|
|
49
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The budget a renderable hands the one child filling its space: its own, less
|
|
134
|
+
* the `rows` it draws itself, and of the same kind.
|
|
135
|
+
*/
|
|
136
|
+
export declare function insetHeight(height: Height | undefined, rows: number): Height | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* The budget a renderable hands each of several children it stacks: its rows,
|
|
139
|
+
* as a ceiling.
|
|
140
|
+
*/
|
|
141
|
+
export declare function stackedHeight(height: Height | undefined): Height | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* The rows a region holds, parsed as a cell count, or `undefined` when there
|
|
144
|
+
* is no region to fill: a ceiling, no budget, or a region of `Infinity` rows,
|
|
145
|
+
* which names no count — as an unbounded width resolves to a natural one in
|
|
146
|
+
* `withBoundedWidth`.
|
|
147
|
+
*/
|
|
148
|
+
export declare function regionRows(height: Height | undefined): number | undefined;
|
|
149
|
+
/**
|
|
150
|
+
* What came back from a child, held to the region `height` names — blank rows
|
|
151
|
+
* padded below, overflow cropped from the bottom — or left at its own height
|
|
152
|
+
* when `height` is no region. This is the shaping a region's setter owes.
|
|
153
|
+
*/
|
|
154
|
+
export declare function fitHeight(lines: Segment[][], height: Height | undefined): Segment[][];
|
|
50
155
|
/**
|
|
51
156
|
* Receives a style error a render would otherwise absorb: the parse failure,
|
|
52
157
|
* and the whole style string it came from — the error names only the token
|
|
@@ -58,7 +163,12 @@ export interface RenderOptions {
|
|
|
58
163
|
*/
|
|
59
164
|
export type StyleErrorHandler = (error: StyleSyntaxError, style: string) => void;
|
|
60
165
|
/**
|
|
61
|
-
* The style a `string | Style` stands for in this render
|
|
166
|
+
* The style a `string | Style` stands for in this render: a name from the
|
|
167
|
+
* theme of whatever is drawing, otherwise a style definition. A string that is
|
|
168
|
+
* neither throws `StyleSyntaxError`. This is how a renderable that yields its
|
|
169
|
+
* own `Segment`s draws with a theme name, since a `Segment` takes a resolved
|
|
170
|
+
* `Style`. It is Rich's `console.get_style`, asked of the options because the
|
|
171
|
+
* options are where the theme travels.
|
|
62
172
|
*
|
|
63
173
|
* [LAW:single-enforcer] Every renderable resolves a style name here, at render
|
|
64
174
|
* time, because only the render knows whose theme it is drawing for. Resolved
|
|
@@ -123,6 +233,16 @@ export declare function withCellWidth(options: RenderOptions): RenderOptions;
|
|
|
123
233
|
* rather than zero, and saying so names the cause — where `String.repeat` and
|
|
124
234
|
* `new Array` only ever name their own argument.
|
|
125
235
|
*/
|
|
236
|
+
/**
|
|
237
|
+
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
238
|
+
* its shape draws nothing on the frame, so it reports no owner to `onDraw` —
|
|
239
|
+
* one that did would put a widget in document order at the moment a
|
|
240
|
+
* container measured it, and keep one the frame then cropped away.
|
|
241
|
+
*
|
|
242
|
+
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
243
|
+
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
244
|
+
*/
|
|
245
|
+
export declare function measuring(options: RenderOptions): RenderOptions;
|
|
126
246
|
export declare function withBoundedWidth(options: RenderOptions, self: Measurable): RenderOptions;
|
|
127
247
|
export interface Renderable {
|
|
128
248
|
render(options: RenderOptions): Iterable<Segment>;
|
|
@@ -133,5 +253,15 @@ export interface Measurable {
|
|
|
133
253
|
maximum: number;
|
|
134
254
|
};
|
|
135
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* What the wheel scrolls: an owner of anchors on the frame (`./anchor.ts`)
|
|
258
|
+
* that moves by lines. `canScrollBy` says whether `scrollBy` with the same
|
|
259
|
+
* `lines` would move it; one that would not passes the wheel outward.
|
|
260
|
+
*/
|
|
261
|
+
export interface Scrollable {
|
|
262
|
+
canScrollBy(lines: number): boolean;
|
|
263
|
+
scrollBy(lines: number): void;
|
|
264
|
+
}
|
|
136
265
|
export declare function isRenderable(obj: unknown): obj is Renderable;
|
|
137
266
|
export declare function isMeasurable(obj: unknown): obj is Measurable;
|
|
267
|
+
export declare function isScrollable(obj: object): obj is Scrollable;
|
package/dist/core/protocol.js
CHANGED
|
@@ -4,8 +4,52 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { cellCount } from "./cells.js";
|
|
6
6
|
import { DEFAULT_THEME } from "./style.js";
|
|
7
|
+
const ASCII_TEXT = /^[\x00-\x7F]*$/;
|
|
8
|
+
export function drawable(options, chosen, ascii, glyphs = String) {
|
|
9
|
+
return options.asciiOnly === true && !ASCII_TEXT.test(glyphs(chosen)) ? ascii : chosen;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The budget a renderable hands the one child filling its space: its own, less
|
|
13
|
+
* the `rows` it draws itself, and of the same kind.
|
|
14
|
+
*/
|
|
15
|
+
export function insetHeight(height, rows) {
|
|
16
|
+
return height && { rows: cellCount(height.rows - rows), exact: height.exact };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The budget a renderable hands each of several children it stacks: its rows,
|
|
20
|
+
* as a ceiling.
|
|
21
|
+
*/
|
|
22
|
+
export function stackedHeight(height) {
|
|
23
|
+
return height && { rows: cellCount(height.rows), exact: false };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The rows a region holds, parsed as a cell count, or `undefined` when there
|
|
27
|
+
* is no region to fill: a ceiling, no budget, or a region of `Infinity` rows,
|
|
28
|
+
* which names no count — as an unbounded width resolves to a natural one in
|
|
29
|
+
* `withBoundedWidth`.
|
|
30
|
+
*/
|
|
31
|
+
export function regionRows(height) {
|
|
32
|
+
return height?.exact && height.rows !== Infinity ? cellCount(height.rows) : undefined;
|
|
33
|
+
}
|
|
7
34
|
/**
|
|
8
|
-
*
|
|
35
|
+
* What came back from a child, held to the region `height` names — blank rows
|
|
36
|
+
* padded below, overflow cropped from the bottom — or left at its own height
|
|
37
|
+
* when `height` is no region. This is the shaping a region's setter owes.
|
|
38
|
+
*/
|
|
39
|
+
export function fitHeight(lines, height) {
|
|
40
|
+
const rows = regionRows(height) ?? lines.length;
|
|
41
|
+
const fitted = lines.slice(0, rows);
|
|
42
|
+
while (fitted.length < rows)
|
|
43
|
+
fitted.push([]);
|
|
44
|
+
return fitted;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The style a `string | Style` stands for in this render: a name from the
|
|
48
|
+
* theme of whatever is drawing, otherwise a style definition. A string that is
|
|
49
|
+
* neither throws `StyleSyntaxError`. This is how a renderable that yields its
|
|
50
|
+
* own `Segment`s draws with a theme name, since a `Segment` takes a resolved
|
|
51
|
+
* `Style`. It is Rich's `console.get_style`, asked of the options because the
|
|
52
|
+
* options are where the theme travels.
|
|
9
53
|
*
|
|
10
54
|
* [LAW:single-enforcer] Every renderable resolves a style name here, at render
|
|
11
55
|
* time, because only the render knows whose theme it is drawing for. Resolved
|
|
@@ -74,11 +118,23 @@ export function withCellWidth(options) {
|
|
|
74
118
|
* rather than zero, and saying so names the cause — where `String.repeat` and
|
|
75
119
|
* `new Array` only ever name their own argument.
|
|
76
120
|
*/
|
|
121
|
+
/**
|
|
122
|
+
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
123
|
+
* its shape draws nothing on the frame, so it reports no owner to `onDraw` —
|
|
124
|
+
* one that did would put a widget in document order at the moment a
|
|
125
|
+
* container measured it, and keep one the frame then cropped away.
|
|
126
|
+
*
|
|
127
|
+
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
128
|
+
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
129
|
+
*/
|
|
130
|
+
export function measuring(options) {
|
|
131
|
+
return { ...options, onDraw: undefined };
|
|
132
|
+
}
|
|
77
133
|
export function withBoundedWidth(options, self) {
|
|
78
134
|
const parsed = withCellWidth(options);
|
|
79
135
|
if (Number.isFinite(parsed.maxWidth))
|
|
80
136
|
return parsed;
|
|
81
|
-
const natural = self.measure(parsed).maximum;
|
|
137
|
+
const natural = self.measure(measuring(parsed)).maximum;
|
|
82
138
|
if (!Number.isFinite(natural)) {
|
|
83
139
|
throw new RangeError("maxWidth is unbounded and this renderable has no natural width to fall " +
|
|
84
140
|
"back on: its content does not implement measure(), or something inside " +
|
|
@@ -99,3 +155,9 @@ export function isMeasurable(obj) {
|
|
|
99
155
|
"measure" in obj &&
|
|
100
156
|
typeof obj.measure === "function");
|
|
101
157
|
}
|
|
158
|
+
export function isScrollable(obj) {
|
|
159
|
+
return ("canScrollBy" in obj &&
|
|
160
|
+
typeof obj.canScrollBy === "function" &&
|
|
161
|
+
"scrollBy" in obj &&
|
|
162
|
+
typeof obj.scrollBy === "function");
|
|
163
|
+
}
|
package/dist/core/render.d.ts
CHANGED
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
import { ColorDepth } from "./color.js";
|
|
39
39
|
import type { Destination } from "./color.js";
|
|
40
40
|
import type { Segment } from "./segment.js";
|
|
41
|
-
import type { Renderable } from "./protocol.js";
|
|
42
|
-
export interface RenderToStringOptions {
|
|
41
|
+
import type { Renderable, RenderOptions } from "./protocol.js";
|
|
42
|
+
export interface RenderToStringOptions extends Pick<RenderOptions, "asciiOnly" | "onStyleError"> {
|
|
43
43
|
/** Cell width to render into. Default 80. */
|
|
44
44
|
width?: number;
|
|
45
45
|
/**
|
package/dist/core/render.js
CHANGED
|
@@ -135,8 +135,8 @@ export function renderToString(renderable, options) {
|
|
|
135
135
|
const renderOptions = {
|
|
136
136
|
maxWidth: width,
|
|
137
137
|
isTerminal: false,
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
asciiOnly: options?.asciiOnly ?? false,
|
|
139
|
+
onStyleError: options?.onStyleError,
|
|
140
140
|
// [LAW:one-source-of-truth] The depth the segments below are encoded at,
|
|
141
141
|
// so a renderable measures what this very call will draw.
|
|
142
142
|
colorSystem: destination.colorSystem,
|
package/dist/core/segment.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type CellCol } from "./cells.js";
|
|
6
6
|
import { Style } from "./style.js";
|
|
7
|
+
import { type Anchor } from "./anchor.js";
|
|
7
8
|
export declare enum ControlType {
|
|
8
9
|
BELL = "bell",
|
|
9
10
|
CARRIAGE_RETURN = "carriage_return",
|
|
@@ -33,10 +34,27 @@ export declare class Segment {
|
|
|
33
34
|
get isControl(): boolean;
|
|
34
35
|
/**
|
|
35
36
|
* Splits at a cell position. Returns [left, right].
|
|
37
|
+
*
|
|
38
|
+
* [LAW:single-enforcer] The right half starts as many cells further into
|
|
39
|
+
* whatever drew it as the left half took from this one, and its anchor says
|
|
40
|
+
* so; see `./anchor.ts`. That is not `leftText`'s width: a cut through a
|
|
41
|
+
* wide glyph pads the left half and keeps the whole glyph on the right.
|
|
36
42
|
*/
|
|
37
43
|
splitCells(position: CellCol): [Segment, Segment];
|
|
38
44
|
private static _line;
|
|
39
45
|
static line(): Segment;
|
|
46
|
+
/**
|
|
47
|
+
* `lines`, as drawn by `owner`: every segment stamped with the row and
|
|
48
|
+
* column of its first cell, wrapping whatever anchor it already carried.
|
|
49
|
+
* Control segments occupy no cell and are left as they are. `firstRow` is
|
|
50
|
+
* the owner's row that `lines[0]` is, for output that arrives in pieces.
|
|
51
|
+
*/
|
|
52
|
+
static anchorLines(lines: Segment[][], owner: object, firstRow?: number): Segment[][];
|
|
53
|
+
/**
|
|
54
|
+
* The anchor of the cell at column `x` of row `y` in a composed frame, or
|
|
55
|
+
* `undefined` when that cell was drawn by no owner or lies outside the frame.
|
|
56
|
+
*/
|
|
57
|
+
static anchorAt(lines: readonly (readonly Segment[])[], x: number, y: number): Anchor | undefined;
|
|
40
58
|
/**
|
|
41
59
|
* Yields segments with combined styles.
|
|
42
60
|
*/
|
|
@@ -79,6 +97,11 @@ export declare class Segment {
|
|
|
79
97
|
* nothing, and a segment the crop did not change leaves as itself.
|
|
80
98
|
*/
|
|
81
99
|
static cropLines(segments: Iterable<Segment>, width: number): Iterable<Segment>;
|
|
100
|
+
/**
|
|
101
|
+
* Splits a renderable's output into rows, each exactly `width` cells:
|
|
102
|
+
* short rows padded with spaces, long ones cut.
|
|
103
|
+
*/
|
|
104
|
+
static splitAndCropLines(segments: Iterable<Segment>, width: number): Segment[][];
|
|
82
105
|
/**
|
|
83
106
|
* Returns total cell width of a line. Ignores control segments.
|
|
84
107
|
*/
|
|
@@ -135,8 +158,4 @@ export declare class Segment {
|
|
|
135
158
|
* Forces lines to exactly width x height.
|
|
136
159
|
*/
|
|
137
160
|
static setShape(lines: Segment[][], width: number, height: number, style?: Style): Segment[][];
|
|
138
|
-
/**
|
|
139
|
-
* Splits segments into lines and adjusts each to exactly `width` cells.
|
|
140
|
-
*/
|
|
141
|
-
static splitAndCropLines(segments: Iterable<Segment>, width: number, pad?: boolean, includeNewLines?: boolean, style?: Style): Segment[][];
|
|
142
161
|
}
|
package/dist/core/segment.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* is represented as a Segment: (text, style?, control?).
|
|
4
4
|
*/
|
|
5
5
|
import { cellLen, splitText, asCellCol } from "./cells.js";
|
|
6
|
+
import { Style } from "./style.js";
|
|
7
|
+
import { shiftAnchor } from "./anchor.js";
|
|
6
8
|
// --- ControlType ---
|
|
7
9
|
export var ControlType;
|
|
8
10
|
(function (ControlType) {
|
|
@@ -44,6 +46,11 @@ export class Segment {
|
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* Splits at a cell position. Returns [left, right].
|
|
49
|
+
*
|
|
50
|
+
* [LAW:single-enforcer] The right half starts as many cells further into
|
|
51
|
+
* whatever drew it as the left half took from this one, and its anchor says
|
|
52
|
+
* so; see `./anchor.ts`. That is not `leftText`'s width: a cut through a
|
|
53
|
+
* wide glyph pads the left half and keeps the whole glyph on the right.
|
|
47
54
|
*/
|
|
48
55
|
splitCells(position) {
|
|
49
56
|
const len = this.cellLength;
|
|
@@ -54,7 +61,7 @@ export class Segment {
|
|
|
54
61
|
const [leftText, rightText] = splitText(this.text, position);
|
|
55
62
|
return [
|
|
56
63
|
new Segment(leftText, this.style),
|
|
57
|
-
new Segment(rightText, this.style),
|
|
64
|
+
new Segment(rightText, this.style?.shiftedBy(len - cellLen(rightText))),
|
|
58
65
|
];
|
|
59
66
|
}
|
|
60
67
|
// --- Static factories ---
|
|
@@ -63,10 +70,54 @@ export class Segment {
|
|
|
63
70
|
return (Segment._line ??= new Segment("\n"));
|
|
64
71
|
}
|
|
65
72
|
// --- Static transformations ---
|
|
73
|
+
/**
|
|
74
|
+
* `lines`, as drawn by `owner`: every segment stamped with the row and
|
|
75
|
+
* column of its first cell, wrapping whatever anchor it already carried.
|
|
76
|
+
* Control segments occupy no cell and are left as they are. `firstRow` is
|
|
77
|
+
* the owner's row that `lines[0]` is, for output that arrives in pieces.
|
|
78
|
+
*/
|
|
79
|
+
static anchorLines(lines, owner, firstRow = 0) {
|
|
80
|
+
return lines.map((line, i) => {
|
|
81
|
+
const row = firstRow + i;
|
|
82
|
+
let col = 0;
|
|
83
|
+
return line.map((segment) => {
|
|
84
|
+
if (segment.isControl)
|
|
85
|
+
return segment;
|
|
86
|
+
const style = segment.style ?? Style.null();
|
|
87
|
+
const anchored = new Segment(segment.text, style.withAnchor({ owner, row, col, inner: style.anchor }));
|
|
88
|
+
col += segment.cellLength;
|
|
89
|
+
return anchored;
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The anchor of the cell at column `x` of row `y` in a composed frame, or
|
|
95
|
+
* `undefined` when that cell was drawn by no owner or lies outside the frame.
|
|
96
|
+
*/
|
|
97
|
+
static anchorAt(lines, x, y) {
|
|
98
|
+
if (!Number.isInteger(x) || x < 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
let start = 0;
|
|
101
|
+
for (const segment of lines[y] ?? []) {
|
|
102
|
+
const end = start + segment.cellLength;
|
|
103
|
+
if (x < end) {
|
|
104
|
+
const anchor = segment.style?.anchor;
|
|
105
|
+
return anchor && shiftAnchor(anchor, x - start);
|
|
106
|
+
}
|
|
107
|
+
start = end;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
66
111
|
/**
|
|
67
112
|
* Yields segments with combined styles.
|
|
68
113
|
*/
|
|
69
114
|
static *applyStyle(segments, style, postStyle) {
|
|
115
|
+
// Applying no style is the identity, so the segments pass through as they
|
|
116
|
+
// are instead of each being copied into an equal one.
|
|
117
|
+
if (!style && !postStyle) {
|
|
118
|
+
yield* segments;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
70
121
|
for (const segment of segments) {
|
|
71
122
|
if (segment.isControl) {
|
|
72
123
|
yield segment;
|
|
@@ -156,6 +207,22 @@ export class Segment {
|
|
|
156
207
|
}
|
|
157
208
|
return result;
|
|
158
209
|
}
|
|
210
|
+
// --- Bounding output to a width ---
|
|
211
|
+
//
|
|
212
|
+
// Two primitives, because containers ask two different questions of content.
|
|
213
|
+
//
|
|
214
|
+
// `cropLines` — *keep the structure, bound it*. The container needs no line
|
|
215
|
+
// to overrun and must not pad: a `Layout` leaf and the console's crop pass
|
|
216
|
+
// the stream on as it is; a `Tree` label splits the cropped stream itself to
|
|
217
|
+
// hang its guides on each row. The crop never decides how many lines there
|
|
218
|
+
// are — whoever splits after it owns that.
|
|
219
|
+
//
|
|
220
|
+
// `splitAndCropLines` — *decompose into rows of exactly this width*. The
|
|
221
|
+
// container goes on to compose the rows (a `Table` cell zipped beside its
|
|
222
|
+
// neighbours), so it needs the two-dimensional shape and every row full.
|
|
223
|
+
//
|
|
224
|
+
// The split decides how many lines there are, so it is wrong for the first
|
|
225
|
+
// question; `cropLines`' header records the two times it was tried there.
|
|
159
226
|
/**
|
|
160
227
|
* Crops a renderable's output so no line exceeds `width`, leaving short lines
|
|
161
228
|
* alone and the line structure exactly as it arrived.
|
|
@@ -201,6 +268,13 @@ export class Segment {
|
|
|
201
268
|
yield cropped === segment.text ? segment : new Segment(cropped, segment.style);
|
|
202
269
|
}
|
|
203
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Splits a renderable's output into rows, each exactly `width` cells:
|
|
273
|
+
* short rows padded with spaces, long ones cut.
|
|
274
|
+
*/
|
|
275
|
+
static splitAndCropLines(segments, width) {
|
|
276
|
+
return Segment.splitLines(segments).map((line) => Segment.adjustLineLength(line, width));
|
|
277
|
+
}
|
|
204
278
|
/**
|
|
205
279
|
* Returns total cell width of a line. Ignores control segments.
|
|
206
280
|
*/
|
|
@@ -407,21 +481,6 @@ export class Segment {
|
|
|
407
481
|
}
|
|
408
482
|
return result.slice(0, height);
|
|
409
483
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Splits segments into lines and adjusts each to exactly `width` cells.
|
|
412
|
-
*/
|
|
413
|
-
static splitAndCropLines(segments, width, pad = true, includeNewLines = false, style) {
|
|
414
|
-
const rawLines = Segment.splitLines(segments);
|
|
415
|
-
const result = [];
|
|
416
|
-
for (const line of rawLines) {
|
|
417
|
-
const adjusted = Segment.adjustLineLength(line, width, style, pad);
|
|
418
|
-
result.push(adjusted);
|
|
419
|
-
if (includeNewLines) {
|
|
420
|
-
result[result.length - 1].push(Segment.line());
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return result;
|
|
424
|
-
}
|
|
425
484
|
}
|
|
426
485
|
// --- Internal helpers ---
|
|
427
486
|
function stylesEqual(a, b) {
|
|
@@ -6,5 +6,7 @@ export interface SpinnerData {
|
|
|
6
6
|
readonly frames: readonly string[];
|
|
7
7
|
readonly interval: number;
|
|
8
8
|
}
|
|
9
|
+
/** The spinner an ASCII-only output draws in place of any other. */
|
|
10
|
+
export declare const LINE_SPINNER: SpinnerData;
|
|
9
11
|
export declare const SPINNERS: Record<string, SpinnerData>;
|
|
10
12
|
export declare const DEFAULT_SPINNER = "dots";
|
package/dist/core/spinnerData.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Built-in spinner frame data.
|
|
3
3
|
* [LAW:one-type-per-behavior] All spinners are instances of SpinnerData — differ only by frame data.
|
|
4
4
|
*/
|
|
5
|
+
/** The spinner an ASCII-only output draws in place of any other. */
|
|
6
|
+
export const LINE_SPINNER = { frames: ["-", "\\", "|", "/"], interval: 130 };
|
|
5
7
|
// [LAW:one-source-of-truth] Single authoritative collection of spinner definitions
|
|
6
8
|
export const SPINNERS = {
|
|
7
9
|
dots: { frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], interval: 80 },
|
|
8
10
|
dots2: { frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], interval: 80 },
|
|
9
11
|
dots3: { frames: ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"], interval: 80 },
|
|
10
|
-
line:
|
|
12
|
+
line: LINE_SPINNER,
|
|
11
13
|
line2: { frames: ["⠂", "-", "–", "—", "–", "-"], interval: 100 },
|
|
12
14
|
pipe: { frames: ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"], interval: 100 },
|
|
13
15
|
simpleDots: { frames: [". ", ".. ", "...", " "], interval: 400 },
|
package/dist/core/strip.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { Segment } from "./segment.js";
|
|
29
29
|
import { Style } from "./style.js";
|
|
30
|
-
import type
|
|
30
|
+
import { type Renderable, type RenderOptions } from "./protocol.js";
|
|
31
31
|
/**
|
|
32
32
|
* Items in a `Strip` expose their per-edge style so joiners can paint the
|
|
33
33
|
* transition glyph at each boundary.
|
|
@@ -101,10 +101,7 @@ export interface PowerlineJoinerOptions {
|
|
|
101
101
|
*/
|
|
102
102
|
export declare const POWERLINE_JOINER_GLYPHS: Readonly<PowerlineJoinerOptions>;
|
|
103
103
|
export declare class PowerlineJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
|
|
104
|
-
private readonly
|
|
105
|
-
private readonly _divider;
|
|
106
|
-
private readonly _lead;
|
|
107
|
-
private readonly _tail;
|
|
104
|
+
private readonly _glyphs;
|
|
108
105
|
constructor(options?: PowerlineJoinerOptions);
|
|
109
106
|
join(left: T | null, right: T | null): Renderable;
|
|
110
107
|
}
|
|
@@ -117,8 +114,7 @@ export interface CapsuleJoinerOptions {
|
|
|
117
114
|
separator?: string;
|
|
118
115
|
}
|
|
119
116
|
export declare class CapsuleJoiner<T extends StyledRenderable = StyledRenderable> implements Joiner<T> {
|
|
120
|
-
private readonly
|
|
121
|
-
private readonly _right;
|
|
117
|
+
private readonly _caps;
|
|
122
118
|
private readonly _separator;
|
|
123
119
|
constructor(options?: CapsuleJoinerOptions);
|
|
124
120
|
_emit(left: T | null, right: T | null, options: RenderOptions): Iterable<Segment>;
|