@promptctl/rich-js 0.24.0 → 0.24.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/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/animation.d.ts +78 -0
- package/dist/core/animation.js +107 -0
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +17 -11
- package/dist/core/cells.js +100 -25
- package/dist/core/console.js +5 -5
- package/dist/core/easing.d.ts +110 -10
- package/dist/core/easing.js +269 -36
- package/dist/core/json.js +2 -1
- package/dist/core/measure.js +2 -2
- package/dist/core/place.js +2 -1
- package/dist/core/pretty.js +2 -2
- package/dist/core/protocol.d.ts +73 -19
- package/dist/core/protocol.js +56 -15
- package/dist/core/render.js +2 -1
- package/dist/core/segment.d.ts +18 -1
- package/dist/core/segment.js +45 -44
- package/dist/core/strip.d.ts +3 -2
- package/dist/core/strip.js +11 -7
- package/dist/core/style.js +17 -6
- package/dist/core/text.js +22 -14
- package/dist/core/timeline.d.ts +84 -0
- package/dist/core/timeline.js +144 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +69 -0
- package/dist/host/app.js +2 -2
- package/dist/index.d.ts +10 -7
- package/dist/index.js +13 -4
- package/dist/renderables/align.d.ts +3 -2
- package/dist/renderables/align.js +3 -0
- package/dist/renderables/columns.d.ts +3 -2
- package/dist/renderables/columns.js +7 -2
- package/dist/renderables/constrain.d.ts +3 -2
- package/dist/renderables/constrain.js +5 -1
- package/dist/renderables/effect.d.ts +43 -58
- package/dist/renderables/effect.js +58 -64
- package/dist/renderables/effects.d.ts +16 -14
- package/dist/renderables/effects.js +64 -45
- package/dist/renderables/embed.js +3 -2
- package/dist/renderables/flexStrip.d.ts +3 -2
- package/dist/renderables/flexStrip.js +6 -1
- package/dist/renderables/frames.d.ts +40 -19
- package/dist/renderables/frames.js +48 -21
- package/dist/renderables/group.d.ts +3 -2
- package/dist/renderables/group.js +5 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +3 -2
- package/dist/renderables/layout.js +15 -2
- package/dist/renderables/list-view.d.ts +3 -2
- package/dist/renderables/list-view.js +12 -5
- package/dist/renderables/live.js +2 -2
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1421 -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 +8 -20
- package/dist/renderables/padding.js +32 -50
- package/dist/renderables/panel.d.ts +26 -26
- package/dist/renderables/panel.js +67 -103
- package/dist/renderables/progress.d.ts +4 -2
- package/dist/renderables/progress.js +11 -2
- package/dist/renderables/progressBar.js +2 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +24 -0
- package/dist/renderables/spinner.d.ts +26 -14
- package/dist/renderables/spinner.js +49 -37
- package/dist/renderables/syntax.js +2 -1
- package/dist/renderables/table.d.ts +19 -11
- package/dist/renderables/table.js +140 -156
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +6 -5
- package/dist/renderables/tree.js +11 -7
- package/dist/renderables/viewport.d.ts +13 -5
- package/dist/renderables/viewport.js +41 -20
- package/dist/template-bindings/animation-funcs.js +2 -2
- package/dist/template-bindings/index.js +2 -1
- package/dist/themes/ramp.d.ts +18 -3
- package/dist/themes/ramp.js +23 -2
- package/dist/widgets/focus-manager.d.ts +21 -14
- package/dist/widgets/focus-manager.js +28 -29
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/static-item.d.ts +4 -3
- package/dist/widgets/static-item.js +9 -7
- package/dist/widgets/text-input.d.ts +28 -18
- package/dist/widgets/text-input.js +144 -101
- package/dist/widgets/widget-app.d.ts +19 -3
- package/dist/widgets/widget-app.js +39 -21
- package/dist/widgets/widget-base.js +0 -3
- package/package.json +2 -5
package/dist/core/protocol.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
|
|
4
4
|
*/
|
|
5
5
|
import type { Segment } from "./segment.js";
|
|
6
|
-
import {
|
|
6
|
+
import { Style, type StyleSyntaxError, type Theme } from "./style.js";
|
|
7
7
|
import { ColorDepth } from "./color.js";
|
|
8
8
|
import type { Highlighter } from "./highlighter.js";
|
|
9
9
|
/**
|
|
@@ -87,15 +87,21 @@ export interface RenderOptions {
|
|
|
87
87
|
*/
|
|
88
88
|
onStyleError?: StyleErrorHandler;
|
|
89
89
|
/**
|
|
90
|
-
* Told of
|
|
91
|
-
*
|
|
92
|
-
* the order it renders them
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* not drawing and is not
|
|
90
|
+
* Told of every renderable `render` is asked for, as it is asked, so the
|
|
91
|
+
* calls arrive in document order: a container before what it holds, its
|
|
92
|
+
* contents in the order it renders them. `"drawn"` is a renderable drawn at
|
|
93
|
+
* the width it was handed; `"held"` is one handed no column, which is still
|
|
94
|
+
* in the view but has nothing drawn for it (`render`). A renderable rendered
|
|
95
|
+
* twice is told twice. A render made to measure is not drawing and is not
|
|
96
|
+
* told (`measuring`).
|
|
96
97
|
*/
|
|
97
|
-
|
|
98
|
+
onPresent?: (renderable: Renderable, presence: Presence) => void;
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether a renderable the view holds was drawn, or held: in the view, handed
|
|
102
|
+
* no column, and so not drawn (`render`).
|
|
103
|
+
*/
|
|
104
|
+
export type Presence = "drawn" | "held";
|
|
99
105
|
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
100
106
|
export declare const isAscii: (text: string) => boolean;
|
|
101
107
|
/**
|
|
@@ -210,7 +216,10 @@ export declare function getStyle(options: RenderOptions, style: string | Style):
|
|
|
210
216
|
* every bundled theme. So where the depth would turn a fixed colour of the
|
|
211
217
|
* pair into a slot, the pair is dropped and the run is drawn on the
|
|
212
218
|
* terminal's own foreground and background, which its theme does make
|
|
213
|
-
* readable; its attributes still set it apart.
|
|
219
|
+
* readable; its attributes still set it apart. Both are named, not left
|
|
220
|
+
* unset, so the colour of whatever the run sits in — a quote, a link — does
|
|
221
|
+
* not show through: Rich's code keeps its own colours inside a quote, and the
|
|
222
|
+
* terminal's are the ones it keeps here. A style whose colours are
|
|
214
223
|
* slots already (`white on blue`), or that paints no ground (`bold magenta`),
|
|
215
224
|
* was written for the terminal's own colours and is drawn as written. Rich
|
|
216
225
|
* draws a highlighted line number the same way below 256 colours
|
|
@@ -238,6 +247,40 @@ export declare function groundedStyle(options: RenderOptions, style: Style): Sty
|
|
|
238
247
|
* ignored the request and emitted its full natural width.
|
|
239
248
|
*/
|
|
240
249
|
export declare function withCellWidth(options: RenderOptions): RenderOptions;
|
|
250
|
+
/** Under one column there is nothing to draw, or to measure. */
|
|
251
|
+
export declare function drawsNothing(options: {
|
|
252
|
+
readonly maxWidth: number;
|
|
253
|
+
}): boolean;
|
|
254
|
+
/**
|
|
255
|
+
* `renderable` drawn under `options`: Rich's `Console.render`, which every
|
|
256
|
+
* renderable there is drawn through, so under one column nothing is.
|
|
257
|
+
*
|
|
258
|
+
* [LAW:single-enforcer] The one place that rule is kept. Every call of a
|
|
259
|
+
* `Renderable`'s `render` in `src/` goes through here — a container drawing
|
|
260
|
+
* its child, a renderable drawing the text it built, a host drawing a frame —
|
|
261
|
+
* and `test/seam/render-calls.test.ts` fails on one that does not. Before
|
|
262
|
+
* this, each container asked `drawsNothing` itself, and the ones that forgot
|
|
263
|
+
* drew a child handed no column anyway.
|
|
264
|
+
*
|
|
265
|
+
* It is also the one reporter of what the view holds (`onPresent`). Under one
|
|
266
|
+
* column a renderable is held, not drawn: what it holds (`Composite`) is
|
|
267
|
+
* walked and reported held in turn, and nothing renders, so no renderable
|
|
268
|
+
* that keeps state while it renders — a `Viewport`'s offset, a `TextInput`'s
|
|
269
|
+
* scroll window — ever commits it at a width that is not shown. The walk runs
|
|
270
|
+
* whether or not anyone listens. Drawing the squeezed subtree at one column
|
|
271
|
+
* instead, and dropping what it drew, did commit that state.
|
|
272
|
+
*/
|
|
273
|
+
export declare function render(renderable: Renderable, options: RenderOptions): Iterable<Segment>;
|
|
274
|
+
/**
|
|
275
|
+
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
276
|
+
* its shape draws nothing on the frame, so it reports nothing to `onPresent` —
|
|
277
|
+
* one that did would put a widget in the view at the moment a container
|
|
278
|
+
* measured it, and keep one the frame then cropped away.
|
|
279
|
+
*
|
|
280
|
+
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
281
|
+
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
282
|
+
*/
|
|
283
|
+
export declare function measuring(options: RenderOptions): RenderOptions;
|
|
241
284
|
/**
|
|
242
285
|
* The options a renderable should *lay out against*: `withCellWidth`, with an
|
|
243
286
|
* unbounded offer resolved to the renderable's own natural width.
|
|
@@ -273,16 +316,6 @@ export declare function withCellWidth(options: RenderOptions): RenderOptions;
|
|
|
273
316
|
* rather than zero, and saying so names the cause — where `String.repeat` and
|
|
274
317
|
* `new Array` only ever name their own argument.
|
|
275
318
|
*/
|
|
276
|
-
/**
|
|
277
|
-
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
278
|
-
* its shape draws nothing on the frame, so it reports no owner to `onDraw` —
|
|
279
|
-
* one that did would put a widget in document order at the moment a
|
|
280
|
-
* container measured it, and keep one the frame then cropped away.
|
|
281
|
-
*
|
|
282
|
-
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
283
|
-
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
284
|
-
*/
|
|
285
|
-
export declare function measuring(options: RenderOptions): RenderOptions;
|
|
286
319
|
export declare function withBoundedWidth(options: RenderOptions, self: Measurable): RenderOptions;
|
|
287
320
|
export interface Renderable {
|
|
288
321
|
render(options: RenderOptions): Iterable<Segment>;
|
|
@@ -293,6 +326,26 @@ export interface Measurable {
|
|
|
293
326
|
maximum: number;
|
|
294
327
|
};
|
|
295
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* A container: a renderable that draws other renderables. `contents` is what
|
|
331
|
+
* its `render` hands to `render`, in that order where the order is not a
|
|
332
|
+
* width's, decided as the render decides it — and so it is how a container
|
|
333
|
+
* handed no column still reports what it holds (`render`). It takes no
|
|
334
|
+
* options: what a container holds may depend on its state (a hidden `Layout`
|
|
335
|
+
* holds nothing) but never on a width, because under one column there is none.
|
|
336
|
+
* It commits no state, and yields only renderables: text a container builds
|
|
337
|
+
* from a string it holds holds nothing.
|
|
338
|
+
*
|
|
339
|
+
* [LAW:one-source-of-truth] A second description of each container's
|
|
340
|
+
* traversal, so `test/renderables/contents.test.ts` holds every exported
|
|
341
|
+
* renderable's `contents` to what its `render` reports.
|
|
342
|
+
*
|
|
343
|
+
* A consumer's container that does not implement it reports nothing it holds
|
|
344
|
+
* while it is handed no column.
|
|
345
|
+
*/
|
|
346
|
+
export interface Composite {
|
|
347
|
+
contents(): Iterable<Renderable>;
|
|
348
|
+
}
|
|
296
349
|
/**
|
|
297
350
|
* What the wheel scrolls: an owner of anchors on the frame (`./anchor.ts`)
|
|
298
351
|
* that moves by lines. `canScrollBy` says whether `scrollBy` with the same
|
|
@@ -305,3 +358,4 @@ export interface Scrollable {
|
|
|
305
358
|
export declare function isRenderable(obj: unknown): obj is Renderable;
|
|
306
359
|
export declare function isMeasurable(obj: unknown): obj is Measurable;
|
|
307
360
|
export declare function isScrollable(obj: object): obj is Scrollable;
|
|
361
|
+
export declare function isComposite(obj: object): obj is Composite;
|
package/dist/core/protocol.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
|
|
4
4
|
*/
|
|
5
5
|
import { cellCount } from "./cells.js";
|
|
6
|
-
import { DEFAULT_THEME } from "./style.js";
|
|
6
|
+
import { DEFAULT_THEME, Style } from "./style.js";
|
|
7
7
|
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
8
8
|
export const isAscii = (text) => /^[\x00-\x7F]*$/.test(text);
|
|
9
9
|
export function drawable(options, chosen, ascii, glyphs = String) {
|
|
@@ -61,6 +61,8 @@ export function fitHeight(lines, height) {
|
|
|
61
61
|
export function getStyle(options, style) {
|
|
62
62
|
return (options.theme ?? DEFAULT_THEME).resolve(style);
|
|
63
63
|
}
|
|
64
|
+
/** The terminal's own foreground on its own background. */
|
|
65
|
+
const TERMINAL_GROUND = Style.parse("default on default");
|
|
64
66
|
/**
|
|
65
67
|
* `style` as the depth the render is encoded at can draw it. A style that
|
|
66
68
|
* paints its own ground in fixed colours chose its ink and ground as a pair;
|
|
@@ -69,7 +71,10 @@ export function getStyle(options, style) {
|
|
|
69
71
|
* every bundled theme. So where the depth would turn a fixed colour of the
|
|
70
72
|
* pair into a slot, the pair is dropped and the run is drawn on the
|
|
71
73
|
* terminal's own foreground and background, which its theme does make
|
|
72
|
-
* readable; its attributes still set it apart.
|
|
74
|
+
* readable; its attributes still set it apart. Both are named, not left
|
|
75
|
+
* unset, so the colour of whatever the run sits in — a quote, a link — does
|
|
76
|
+
* not show through: Rich's code keeps its own colours inside a quote, and the
|
|
77
|
+
* terminal's are the ones it keeps here. A style whose colours are
|
|
73
78
|
* slots already (`white on blue`), or that paints no ground (`bold magenta`),
|
|
74
79
|
* was written for the terminal's own colours and is drawn as written. Rich
|
|
75
80
|
* draws a highlighted line number the same way below 256 colours
|
|
@@ -79,7 +84,7 @@ export function groundedStyle(options, style) {
|
|
|
79
84
|
const drawn = style.drawnColors(options.colorSystem ?? undefined);
|
|
80
85
|
const lost = (written, shown) => written?.fixedValue !== undefined && shown?.fixedValue === undefined;
|
|
81
86
|
const pairLost = style.bgcolor !== undefined && (lost(style.color, drawn.color) || lost(style.bgcolor, drawn.bgcolor));
|
|
82
|
-
return pairLost ? style.
|
|
87
|
+
return pairLost ? style.add(TERMINAL_GROUND) : style;
|
|
83
88
|
}
|
|
84
89
|
/**
|
|
85
90
|
* The options a renderable should work from: the caller's, with `maxWidth`
|
|
@@ -104,6 +109,51 @@ export function groundedStyle(options, style) {
|
|
|
104
109
|
export function withCellWidth(options) {
|
|
105
110
|
return { ...options, maxWidth: cellCount(options.maxWidth) };
|
|
106
111
|
}
|
|
112
|
+
/** Under one column there is nothing to draw, or to measure. */
|
|
113
|
+
export function drawsNothing(options) {
|
|
114
|
+
return cellCount(options.maxWidth) < 1;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* `renderable` drawn under `options`: Rich's `Console.render`, which every
|
|
118
|
+
* renderable there is drawn through, so under one column nothing is.
|
|
119
|
+
*
|
|
120
|
+
* [LAW:single-enforcer] The one place that rule is kept. Every call of a
|
|
121
|
+
* `Renderable`'s `render` in `src/` goes through here — a container drawing
|
|
122
|
+
* its child, a renderable drawing the text it built, a host drawing a frame —
|
|
123
|
+
* and `test/seam/render-calls.test.ts` fails on one that does not. Before
|
|
124
|
+
* this, each container asked `drawsNothing` itself, and the ones that forgot
|
|
125
|
+
* drew a child handed no column anyway.
|
|
126
|
+
*
|
|
127
|
+
* It is also the one reporter of what the view holds (`onPresent`). Under one
|
|
128
|
+
* column a renderable is held, not drawn: what it holds (`Composite`) is
|
|
129
|
+
* walked and reported held in turn, and nothing renders, so no renderable
|
|
130
|
+
* that keeps state while it renders — a `Viewport`'s offset, a `TextInput`'s
|
|
131
|
+
* scroll window — ever commits it at a width that is not shown. The walk runs
|
|
132
|
+
* whether or not anyone listens. Drawing the squeezed subtree at one column
|
|
133
|
+
* instead, and dropping what it drew, did commit that state.
|
|
134
|
+
*/
|
|
135
|
+
export function render(renderable, options) {
|
|
136
|
+
const presence = drawsNothing(options) ? "held" : "drawn";
|
|
137
|
+
options.onPresent?.(renderable, presence);
|
|
138
|
+
if (presence === "drawn")
|
|
139
|
+
return renderable.render(options);
|
|
140
|
+
if (isComposite(renderable))
|
|
141
|
+
for (const content of renderable.contents())
|
|
142
|
+
render(content, options);
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
147
|
+
* its shape draws nothing on the frame, so it reports nothing to `onPresent` —
|
|
148
|
+
* one that did would put a widget in the view at the moment a container
|
|
149
|
+
* measured it, and keep one the frame then cropped away.
|
|
150
|
+
*
|
|
151
|
+
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
152
|
+
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
153
|
+
*/
|
|
154
|
+
export function measuring(options) {
|
|
155
|
+
return { ...options, onPresent: undefined };
|
|
156
|
+
}
|
|
107
157
|
/**
|
|
108
158
|
* The options a renderable should *lay out against*: `withCellWidth`, with an
|
|
109
159
|
* unbounded offer resolved to the renderable's own natural width.
|
|
@@ -139,18 +189,6 @@ export function withCellWidth(options) {
|
|
|
139
189
|
* rather than zero, and saying so names the cause — where `String.repeat` and
|
|
140
190
|
* `new Array` only ever name their own argument.
|
|
141
191
|
*/
|
|
142
|
-
/**
|
|
143
|
-
* `options` as a measurement is asked with. A `measure` that renders to learn
|
|
144
|
-
* its shape draws nothing on the frame, so it reports no owner to `onDraw` —
|
|
145
|
-
* one that did would put a widget in document order at the moment a
|
|
146
|
-
* container measured it, and keep one the frame then cropped away.
|
|
147
|
-
*
|
|
148
|
-
* [LAW:single-enforcer] Every measurement starts here: `Measurement.get` and
|
|
149
|
-
* `withBoundedWidth` ask through it, and each `measure` passes it on.
|
|
150
|
-
*/
|
|
151
|
-
export function measuring(options) {
|
|
152
|
-
return { ...options, onDraw: undefined };
|
|
153
|
-
}
|
|
154
192
|
export function withBoundedWidth(options, self) {
|
|
155
193
|
const parsed = withCellWidth(options);
|
|
156
194
|
if (Number.isFinite(parsed.maxWidth))
|
|
@@ -182,3 +220,6 @@ export function isScrollable(obj) {
|
|
|
182
220
|
"scrollBy" in obj &&
|
|
183
221
|
typeof obj.scrollBy === "function");
|
|
184
222
|
}
|
|
223
|
+
export function isComposite(obj) {
|
|
224
|
+
return "contents" in obj && typeof obj.contents === "function";
|
|
225
|
+
}
|
package/dist/core/render.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* `Style.render` returns bare text for a null colour system.
|
|
43
43
|
*/
|
|
44
44
|
import { ColorDepth, resolveDestination } from "./color.js";
|
|
45
|
+
import { render } from "./protocol.js";
|
|
45
46
|
import { OSC8_CLOSE, osc8Open } from "./osc8.js";
|
|
46
47
|
const DEFAULT_WIDTH = 80;
|
|
47
48
|
/**
|
|
@@ -101,5 +102,5 @@ export function renderToString(renderable, options) {
|
|
|
101
102
|
// so a renderable measures what this very call will draw.
|
|
102
103
|
colorSystem: destination.colorSystem,
|
|
103
104
|
};
|
|
104
|
-
return segmentsToString(
|
|
105
|
+
return segmentsToString(render(renderable, renderOptions), destination);
|
|
105
106
|
}
|
package/dist/core/segment.d.ts
CHANGED
|
@@ -42,6 +42,15 @@ export declare class Segment {
|
|
|
42
42
|
* whatever drew it, and its anchor says so; see `./anchor.ts`.
|
|
43
43
|
*/
|
|
44
44
|
splitCells(position: CellCol): [Segment, Segment];
|
|
45
|
+
/**
|
|
46
|
+
* Cuts at each of `positions`, non-decreasing and inside this segment's
|
|
47
|
+
* cells: one piece more than there are positions, each exactly as wide as
|
|
48
|
+
* the distance between its cuts, the way `splitCells` cuts at one.
|
|
49
|
+
*
|
|
50
|
+
* [LAW:single-enforcer] Each piece but the first starts that many cells
|
|
51
|
+
* further into whatever drew it, and its anchor says so; see `./anchor.ts`.
|
|
52
|
+
*/
|
|
53
|
+
private cutAt;
|
|
45
54
|
private static _line;
|
|
46
55
|
static line(): Segment;
|
|
47
56
|
/**
|
|
@@ -150,7 +159,15 @@ export declare class Segment {
|
|
|
150
159
|
*/
|
|
151
160
|
static removeColor(segments: Iterable<Segment>): Iterable<Segment>;
|
|
152
161
|
/**
|
|
153
|
-
* Divides segments at cell positions
|
|
162
|
+
* Divides segments at cell positions: a section ending at each cut, and the
|
|
163
|
+
* cells after the last cut when there are any. A segment wider than zero
|
|
164
|
+
* that starts at a cut opens the section after it; one with no cells stays
|
|
165
|
+
* in the section it ends.
|
|
166
|
+
*
|
|
167
|
+
* One walk, and each segment is cut once at every cut that falls inside it:
|
|
168
|
+
* cutting what is left of it again at each cut re-measures and copies the
|
|
169
|
+
* rest every time, so a long line wrapped into many rows took time quadratic
|
|
170
|
+
* in its length.
|
|
154
171
|
*/
|
|
155
172
|
static divide(segments: Segment[], cuts: number[]): Segment[][];
|
|
156
173
|
/**
|
package/dist/core/segment.js
CHANGED
|
@@ -59,11 +59,10 @@ export class Segment {
|
|
|
59
59
|
return [this, new Segment("")];
|
|
60
60
|
if (position <= 0)
|
|
61
61
|
return [new Segment(""), this];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
];
|
|
62
|
+
return this.cutAt([position]);
|
|
63
|
+
}
|
|
64
|
+
cutAt(positions) {
|
|
65
|
+
return cutCells(this.text, positions).map((text, piece) => new Segment(text, piece === 0 ? this.style : this.style?.shiftedBy(positions[piece - 1])));
|
|
67
66
|
}
|
|
68
67
|
// --- Static factories ---
|
|
69
68
|
static _line;
|
|
@@ -129,8 +128,11 @@ export class Segment {
|
|
|
129
128
|
*/
|
|
130
129
|
static *applyStyle(segments, style, postStyle) {
|
|
131
130
|
// Applying no style is the identity, so the segments pass through as they
|
|
132
|
-
// are instead of each being copied into an equal one.
|
|
133
|
-
|
|
131
|
+
// are instead of each being copied into an equal one. A null style — a
|
|
132
|
+
// theme's plain entry, such as `markdown.item` — is no style.
|
|
133
|
+
const pre = style?.isNull ? undefined : style;
|
|
134
|
+
const post = postStyle?.isNull ? undefined : postStyle;
|
|
135
|
+
if (!pre && !post) {
|
|
134
136
|
yield* segments;
|
|
135
137
|
return;
|
|
136
138
|
}
|
|
@@ -140,10 +142,10 @@ export class Segment {
|
|
|
140
142
|
continue;
|
|
141
143
|
}
|
|
142
144
|
let s = segment.style;
|
|
143
|
-
if (
|
|
144
|
-
s =
|
|
145
|
-
if (
|
|
146
|
-
s = s ? s.add(
|
|
145
|
+
if (pre)
|
|
146
|
+
s = pre.add(s);
|
|
147
|
+
if (post)
|
|
148
|
+
s = s ? s.add(post) : post;
|
|
147
149
|
yield new Segment(segment.text, s, segment.control);
|
|
148
150
|
}
|
|
149
151
|
}
|
|
@@ -398,45 +400,44 @@ export class Segment {
|
|
|
398
400
|
}
|
|
399
401
|
}
|
|
400
402
|
/**
|
|
401
|
-
* Divides segments at cell positions
|
|
403
|
+
* Divides segments at cell positions: a section ending at each cut, and the
|
|
404
|
+
* cells after the last cut when there are any. A segment wider than zero
|
|
405
|
+
* that starts at a cut opens the section after it; one with no cells stays
|
|
406
|
+
* in the section it ends.
|
|
407
|
+
*
|
|
408
|
+
* One walk, and each segment is cut once at every cut that falls inside it:
|
|
409
|
+
* cutting what is left of it again at each cut re-measures and copies the
|
|
410
|
+
* rest every time, so a long line wrapped into many rows took time quadratic
|
|
411
|
+
* in its length.
|
|
402
412
|
*/
|
|
403
413
|
static divide(segments, cuts) {
|
|
404
414
|
if (cuts.length === 0)
|
|
405
415
|
return [segments];
|
|
406
|
-
const
|
|
407
|
-
let
|
|
408
|
-
let
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (currentSegment && !currentSegment.isControl && cellOffset < cut) {
|
|
419
|
-
const splitAt = asCellCol(cut - cellOffset);
|
|
420
|
-
const [left, right] = currentSegment.splitCells(splitAt);
|
|
421
|
-
if (left.hasText)
|
|
422
|
-
section.push(left);
|
|
423
|
-
cellOffset += left.cellLength;
|
|
424
|
-
currentSegment = right;
|
|
416
|
+
const sections = [[]];
|
|
417
|
+
let offset = 0;
|
|
418
|
+
let next = 0;
|
|
419
|
+
for (const segment of segments) {
|
|
420
|
+
const end = offset + segment.cellLength;
|
|
421
|
+
const inside = [];
|
|
422
|
+
for (; next < cuts.length && cuts[next] < end; next++) {
|
|
423
|
+
const cut = cuts[next];
|
|
424
|
+
if (cut <= offset)
|
|
425
|
+
sections.push([]);
|
|
426
|
+
else
|
|
427
|
+
inside.push(asCellCol(cut - offset));
|
|
425
428
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
segmentIndex++;
|
|
433
|
-
while (segmentIndex < segments.length) {
|
|
434
|
-
tail.push(segments[segmentIndex]);
|
|
435
|
-
segmentIndex++;
|
|
429
|
+
const [head, ...rest] = inside.length === 0 ? [segment] : segment.cutAt(inside);
|
|
430
|
+
sections[sections.length - 1].push(head);
|
|
431
|
+
// A piece between two cuts at one cell has no text: the section it is holds nothing.
|
|
432
|
+
for (const piece of rest)
|
|
433
|
+
sections.push(piece.hasText ? [piece] : []);
|
|
434
|
+
offset = end;
|
|
436
435
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
436
|
+
for (; next < cuts.length; next++)
|
|
437
|
+
sections.push([]);
|
|
438
|
+
if (sections[sections.length - 1].length === 0)
|
|
439
|
+
sections.pop();
|
|
440
|
+
return sections;
|
|
440
441
|
}
|
|
441
442
|
// --- Layout helpers ---
|
|
442
443
|
/**
|
package/dist/core/strip.d.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import type { Anchor } from "./anchor.js";
|
|
29
29
|
import { Segment } from "./segment.js";
|
|
30
30
|
import { Style } from "./style.js";
|
|
31
|
-
import { type Measurable, type Renderable, type RenderOptions } from "./protocol.js";
|
|
31
|
+
import { type Composite, type Measurable, type Renderable, type RenderOptions } from "./protocol.js";
|
|
32
32
|
/**
|
|
33
33
|
* Items in a `Strip` expose their per-edge style so joiners can paint the
|
|
34
34
|
* transition glyph at each boundary.
|
|
@@ -97,11 +97,12 @@ export declare function drawJoin<T extends StyledRenderable>(joiner: Joiner<T>,
|
|
|
97
97
|
* item.
|
|
98
98
|
*/
|
|
99
99
|
export declare function drawItem(item: StyledRenderable): Renderable;
|
|
100
|
-
export declare class Strip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable {
|
|
100
|
+
export declare class Strip<T extends StyledRenderable = StyledRenderable> implements Renderable, Measurable, Composite {
|
|
101
101
|
readonly items: readonly T[];
|
|
102
102
|
readonly joiner: Joiner<T>;
|
|
103
103
|
constructor(items: readonly T[], joiner: Joiner<T>);
|
|
104
104
|
render(options: RenderOptions): Iterable<Segment>;
|
|
105
|
+
contents(): Iterable<Renderable>;
|
|
105
106
|
/**
|
|
106
107
|
* [LAW:one-source-of-truth] The width of the one row it draws, and that is
|
|
107
108
|
* both bounds: the row never wraps and no item is offered less than the whole
|
package/dist/core/strip.js
CHANGED
|
@@ -31,7 +31,7 @@ import { ColorDepth, ColorSpec, SURFACE_BLACK } from "./color.js";
|
|
|
31
31
|
import { Oklch } from "./oklch.js";
|
|
32
32
|
import { cellLen } from "./cells.js";
|
|
33
33
|
import { fnv1a } from "./fnv1a.js";
|
|
34
|
-
import { drawable, getStyle, isAscii } from "./protocol.js";
|
|
34
|
+
import { drawable, getStyle, isAscii, render } from "./protocol.js";
|
|
35
35
|
/**
|
|
36
36
|
* The owner every cell a strip's joiner draws is stamped with
|
|
37
37
|
* (`Segment.anchorLines`), whatever the joiner: a seam is told by what drew
|
|
@@ -80,7 +80,7 @@ export function colorsOwnedBy(anchor, owners) {
|
|
|
80
80
|
*/
|
|
81
81
|
export function drawJoin(joiner, left, right, context) {
|
|
82
82
|
const join = joiner.join(left, right, context);
|
|
83
|
-
return { render: (options) => Segment.anchorLines([[...
|
|
83
|
+
return { render: (options) => Segment.anchorLines([[...render(join, options)]], JOIN)[0] };
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* [LAW:single-enforcer] What `item` draws, every cell stamped as the item's,
|
|
@@ -89,7 +89,7 @@ export function drawJoin(joiner, left, right, context) {
|
|
|
89
89
|
* item.
|
|
90
90
|
*/
|
|
91
91
|
export function drawItem(item) {
|
|
92
|
-
return { render: (options) => Segment.anchorLines([[...
|
|
92
|
+
return { render: (options) => Segment.anchorLines([[...render(item, options)]], item)[0] };
|
|
93
93
|
}
|
|
94
94
|
// --- Strip ---
|
|
95
95
|
export class Strip {
|
|
@@ -112,12 +112,12 @@ export class Strip {
|
|
|
112
112
|
// `left` fills the canvas, and the first item would pad over the rest of
|
|
113
113
|
// the row. Where the row sits in the width is its container's to say.
|
|
114
114
|
const row = { ...options, justify: undefined };
|
|
115
|
-
yield* drawJoin(this.joiner, null, items[0], { index: 0 })
|
|
115
|
+
yield* render(drawJoin(this.joiner, null, items[0], { index: 0 }), row);
|
|
116
116
|
for (let i = 0; i < items.length; i++) {
|
|
117
117
|
const item = items[i];
|
|
118
|
-
yield* drawItem(item)
|
|
118
|
+
yield* render(drawItem(item), row);
|
|
119
119
|
const next = i + 1 < items.length ? items[i + 1] : null;
|
|
120
|
-
yield* drawJoin(this.joiner, item, next, { index: i + 1 })
|
|
120
|
+
yield* render(drawJoin(this.joiner, item, next, { index: i + 1 }), row);
|
|
121
121
|
}
|
|
122
122
|
// [LAW:one-source-of-truth] `Strip`'s sibling `FlexStrip` ends every line
|
|
123
123
|
// it emits, including the last, and the other block-level renderables
|
|
@@ -131,6 +131,10 @@ export class Strip {
|
|
|
131
131
|
// of which renderables end their own line and which are fragments.
|
|
132
132
|
yield Segment.line();
|
|
133
133
|
}
|
|
134
|
+
// The items; a join is drawn from what the joiner emits for it, not held.
|
|
135
|
+
contents() {
|
|
136
|
+
return this.items;
|
|
137
|
+
}
|
|
134
138
|
/**
|
|
135
139
|
* [LAW:one-source-of-truth] The width of the one row it draws, and that is
|
|
136
140
|
* both bounds: the row never wraps and no item is offered less than the whole
|
|
@@ -140,7 +144,7 @@ export class Strip {
|
|
|
140
144
|
*/
|
|
141
145
|
measure(options) {
|
|
142
146
|
let width = 0;
|
|
143
|
-
for (const segment of
|
|
147
|
+
for (const segment of render(this, options))
|
|
144
148
|
width += segment.cellLength;
|
|
145
149
|
return { minimum: width, maximum: width };
|
|
146
150
|
}
|
package/dist/core/style.js
CHANGED
|
@@ -129,7 +129,8 @@ export class Style {
|
|
|
129
129
|
this.frame = options.frame;
|
|
130
130
|
this.encircle = options.encircle;
|
|
131
131
|
this.overline = options.overline;
|
|
132
|
-
|
|
132
|
+
// As the reference's `self._link`, read only for truth: an empty URL is no link.
|
|
133
|
+
this.link = options.link || undefined;
|
|
133
134
|
this.meta = options.meta;
|
|
134
135
|
this.anchor = options.anchor;
|
|
135
136
|
}
|
|
@@ -695,16 +696,26 @@ export const DEFAULT_STYLES = {
|
|
|
695
696
|
"syntax.line_number.highlight": Style.parse("bold grey93 on grey27"),
|
|
696
697
|
// Markdown
|
|
697
698
|
"markdown.h1": Style.parse("bold underline"),
|
|
698
|
-
"markdown.h2": Style.parse("
|
|
699
|
-
"markdown.h3": Style.parse("bold
|
|
700
|
-
"markdown.h4": Style.parse("
|
|
699
|
+
"markdown.h2": Style.parse("underline magenta"),
|
|
700
|
+
"markdown.h3": Style.parse("bold magenta"),
|
|
701
|
+
"markdown.h4": Style.parse("italic magenta"),
|
|
702
|
+
"markdown.h5": Style.parse("italic"),
|
|
703
|
+
"markdown.h6": Style.parse("dim"),
|
|
701
704
|
// A fixed cyan, not the theme's: `cyan` is dark enough on some themes to
|
|
702
705
|
// sink into grey11. Bold, as Rich's is: at 16 colours a renderable draws it
|
|
703
706
|
// without its pair (`groundedStyle`), and bold is what still marks code.
|
|
704
707
|
"markdown.code": Style.parse("bold dark_turquoise on grey11"),
|
|
705
|
-
"markdown.
|
|
708
|
+
"markdown.em": Style.parse("italic"),
|
|
709
|
+
"markdown.strong": Style.parse("bold"),
|
|
710
|
+
"markdown.s": Style.parse("strike"),
|
|
711
|
+
"markdown.kbd": Style.parse("bold bright_yellow"),
|
|
712
|
+
"markdown.block_quote": Style.parse("magenta"),
|
|
713
|
+
"markdown.hr": Style.parse("dim"),
|
|
714
|
+
"markdown.item": NULL_STYLE,
|
|
715
|
+
"markdown.item.bullet": Style.parse("bold"),
|
|
716
|
+
"markdown.item.number": Style.parse("cyan"),
|
|
706
717
|
"markdown.link": Style.parse("bright_blue"),
|
|
707
|
-
"markdown.link_url": Style.parse("blue"),
|
|
718
|
+
"markdown.link_url": Style.parse("underline blue"),
|
|
708
719
|
"markdown.table.border": Style.parse("cyan"),
|
|
709
720
|
"markdown.table.header": Style.parse("not bold cyan"),
|
|
710
721
|
// Progress
|
package/dist/core/text.js
CHANGED
|
@@ -9,10 +9,20 @@ import { stripOscTerminators } from "./osc8.js";
|
|
|
9
9
|
import { drawable, getStyle, withBoundedWidth } from "./protocol.js";
|
|
10
10
|
// Every C0 control and DEL except \t and \n. Rich's `Text` strips only
|
|
11
11
|
// `STRIP_CONTROL_CODES` (BEL, BS, VT, FF, CR); the rest of the set is the
|
|
12
|
-
// port's extension, so no
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
12
|
+
// port's extension, so no byte of content can start an escape sequence or move
|
|
13
|
+
// the cursor on the wire: the C1 controls (U+0080–U+009F) go as well, since a
|
|
14
|
+
// terminal reads U+009B as CSI and U+009D as OSC (TextInput refuses them for
|
|
15
|
+
// the same reason). So do the format controls that hold state past the text,
|
|
16
|
+
// also the port's extension: the bidi embeddings and overrides with their
|
|
17
|
+
// closer PDF (U+202A–U+202E), the isolates with their closer PDI
|
|
18
|
+
// (U+2066–U+2069), and the deprecated shaping and swapping switches
|
|
19
|
+
// (U+206A–U+206F). An opener left unclosed in content reverses whatever is
|
|
20
|
+
// drawn after it on the line, and a closer whose opener is gone closes
|
|
21
|
+
// nothing of the text's own. The bidi marks (LRM, RLM, ALM) hold no state and
|
|
22
|
+
// pass, since right-to-left text needs them.
|
|
23
|
+
// [LAW:single-enforcer] Single place where control chars are sanitized: every
|
|
24
|
+
// string that becomes part of a Text's characters passes here.
|
|
25
|
+
const CONTROL_CHARS_RE = /[\x00-\x08\x0B-\x1F\x7F-\x9F\u202A-\u202E\u2066-\u206F]/g;
|
|
16
26
|
export function stripControlChars(text) {
|
|
17
27
|
return text.replace(CONTROL_CHARS_RE, "");
|
|
18
28
|
}
|
|
@@ -473,10 +483,9 @@ export class RichText {
|
|
|
473
483
|
return this;
|
|
474
484
|
}
|
|
475
485
|
pad(count, char) {
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
this._spans = this._spans.map((s) => s.move(count));
|
|
486
|
+
const padding = stripControlChars(char ?? " ").repeat(count);
|
|
487
|
+
// Shift all spans right by what the left side gained.
|
|
488
|
+
this._spans = this._spans.map((s) => s.move(padding.length));
|
|
480
489
|
this._text = padding + this._text + padding;
|
|
481
490
|
return this;
|
|
482
491
|
}
|
|
@@ -492,14 +501,13 @@ export class RichText {
|
|
|
492
501
|
return this;
|
|
493
502
|
}
|
|
494
503
|
padLeft(count, char) {
|
|
495
|
-
const
|
|
496
|
-
this._spans = this._spans.map((s) => s.move(
|
|
497
|
-
this._text =
|
|
504
|
+
const padding = stripControlChars(char ?? " ").repeat(count);
|
|
505
|
+
this._spans = this._spans.map((s) => s.move(padding.length));
|
|
506
|
+
this._text = padding + this._text;
|
|
498
507
|
return this;
|
|
499
508
|
}
|
|
500
509
|
padRight(count, char) {
|
|
501
|
-
|
|
502
|
-
this._text += c.repeat(count);
|
|
510
|
+
this._text += stripControlChars(char ?? " ").repeat(count);
|
|
503
511
|
return this;
|
|
504
512
|
}
|
|
505
513
|
setLength(length) {
|
|
@@ -558,7 +566,7 @@ export class RichText {
|
|
|
558
566
|
// value, so width 0 and a marker wider than the width run the same walk
|
|
559
567
|
// as every other call: what is drawn plus what is kept is `cells` wide.
|
|
560
568
|
const cells = cellCount(width);
|
|
561
|
-
const marker = cellFit(options?.marker ?? "\u2026", cells);
|
|
569
|
+
const marker = cellFit(stripControlChars(options?.marker ?? "\u2026"), cells);
|
|
562
570
|
const budget = asCellCol(cells - cellLen(marker));
|
|
563
571
|
if (mode === "right") {
|
|
564
572
|
this._cropRightTo(budget);
|