@promptctl/rich-js 0.18.0 → 0.19.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 +9 -7
- 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 +145 -0
- package/dist/core/box.js +372 -0
- package/dist/core/cells.d.ts +192 -0
- package/dist/core/cells.js +334 -0
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +326 -0
- package/dist/core/color.js +1225 -0
- package/dist/core/console.d.ts +196 -0
- package/dist/core/console.js +596 -0
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/emoji.d.ts +24 -0
- package/dist/core/emoji.js +391 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.d.ts +59 -0
- package/dist/core/export-html.js +197 -0
- package/dist/core/export-lines.d.ts +130 -0
- package/dist/core/export-lines.js +159 -0
- package/dist/core/export-svg.d.ts +49 -0
- package/dist/core/export-svg.js +213 -0
- package/dist/core/fnv1a.d.ts +10 -0
- package/dist/core/fnv1a.js +17 -0
- package/dist/core/highlighter.d.ts +33 -0
- package/dist/core/highlighter.js +190 -0
- package/dist/core/json.d.ts +27 -0
- package/dist/core/json.js +53 -0
- package/dist/core/markup.d.ts +140 -0
- package/dist/core/markup.js +654 -0
- package/dist/core/measure.d.ts +27 -0
- package/dist/core/measure.js +67 -0
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/oklch.d.ts +161 -0
- package/dist/core/oklch.js +312 -0
- package/dist/core/osc8.d.ts +68 -0
- package/dist/core/osc8.js +78 -0
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +150 -0
- package/dist/core/place.d.ts +30 -0
- package/dist/core/place.js +49 -0
- package/dist/core/pretty.d.ts +247 -0
- package/dist/core/pretty.js +906 -0
- package/dist/core/protocol.d.ts +305 -0
- package/dist/core/protocol.js +183 -0
- package/dist/core/render.d.ts +87 -0
- package/dist/core/render.js +105 -0
- package/dist/core/segment.d.ts +162 -0
- package/dist/core/segment.js +488 -0
- package/dist/core/spinnerData.d.ts +12 -0
- package/dist/core/spinnerData.js +117 -0
- package/dist/core/strip.d.ts +152 -0
- package/dist/core/strip.js +326 -0
- package/dist/core/style.d.ts +189 -0
- package/dist/core/style.js +758 -0
- package/dist/core/subscription.d.ts +12 -0
- package/dist/core/subscription.js +12 -0
- package/dist/core/text.d.ts +283 -0
- package/dist/core/text.js +1097 -0
- package/dist/core/wrap.d.ts +46 -0
- package/dist/core/wrap.js +140 -0
- package/dist/host/app.d.ts +133 -0
- package/dist/host/app.js +285 -0
- package/dist/host/host-environment.d.ts +26 -0
- package/dist/host/host-environment.js +44 -0
- package/dist/host/index.d.ts +6 -0
- package/dist/host/index.js +3 -0
- package/dist/host/terminal-host.d.ts +180 -0
- package/dist/host/terminal-host.js +186 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +114 -0
- package/dist/node/prompt.d.ts +34 -0
- package/dist/node/prompt.js +51 -0
- package/dist/node/save.d.ts +18 -0
- package/dist/node/save.js +24 -0
- package/dist/node/terminal-host.d.ts +114 -0
- package/dist/node/terminal-host.js +227 -0
- package/dist/node/traceback.d.ts +38 -0
- package/dist/node/traceback.js +98 -0
- package/dist/renderables/align.d.ts +16 -0
- package/dist/renderables/align.js +26 -0
- package/dist/renderables/columns.d.ts +77 -0
- package/dist/renderables/columns.js +191 -0
- package/dist/renderables/constrain.d.ts +15 -0
- package/dist/renderables/constrain.js +31 -0
- package/dist/renderables/effect.d.ts +98 -0
- package/dist/renderables/effect.js +160 -0
- package/dist/renderables/embed.d.ts +107 -0
- package/dist/renderables/embed.js +160 -0
- package/dist/renderables/flexStrip.d.ts +41 -0
- package/dist/renderables/flexStrip.js +200 -0
- package/dist/renderables/group.d.ts +14 -0
- package/dist/renderables/group.js +22 -0
- package/dist/renderables/layout.d.ts +75 -0
- package/dist/renderables/layout.js +223 -0
- package/dist/renderables/live.d.ts +74 -0
- package/dist/renderables/live.js +188 -0
- package/dist/renderables/markdown.d.ts +38 -0
- package/dist/renderables/markdown.js +587 -0
- package/dist/renderables/padding.d.ts +59 -0
- package/dist/renderables/padding.js +147 -0
- package/dist/renderables/panel.d.ts +114 -0
- package/dist/renderables/panel.js +303 -0
- package/dist/renderables/progress.d.ts +178 -0
- package/dist/renderables/progress.js +362 -0
- package/dist/renderables/progressBar.d.ts +31 -0
- package/dist/renderables/progressBar.js +64 -0
- package/dist/renderables/prompt.d.ts +71 -0
- package/dist/renderables/prompt.js +165 -0
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +26 -0
- package/dist/renderables/rule.js +86 -0
- package/dist/renderables/spinner.d.ts +57 -0
- package/dist/renderables/spinner.js +103 -0
- package/dist/renderables/status.d.ts +29 -0
- package/dist/renderables/status.js +46 -0
- package/dist/renderables/syntax.d.ts +65 -0
- package/dist/renderables/syntax.js +166 -0
- package/dist/renderables/table.d.ts +232 -0
- package/dist/renderables/table.js +973 -0
- package/dist/renderables/traceback.d.ts +32 -0
- package/dist/renderables/traceback.js +279 -0
- package/dist/renderables/tree.d.ts +76 -0
- package/dist/renderables/tree.js +177 -0
- package/dist/renderables/viewport.d.ts +114 -0
- package/dist/renderables/viewport.js +196 -0
- package/dist/template-bindings/color-funcs.d.ts +109 -0
- package/dist/template-bindings/color-funcs.js +243 -0
- package/dist/template-bindings/helpers.d.ts +26 -0
- package/dist/template-bindings/helpers.js +33 -0
- package/dist/template-bindings/index.d.ts +119 -0
- package/dist/template-bindings/index.js +162 -0
- package/dist/template-bindings/palette-funcs.d.ts +96 -0
- package/dist/template-bindings/palette-funcs.js +145 -0
- package/dist/template-bindings/style-funcs.d.ts +28 -0
- package/dist/template-bindings/style-funcs.js +157 -0
- package/dist/themes/buildPalette.d.ts +57 -0
- package/dist/themes/buildPalette.js +100 -0
- package/dist/themes/colorMath.d.ts +94 -0
- package/dist/themes/colorMath.js +280 -0
- package/dist/themes/colorRef.d.ts +36 -0
- package/dist/themes/colorRef.js +78 -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.d.ts +3 -0
- package/dist/themes/data/atom-one-dark.js +177 -0
- package/dist/themes/data/atom-one-light.d.ts +3 -0
- package/dist/themes/data/atom-one-light.js +177 -0
- package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
- package/dist/themes/data/catppuccin-frappe.js +177 -0
- package/dist/themes/data/catppuccin-latte.d.ts +3 -0
- package/dist/themes/data/catppuccin-latte.js +177 -0
- package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
- package/dist/themes/data/catppuccin-macchiato.js +177 -0
- package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
- package/dist/themes/data/catppuccin-mocha.js +177 -0
- package/dist/themes/data/cyberpunk.d.ts +17 -0
- package/dist/themes/data/cyberpunk.js +193 -0
- package/dist/themes/data/default.d.ts +3 -0
- package/dist/themes/data/default.js +160 -0
- package/dist/themes/data/dracula.d.ts +3 -0
- package/dist/themes/data/dracula.js +177 -0
- package/dist/themes/data/flexoki.d.ts +3 -0
- package/dist/themes/data/flexoki.js +177 -0
- package/dist/themes/data/gruvbox.d.ts +3 -0
- package/dist/themes/data/gruvbox.js +177 -0
- package/dist/themes/data/index.d.ts +35 -0
- package/dist/themes/data/index.js +64 -0
- package/dist/themes/data/monokai.d.ts +3 -0
- package/dist/themes/data/monokai.js +177 -0
- package/dist/themes/data/nord.d.ts +3 -0
- package/dist/themes/data/nord.js +177 -0
- package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
- package/dist/themes/data/rose-pine-dawn.js +177 -0
- package/dist/themes/data/rose-pine-moon.d.ts +3 -0
- package/dist/themes/data/rose-pine-moon.js +177 -0
- package/dist/themes/data/rose-pine.d.ts +3 -0
- package/dist/themes/data/rose-pine.js +177 -0
- package/dist/themes/data/solarized-dark.d.ts +3 -0
- package/dist/themes/data/solarized-dark.js +177 -0
- package/dist/themes/data/solarized-light.d.ts +3 -0
- package/dist/themes/data/solarized-light.js +177 -0
- package/dist/themes/data/svg-export.d.ts +3 -0
- package/dist/themes/data/svg-export.js +177 -0
- package/dist/themes/data/textual-ansi.d.ts +3 -0
- package/dist/themes/data/textual-ansi.js +160 -0
- package/dist/themes/data/textual-dark.d.ts +3 -0
- package/dist/themes/data/textual-dark.js +160 -0
- package/dist/themes/data/textual-light.d.ts +3 -0
- package/dist/themes/data/textual-light.js +160 -0
- package/dist/themes/data/tokyo-night.d.ts +3 -0
- package/dist/themes/data/tokyo-night.js +177 -0
- package/dist/themes/data/types.d.ts +31 -0
- package/dist/themes/data/types.js +10 -0
- package/dist/themes/palette.d.ts +45 -0
- package/dist/themes/palette.js +71 -0
- package/dist/themes/ramp.d.ts +70 -0
- package/dist/themes/ramp.js +103 -0
- package/dist/themes/registry.d.ts +44 -0
- package/dist/themes/registry.js +105 -0
- package/dist/themes/terminalThemes.d.ts +49 -0
- package/dist/themes/terminalThemes.js +63 -0
- package/dist/themes/transpose.d.ts +87 -0
- package/dist/themes/transpose.js +138 -0
- package/dist/widgets/button.d.ts +45 -0
- package/dist/widgets/button.js +177 -0
- package/dist/widgets/checkbox.d.ts +38 -0
- package/dist/widgets/checkbox.js +135 -0
- package/dist/widgets/dropdown.d.ts +64 -0
- package/dist/widgets/dropdown.js +416 -0
- package/dist/widgets/event-router.d.ts +71 -0
- package/dist/widgets/event-router.js +629 -0
- package/dist/widgets/focus-manager.d.ts +45 -0
- package/dist/widgets/focus-manager.js +191 -0
- package/dist/widgets/hit.d.ts +64 -0
- package/dist/widgets/hit.js +113 -0
- package/dist/widgets/index.d.ts +20 -0
- package/dist/widgets/index.js +14 -0
- package/dist/widgets/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +54 -0
- package/dist/widgets/slider.js +247 -0
- package/dist/widgets/static-item.d.ts +34 -0
- package/dist/widgets/static-item.js +54 -0
- package/dist/widgets/text-input.d.ts +283 -0
- package/dist/widgets/text-input.js +1174 -0
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +41 -0
- package/dist/widgets/toggle.js +157 -0
- package/dist/widgets/types.d.ts +87 -0
- package/dist/widgets/types.js +30 -0
- package/dist/widgets/widget-app.d.ts +65 -0
- package/dist/widgets/widget-app.js +206 -0
- package/dist/widgets/widget-base.d.ts +42 -0
- package/dist/widgets/widget-base.js +172 -0
- package/package.json +16 -2
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout — divides the screen into rectangular regions.
|
|
3
|
+
*/
|
|
4
|
+
import { Segment } from "../core/segment.js";
|
|
5
|
+
import { embed } from "./embed.js";
|
|
6
|
+
import { fitHeight, regionRows, stackedHeight, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
|
|
7
|
+
import { Measurement } from "../core/measure.js";
|
|
8
|
+
import { cellCount } from "../core/cells.js";
|
|
9
|
+
import { ratioBudget, ratioResolve, shareWeight } from "./ratio.js";
|
|
10
|
+
/**
|
|
11
|
+
* A pane rendered into a region of `rows` and held to exactly that many lines,
|
|
12
|
+
* or — with `rows` undefined — rendered under the layout's own budget, as a
|
|
13
|
+
* ceiling, at its natural height.
|
|
14
|
+
*
|
|
15
|
+
* [LAW:single-enforcer] The region's setter shapes it (`fitHeight`). Forwarded
|
|
16
|
+
* unshaped, a pane whose content ran short pulled every pane below it up, and
|
|
17
|
+
* one that ran long pushed them down.
|
|
18
|
+
*/
|
|
19
|
+
function paneLines(pane, options, rows) {
|
|
20
|
+
const height = rows === undefined ? stackedHeight(options.height) : { rows, exact: true };
|
|
21
|
+
return fitHeight(Segment.splitLines(pane.render({ ...options, height })), height);
|
|
22
|
+
}
|
|
23
|
+
export class Layout {
|
|
24
|
+
name;
|
|
25
|
+
visible;
|
|
26
|
+
_ratio;
|
|
27
|
+
_size;
|
|
28
|
+
_minimumSize;
|
|
29
|
+
_renderable;
|
|
30
|
+
_children;
|
|
31
|
+
_splitDirection;
|
|
32
|
+
constructor(renderable, options) {
|
|
33
|
+
if (renderable !== undefined) {
|
|
34
|
+
this._renderable = embed(renderable);
|
|
35
|
+
}
|
|
36
|
+
this.name = options?.name;
|
|
37
|
+
this.ratio = options?.ratio ?? 1;
|
|
38
|
+
this.size = options?.size;
|
|
39
|
+
this.minimumSize = options?.minimumSize ?? 1;
|
|
40
|
+
this.visible = options?.visible !== false;
|
|
41
|
+
this._children = [];
|
|
42
|
+
this._splitDirection = undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The three declared numbers, parsed on assignment rather than at the
|
|
46
|
+
* constructor. All three are public and a caller reaches them long after
|
|
47
|
+
* construction — `layout.getByName("pane")!.ratio = -1` walked straight past a
|
|
48
|
+
* constructor-only parse and put a negative weight back into the division that
|
|
49
|
+
* `ratioResolve` and `ratioBudget` are written to trust.
|
|
50
|
+
*
|
|
51
|
+
* [LAW:parse-dont-validate] The setter is the border, so the guarantee holds
|
|
52
|
+
* for the object's whole lifetime and nothing downstream re-checks. `size` and
|
|
53
|
+
* `minimumSize` are cell counts; `ratio` is a share weight and keeps its
|
|
54
|
+
* fractions. Absence is preserved rather than parsed: an undefined `size`
|
|
55
|
+
* selects a flex pane, and `cellCount` would read it as a declared zero.
|
|
56
|
+
*/
|
|
57
|
+
get ratio() {
|
|
58
|
+
return this._ratio;
|
|
59
|
+
}
|
|
60
|
+
set ratio(value) {
|
|
61
|
+
this._ratio = shareWeight(value);
|
|
62
|
+
}
|
|
63
|
+
get size() {
|
|
64
|
+
return this._size;
|
|
65
|
+
}
|
|
66
|
+
set size(value) {
|
|
67
|
+
this._size = value === undefined ? undefined : cellCount(value);
|
|
68
|
+
}
|
|
69
|
+
get minimumSize() {
|
|
70
|
+
return this._minimumSize;
|
|
71
|
+
}
|
|
72
|
+
set minimumSize(value) {
|
|
73
|
+
this._minimumSize = cellCount(value);
|
|
74
|
+
}
|
|
75
|
+
get children() {
|
|
76
|
+
return this._children;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether this layout draws `_renderable` or its children.
|
|
80
|
+
*
|
|
81
|
+
* [LAW:one-source-of-truth] `render` and `_naturalWidth` must answer this the
|
|
82
|
+
* same way. Asked separately they did not: `_naturalWidth` read it off the
|
|
83
|
+
* *visible* children, so a layout holding content and a single hidden child
|
|
84
|
+
* reported that content's width while `render` emitted nothing at all, and a
|
|
85
|
+
* fit-mode `Panel` framed twelve cells of air.
|
|
86
|
+
*/
|
|
87
|
+
get _isLeaf() {
|
|
88
|
+
return this._children.length === 0;
|
|
89
|
+
}
|
|
90
|
+
splitColumn(...layouts) {
|
|
91
|
+
this._children = layouts;
|
|
92
|
+
this._splitDirection = "column";
|
|
93
|
+
}
|
|
94
|
+
splitRow(...layouts) {
|
|
95
|
+
this._children = layouts;
|
|
96
|
+
this._splitDirection = "row";
|
|
97
|
+
}
|
|
98
|
+
update(renderable) {
|
|
99
|
+
this._renderable = embed(renderable);
|
|
100
|
+
}
|
|
101
|
+
getByName(name) {
|
|
102
|
+
if (this.name === name)
|
|
103
|
+
return this;
|
|
104
|
+
for (const child of this._children) {
|
|
105
|
+
const found = child.getByName(name);
|
|
106
|
+
if (found)
|
|
107
|
+
return found;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
*render(rawOptions) {
|
|
112
|
+
if (!this.visible)
|
|
113
|
+
return;
|
|
114
|
+
// Parsed once at the top of the layout rather than at the row split below,
|
|
115
|
+
// because a column split forwards the width to its children untouched and
|
|
116
|
+
// would otherwise hand each of them the caller's raw number.
|
|
117
|
+
const options = withBoundedWidth(rawOptions, this);
|
|
118
|
+
if (this._isLeaf) {
|
|
119
|
+
// Shaped to its region, as Rich's `render_lines` shapes a pane: every
|
|
120
|
+
// line cropped and padded to the width, and held to the region's rows.
|
|
121
|
+
// The leaf sets its content's region, so it shapes what comes back, as
|
|
122
|
+
// `paneLines` shapes a pane for the split that set the pane's.
|
|
123
|
+
// Cropped rather than forwarded because content is free to ignore its
|
|
124
|
+
// offer, and a pane wider than its region is the one thing a layout may
|
|
125
|
+
// never emit — in a row split it overwrites the pane beside it. Held to
|
|
126
|
+
// the rows because content keeps its own `end`: `RichText("status:
|
|
127
|
+
// ok\n")` is two lines, and a one-row region cuts the second. Every line
|
|
128
|
+
// ended, as the split paths below end theirs: a leaf stacked in a
|
|
129
|
+
// `Group` is still a region of lines.
|
|
130
|
+
if (this._renderable) {
|
|
131
|
+
const lines = fitHeight(Segment.splitLines(this._renderable.render(options)), options.height);
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
yield* Segment.adjustLineLength(line, options.maxWidth);
|
|
134
|
+
yield Segment.line();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const visibleChildren = this._children.filter((c) => c.visible);
|
|
140
|
+
if (visibleChildren.length === 0)
|
|
141
|
+
return;
|
|
142
|
+
if (this._splitDirection === "row") {
|
|
143
|
+
yield* this._renderRow(visibleChildren, options);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
yield* this._renderColumn(visibleChildren, options);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
*_renderColumn(children, options) {
|
|
150
|
+
// Each pane gets full width and its share of the region. With no region —
|
|
151
|
+
// a ceiling, or no budget at all — a pane's share is its declared `size`,
|
|
152
|
+
// and a pane without one takes its content's height.
|
|
153
|
+
const region = regionRows(options.height);
|
|
154
|
+
const shares = region === undefined
|
|
155
|
+
? children.map((child) => child.size)
|
|
156
|
+
: ratioResolve(region, children);
|
|
157
|
+
for (let i = 0; i < children.length; i++) {
|
|
158
|
+
for (const line of paneLines(children[i], options, shares[i])) {
|
|
159
|
+
yield* line;
|
|
160
|
+
yield Segment.line();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
*_renderRow(children, options) {
|
|
165
|
+
// Horizontal side-by-side: divide the requested width once, then merge
|
|
166
|
+
// child lines. The budget arrives parsed from `render` — unparsed, a NaN
|
|
167
|
+
// width made every share NaN and the merge threw `Invalid array length`.
|
|
168
|
+
// Every pane stands in the whole region, and with none the merge pads the
|
|
169
|
+
// row to its tallest pane.
|
|
170
|
+
const widths = ratioResolve(options.maxWidth, children);
|
|
171
|
+
const region = regionRows(options.height);
|
|
172
|
+
const cells = children.map((child, i) => ({
|
|
173
|
+
width: widths[i],
|
|
174
|
+
lines: paneLines(child, { ...options, maxWidth: widths[i] }, region),
|
|
175
|
+
}));
|
|
176
|
+
yield* Segment.mergeHorizontal(cells);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The width this layout would take if nothing constrained it: its content's
|
|
180
|
+
* for a leaf, its children's laid out the way the split lays them out.
|
|
181
|
+
*
|
|
182
|
+
* A `size` counts only across a row, which is the one direction in which it
|
|
183
|
+
* is a width — down a column the same field is a height, and reading it as a
|
|
184
|
+
* width there would report a two-line pane as two cells wide.
|
|
185
|
+
*/
|
|
186
|
+
_naturalWidth(options) {
|
|
187
|
+
if (!this.visible)
|
|
188
|
+
return 0;
|
|
189
|
+
if (this._isLeaf) {
|
|
190
|
+
if (this._renderable === undefined)
|
|
191
|
+
return 0;
|
|
192
|
+
return Measurement.get(options, this._renderable).maximum;
|
|
193
|
+
}
|
|
194
|
+
const visible = this._children.filter((c) => c.visible);
|
|
195
|
+
if (visible.length === 0)
|
|
196
|
+
return 0;
|
|
197
|
+
const widths = visible.map((c) => c._naturalWidth(options));
|
|
198
|
+
if (this._splitDirection === "row")
|
|
199
|
+
return ratioBudget(visible, widths);
|
|
200
|
+
// Accumulated, not spread: a column split holds as many children as a caller
|
|
201
|
+
// made, and `Math.max(...widths)` passes one argument per child, so a
|
|
202
|
+
// generated dashboard deep enough overruns the engine's argument limit and
|
|
203
|
+
// throws out of `measure()`.
|
|
204
|
+
let widest = 0;
|
|
205
|
+
for (const width of widths)
|
|
206
|
+
widest = Math.max(widest, width);
|
|
207
|
+
return widest;
|
|
208
|
+
}
|
|
209
|
+
measure(rawOptions) {
|
|
210
|
+
// `minimumSize` is what this layout asks for and the ceiling is what it was
|
|
211
|
+
// offered; the ceiling wins. Unclamped, a layout measured into no width
|
|
212
|
+
// reported the range 1..0 — a floor above its own ceiling, which the parent
|
|
213
|
+
// that asked cannot divide.
|
|
214
|
+
//
|
|
215
|
+
// The ceiling alone was not the answer either: a layout that reported the
|
|
216
|
+
// whole offer as its maximum told every parent it wanted all of it, so
|
|
217
|
+
// `Panel` in fit mode drew its frame at the full console width around a
|
|
218
|
+
// layout of two short panes, and an unbounded offer came back unbounded.
|
|
219
|
+
const parsed = withCellWidth(rawOptions);
|
|
220
|
+
const maximum = Math.min(this._naturalWidth(parsed), parsed.maxWidth);
|
|
221
|
+
return { minimum: Math.min(this.minimumSize, maximum), maximum };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live — animates a portion of the terminal by continuously re-rendering.
|
|
3
|
+
*
|
|
4
|
+
* Two modes, each painting every frame over the last in place
|
|
5
|
+
* (`core/paint`):
|
|
6
|
+
* - **Inline** (default): the frame starts at the cursor's line and keeps its
|
|
7
|
+
* own height. Good for spinners/progress bars below other output.
|
|
8
|
+
* - **Alt-screen** (`altScreen: true`): enters the alternate screen buffer
|
|
9
|
+
* on start and restores the original buffer on stop. The screen is the
|
|
10
|
+
* frame's region, so a `Layout` fills it. Good for full-screen TUI apps.
|
|
11
|
+
*
|
|
12
|
+
* On a console that is not interactive — a file, a pipe, a CI log — neither
|
|
13
|
+
* mode paints: what is printed goes out plain, and the frame is written once,
|
|
14
|
+
* when the display stops, as Rich's `Live` does.
|
|
15
|
+
*
|
|
16
|
+
* Live is not built on `App`, though both take a terminal, paint a frame over
|
|
17
|
+
* the last and hand the terminal back. That much is the `Painter`'s, and both
|
|
18
|
+
* hold one. The rest differs in who owns what. An `App` owns its terminal: it
|
|
19
|
+
* starts and stops a `TerminalHost`, sets raw mode, reads keys and pointers,
|
|
20
|
+
* suspends, and builds its own `Console` from the host. A `Live` owns none of
|
|
21
|
+
* that. It draws into a `Console` its caller owns and goes on writing
|
|
22
|
+
* through — a sink that may be a file, whose every other write has to step
|
|
23
|
+
* around the frame (`Console.claimLiveRegion`) — and it reads no input. An
|
|
24
|
+
* `App` over that console would need a host that cannot start, stop, read or
|
|
25
|
+
* suspend, and a second owner of the console's writes.
|
|
26
|
+
*/
|
|
27
|
+
import { type Clock } from "../core/clock.js";
|
|
28
|
+
import { Console } from "../core/console.js";
|
|
29
|
+
import { type Renderable } from "../core/protocol.js";
|
|
30
|
+
export interface LiveOptions {
|
|
31
|
+
/** Frames a second while auto-refreshing: positive and finite, fractional included. */
|
|
32
|
+
refreshPerSecond?: number;
|
|
33
|
+
/** What the auto-refresh ticks on. The platform's, unless given. */
|
|
34
|
+
clock?: Clock;
|
|
35
|
+
autoRefresh?: boolean;
|
|
36
|
+
transient?: boolean;
|
|
37
|
+
console?: Console;
|
|
38
|
+
verticalOverflow?: "crop" | "ellipsis" | "visible";
|
|
39
|
+
/** When true, Live enters the alternate screen buffer on start() and
|
|
40
|
+
* restores the original on stop(). The screen is the frame's region, so
|
|
41
|
+
* a full-screen layout fills it. */
|
|
42
|
+
altScreen?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare class Live {
|
|
45
|
+
private _renderable;
|
|
46
|
+
private _console;
|
|
47
|
+
private readonly _rate;
|
|
48
|
+
private readonly _clock;
|
|
49
|
+
private _autoRefresh;
|
|
50
|
+
private _transient;
|
|
51
|
+
private _verticalOverflow;
|
|
52
|
+
private readonly _painter;
|
|
53
|
+
private _stopTicking;
|
|
54
|
+
private _held;
|
|
55
|
+
constructor(renderable?: Renderable, options?: LiveOptions);
|
|
56
|
+
/**
|
|
57
|
+
* The console to print through while this Live runs: what it prints lands
|
|
58
|
+
* above an inline frame. The alternate screen is all frame, and paints over it.
|
|
59
|
+
*/
|
|
60
|
+
get console(): Console;
|
|
61
|
+
get renderable(): Renderable | undefined;
|
|
62
|
+
start(): void;
|
|
63
|
+
stop(): void;
|
|
64
|
+
update(renderable?: Renderable, options?: {
|
|
65
|
+
refresh?: boolean;
|
|
66
|
+
}): void;
|
|
67
|
+
refresh(): void;
|
|
68
|
+
private _frame;
|
|
69
|
+
private _stepAround;
|
|
70
|
+
private _paint;
|
|
71
|
+
private _screen;
|
|
72
|
+
private _overflow;
|
|
73
|
+
private _release;
|
|
74
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live — animates a portion of the terminal by continuously re-rendering.
|
|
3
|
+
*
|
|
4
|
+
* Two modes, each painting every frame over the last in place
|
|
5
|
+
* (`core/paint`):
|
|
6
|
+
* - **Inline** (default): the frame starts at the cursor's line and keeps its
|
|
7
|
+
* own height. Good for spinners/progress bars below other output.
|
|
8
|
+
* - **Alt-screen** (`altScreen: true`): enters the alternate screen buffer
|
|
9
|
+
* on start and restores the original buffer on stop. The screen is the
|
|
10
|
+
* frame's region, so a `Layout` fills it. Good for full-screen TUI apps.
|
|
11
|
+
*
|
|
12
|
+
* On a console that is not interactive — a file, a pipe, a CI log — neither
|
|
13
|
+
* mode paints: what is printed goes out plain, and the frame is written once,
|
|
14
|
+
* when the display stops, as Rich's `Live` does.
|
|
15
|
+
*
|
|
16
|
+
* Live is not built on `App`, though both take a terminal, paint a frame over
|
|
17
|
+
* the last and hand the terminal back. That much is the `Painter`'s, and both
|
|
18
|
+
* hold one. The rest differs in who owns what. An `App` owns its terminal: it
|
|
19
|
+
* starts and stops a `TerminalHost`, sets raw mode, reads keys and pointers,
|
|
20
|
+
* suspends, and builds its own `Console` from the host. A `Live` owns none of
|
|
21
|
+
* that. It draws into a `Console` its caller owns and goes on writing
|
|
22
|
+
* through — a sink that may be a file, whose every other write has to step
|
|
23
|
+
* around the frame (`Console.claimLiveRegion`) — and it reads no input. An
|
|
24
|
+
* `App` over that console would need a host that cannot start, stop, read or
|
|
25
|
+
* suspend, and a second owner of the console's writes.
|
|
26
|
+
*/
|
|
27
|
+
import { frameRate, systemClock } from "../core/clock.js";
|
|
28
|
+
import { Console } from "../core/console.js";
|
|
29
|
+
import { Segment } from "../core/segment.js";
|
|
30
|
+
import { FinalFramePainter, SurfacePainter } from "../core/paint.js";
|
|
31
|
+
import { fitHeight } from "../core/protocol.js";
|
|
32
|
+
export class Live {
|
|
33
|
+
_renderable;
|
|
34
|
+
_console;
|
|
35
|
+
_rate;
|
|
36
|
+
_clock;
|
|
37
|
+
_autoRefresh;
|
|
38
|
+
_transient;
|
|
39
|
+
_verticalOverflow;
|
|
40
|
+
_painter;
|
|
41
|
+
_stopTicking;
|
|
42
|
+
// [LAW:one-source-of-truth] The console's live region, held from `start()`
|
|
43
|
+
// to the hand-back — which is what "started" means, so there is no separate
|
|
44
|
+
// flag to disagree with it.
|
|
45
|
+
_held;
|
|
46
|
+
constructor(renderable, options) {
|
|
47
|
+
this._renderable = renderable;
|
|
48
|
+
this._console = options?.console ?? new Console();
|
|
49
|
+
// [LAW:parse-dont-validate] Parsed whether or not it auto-refreshes, so a
|
|
50
|
+
// rate that could never tick is refused where it is given.
|
|
51
|
+
this._rate = frameRate(options?.refreshPerSecond ?? 4);
|
|
52
|
+
this._clock = options?.clock ?? systemClock();
|
|
53
|
+
this._autoRefresh = options?.autoRefresh !== false;
|
|
54
|
+
this._transient = options?.transient ?? false;
|
|
55
|
+
this._verticalOverflow = options?.verticalOverflow ?? "ellipsis";
|
|
56
|
+
// [LAW:dataflow-not-control-flow] What the console is decides which
|
|
57
|
+
// painter Live holds; every frame then takes the same path through it.
|
|
58
|
+
// The final frame is a print, as in Rich, so a recording or a capture
|
|
59
|
+
// holds it with everything else the console wrote.
|
|
60
|
+
this._painter = this._console.isInteractive
|
|
61
|
+
? new SurfacePainter(options?.altScreen ? "alternate" : "inline", (bytes) => void this._console.file.write(bytes))
|
|
62
|
+
: new FinalFramePainter((frame) => this._console.print(linesOf(frame)));
|
|
63
|
+
this._held = null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The console to print through while this Live runs: what it prints lands
|
|
67
|
+
* above an inline frame. The alternate screen is all frame, and paints over it.
|
|
68
|
+
*/
|
|
69
|
+
get console() {
|
|
70
|
+
return this._console;
|
|
71
|
+
}
|
|
72
|
+
get renderable() {
|
|
73
|
+
return this._renderable;
|
|
74
|
+
}
|
|
75
|
+
start() {
|
|
76
|
+
if (this._held !== null)
|
|
77
|
+
return;
|
|
78
|
+
// Claimed before a byte is written, so a console another Live is running
|
|
79
|
+
// on refuses this one with the terminal untouched.
|
|
80
|
+
const held = this._console.claimLiveRegion({ around: (text) => this._stepAround(text) });
|
|
81
|
+
this._held = held;
|
|
82
|
+
this._painter.take();
|
|
83
|
+
if (this._autoRefresh) {
|
|
84
|
+
// No caller is on the stack to run `stop()` for a frame that throws
|
|
85
|
+
// here, and in Node the throw ends the process — so the terminal is
|
|
86
|
+
// handed back first, and the error then goes on to wherever an uncaught
|
|
87
|
+
// one goes.
|
|
88
|
+
this._stopTicking = this._clock.every(this._rate, () => {
|
|
89
|
+
try {
|
|
90
|
+
this.refresh();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
this._release(held);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
stop() {
|
|
100
|
+
const held = this._held;
|
|
101
|
+
if (held === null)
|
|
102
|
+
return;
|
|
103
|
+
// [LAW:no-silent-failure] A final frame that throws still propagates, and
|
|
104
|
+
// the terminal is handed back on the way out.
|
|
105
|
+
try {
|
|
106
|
+
if (this._transient) {
|
|
107
|
+
this._paint(() => []);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.refresh();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
this._release(held);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
update(renderable, options) {
|
|
118
|
+
if (renderable !== undefined) {
|
|
119
|
+
this._renderable = renderable;
|
|
120
|
+
}
|
|
121
|
+
if (options?.refresh) {
|
|
122
|
+
this.refresh();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
refresh() {
|
|
126
|
+
// A frame is drawn only while this Live holds the terminal, as in Rich:
|
|
127
|
+
// before `start()` or after the hand-back it would land on a screen that
|
|
128
|
+
// belongs to someone else — the main buffer, under the user's own output.
|
|
129
|
+
if (this._held === null)
|
|
130
|
+
return;
|
|
131
|
+
this._paint(() => this._frame());
|
|
132
|
+
}
|
|
133
|
+
_frame() {
|
|
134
|
+
if (!this._renderable)
|
|
135
|
+
return [];
|
|
136
|
+
// [LAW:dataflow-not-control-flow] Both modes render under the terminal's
|
|
137
|
+
// rows and differ only in what those rows are: the alternate screen is a
|
|
138
|
+
// region the frame stands in, and an inline frame keeps its natural height
|
|
139
|
+
// under them as a ceiling. Live set the budget, so Live shapes what comes
|
|
140
|
+
// back — its overflow policy, then `fitHeight`, which pads only a region.
|
|
141
|
+
const options = this._console.options;
|
|
142
|
+
const height = this._painter.height(options.height.rows);
|
|
143
|
+
const lines = Segment.splitLines(this._renderable.render({ ...options, height }));
|
|
144
|
+
return fitHeight(this._overflow(lines, height.rows), height);
|
|
145
|
+
}
|
|
146
|
+
// What the console writes goes where the frame was, and the frame is painted
|
|
147
|
+
// again under it, as Rich's render hook does — in the bytes the console
|
|
148
|
+
// writes, so a render that throws writes nothing.
|
|
149
|
+
_stepAround(text) {
|
|
150
|
+
return this._painter.around(text, () => this._frame(), this._screen(), this._console.destination);
|
|
151
|
+
}
|
|
152
|
+
// [LAW:effects-at-boundaries] The frame is fully rendered before a byte
|
|
153
|
+
// reaches the terminal, and whatever takes the last frame away goes out in
|
|
154
|
+
// the same write as the new one — so a render that throws leaves the last
|
|
155
|
+
// good frame showing.
|
|
156
|
+
_paint(frame) {
|
|
157
|
+
this._painter.paint(frame, this._screen(), this._console.destination);
|
|
158
|
+
}
|
|
159
|
+
_screen() {
|
|
160
|
+
const { height, maxWidth } = this._console.options;
|
|
161
|
+
return { rows: height.rows, cols: maxWidth };
|
|
162
|
+
}
|
|
163
|
+
// Lines past `rows`: dropped, with the last kept row replaced by an ellipsis,
|
|
164
|
+
// or left for the terminal to scroll.
|
|
165
|
+
_overflow(lines, rows) {
|
|
166
|
+
if (this._verticalOverflow === "visible" || lines.length <= rows)
|
|
167
|
+
return lines;
|
|
168
|
+
const kept = lines.slice(0, rows);
|
|
169
|
+
if (this._verticalOverflow === "ellipsis" && kept.length > 0) {
|
|
170
|
+
kept[kept.length - 1] = [new Segment("...")];
|
|
171
|
+
}
|
|
172
|
+
return kept;
|
|
173
|
+
}
|
|
174
|
+
// [LAW:single-enforcer] The one way the terminal is handed back — the timer
|
|
175
|
+
// stopped, the cursor shown, the alternate screen left — whether `stop()`
|
|
176
|
+
// ran or a frame threw with no caller to run it.
|
|
177
|
+
_release(held) {
|
|
178
|
+
held();
|
|
179
|
+
this._held = null;
|
|
180
|
+
this._stopTicking?.();
|
|
181
|
+
this._stopTicking = undefined;
|
|
182
|
+
this._painter.handBack();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// A drawn frame as a renderable, each row a line, for the console to print.
|
|
186
|
+
function linesOf(frame) {
|
|
187
|
+
return { render: () => frame.flatMap((row) => [...row, Segment.line()]) };
|
|
188
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown — renders Markdown content to the terminal.
|
|
3
|
+
* Uses built-in parsing (no external dependency).
|
|
4
|
+
*
|
|
5
|
+
* Block structure is read as CommonMark reads it: a blockquote and a list
|
|
6
|
+
* item are both containers, their lines tokenized as Markdown of their own
|
|
7
|
+
* once the container's marker or indentation is cut off, and drawn beside a
|
|
8
|
+
* gutter — the quote's bar, the item's marker then its hang. Where CommonMark
|
|
9
|
+
* leaves the drawing open, Rich's is followed.
|
|
10
|
+
*/
|
|
11
|
+
import { Segment } from "../core/segment.js";
|
|
12
|
+
import { Style } from "../core/style.js";
|
|
13
|
+
import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
|
|
14
|
+
export interface MarkdownOptions {
|
|
15
|
+
/** The style of `inline code`: a theme name or a style definition. Default `markdown.code`. */
|
|
16
|
+
inlineCodeStyle?: string | Style;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a link's text is the link. `false` writes the URL after the text
|
|
19
|
+
* in parentheses, for a reader who cannot click it. Default `true`.
|
|
20
|
+
*/
|
|
21
|
+
hyperlinks?: boolean;
|
|
22
|
+
/** How body text — paragraphs, list items, quotes — is placed in its width. Default `"left"`; headings keep their own. */
|
|
23
|
+
justify?: Justify;
|
|
24
|
+
}
|
|
25
|
+
type Justify = NonNullable<RenderOptions["justify"]>;
|
|
26
|
+
export declare class Markdown implements Renderable, Measurable {
|
|
27
|
+
readonly markdown: string;
|
|
28
|
+
readonly inlineCodeStyle: string | Style;
|
|
29
|
+
readonly hyperlinks: boolean;
|
|
30
|
+
readonly justify: Justify;
|
|
31
|
+
constructor(markdown: string, options?: MarkdownOptions);
|
|
32
|
+
render(rawOptions: RenderOptions): Iterable<Segment>;
|
|
33
|
+
measure(options: RenderOptions): {
|
|
34
|
+
minimum: number;
|
|
35
|
+
maximum: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {};
|