@promptctl/rich-js 0.18.1 → 0.20.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 +11 -8
- package/dist/core/box.d.ts +3 -1
- package/dist/core/box.js +6 -13
- package/dist/core/cells.d.ts +61 -28
- package/dist/core/cells.js +171 -124
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +99 -18
- package/dist/core/color.js +334 -146
- package/dist/core/console.d.ts +38 -7
- package/dist/core/console.js +159 -90
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.js +1 -4
- package/dist/core/export-lines.d.ts +22 -13
- package/dist/core/export-lines.js +37 -19
- 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 +2 -2
- package/dist/core/highlighter.js +113 -47
- package/dist/core/json.js +1 -1
- package/dist/core/markup.d.ts +28 -7
- package/dist/core/markup.js +235 -312
- package/dist/core/measure.d.ts +3 -3
- package/dist/core/measure.js +12 -7
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/noise.d.ts +31 -0
- package/dist/core/noise.js +94 -0
- package/dist/core/osc8.d.ts +6 -6
- package/dist/core/osc8.js +9 -21
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +161 -0
- package/dist/core/place.d.ts +1 -1
- package/dist/core/place.js +4 -6
- package/dist/core/pretty.d.ts +126 -2
- package/dist/core/pretty.js +571 -105
- package/dist/core/protocol.d.ts +43 -3
- package/dist/core/protocol.js +23 -2
- package/dist/core/render.d.ts +33 -30
- package/dist/core/render.js +43 -83
- package/dist/core/segment.d.ts +6 -5
- package/dist/core/segment.js +11 -15
- package/dist/core/selection.d.ts +12 -0
- package/dist/core/selection.js +14 -0
- package/dist/core/strip.d.ts +116 -74
- package/dist/core/strip.js +266 -217
- package/dist/core/style.d.ts +27 -7
- package/dist/core/style.js +106 -41
- package/dist/core/text.d.ts +43 -16
- package/dist/core/text.js +180 -59
- package/dist/core/wrap.d.ts +5 -0
- package/dist/core/wrap.js +16 -3
- package/dist/host/app.d.ts +95 -17
- package/dist/host/app.js +201 -61
- package/dist/host/index.d.ts +4 -1
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +84 -0
- package/dist/host/input.js +498 -0
- package/dist/host/terminal-host.d.ts +15 -2
- package/dist/host/terminal-host.js +2 -0
- package/dist/index.d.ts +26 -14
- package/dist/index.js +17 -7
- package/dist/node/prompt.d.ts +13 -0
- package/dist/node/prompt.js +30 -9
- package/dist/node/save.d.ts +4 -3
- package/dist/node/save.js +6 -3
- package/dist/node/terminal-host.d.ts +10 -2
- package/dist/node/terminal-host.js +7 -0
- package/dist/node/traceback.d.ts +2 -1
- package/dist/node/traceback.js +3 -39
- package/dist/renderables/align.js +2 -9
- package/dist/renderables/columns.d.ts +45 -29
- package/dist/renderables/columns.js +142 -103
- package/dist/renderables/constrain.js +6 -12
- package/dist/renderables/effect.d.ts +113 -0
- package/dist/renderables/effect.js +182 -0
- package/dist/renderables/effects.d.ts +252 -0
- package/dist/renderables/effects.js +565 -0
- package/dist/renderables/embed.d.ts +94 -31
- package/dist/renderables/embed.js +141 -45
- package/dist/renderables/group.d.ts +6 -3
- package/dist/renderables/group.js +5 -0
- package/dist/renderables/layout.d.ts +6 -23
- package/dist/renderables/layout.js +24 -113
- package/dist/renderables/list-view.d.ts +42 -0
- package/dist/renderables/list-view.js +78 -0
- package/dist/renderables/live.d.ts +40 -16
- package/dist/renderables/live.js +96 -83
- package/dist/renderables/markdown.d.ts +16 -2
- package/dist/renderables/markdown.js +521 -118
- package/dist/renderables/padding.d.ts +22 -1
- package/dist/renderables/padding.js +30 -24
- package/dist/renderables/panel.d.ts +31 -33
- package/dist/renderables/panel.js +137 -141
- package/dist/renderables/progress.d.ts +110 -16
- package/dist/renderables/progress.js +212 -81
- package/dist/renderables/progressBar.d.ts +24 -4
- package/dist/renderables/progressBar.js +78 -23
- package/dist/renderables/prompt.d.ts +37 -15
- package/dist/renderables/prompt.js +125 -85
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +3 -2
- package/dist/renderables/rule.js +50 -52
- package/dist/renderables/spinner.d.ts +28 -2
- package/dist/renderables/spinner.js +52 -19
- package/dist/renderables/status.d.ts +13 -7
- package/dist/renderables/status.js +15 -29
- package/dist/renderables/syntax.d.ts +37 -7
- package/dist/renderables/syntax.js +134 -83
- package/dist/renderables/table.d.ts +58 -20
- package/dist/renderables/table.js +373 -197
- package/dist/renderables/traceback.d.ts +5 -4
- package/dist/renderables/traceback.js +239 -68
- package/dist/renderables/tree.d.ts +10 -2
- package/dist/renderables/tree.js +61 -34
- package/dist/renderables/viewport.d.ts +30 -0
- package/dist/renderables/viewport.js +61 -7
- package/dist/template-bindings/color-funcs.d.ts +31 -9
- package/dist/template-bindings/color-funcs.js +40 -14
- package/dist/template-bindings/helpers.d.ts +4 -2
- package/dist/template-bindings/helpers.js +5 -20
- package/dist/template-bindings/index.d.ts +29 -17
- package/dist/template-bindings/index.js +36 -20
- package/dist/template-bindings/palette-funcs.d.ts +3 -1
- package/dist/template-bindings/palette-funcs.js +11 -7
- package/dist/template-bindings/style-funcs.js +4 -2
- package/dist/themes/buildPalette.d.ts +24 -3
- package/dist/themes/buildPalette.js +52 -8
- package/dist/themes/colorMath.d.ts +29 -17
- package/dist/themes/colorMath.js +74 -48
- package/dist/themes/colorRef.d.ts +2 -17
- package/dist/themes/colorRef.js +13 -31
- package/dist/themes/palette.d.ts +24 -0
- package/dist/themes/palette.js +46 -3
- package/dist/themes/ramp.d.ts +21 -34
- package/dist/themes/ramp.js +23 -42
- package/dist/themes/registry.js +39 -28
- package/dist/widgets/button.d.ts +2 -6
- package/dist/widgets/button.js +9 -40
- package/dist/widgets/checkbox.d.ts +2 -5
- package/dist/widgets/checkbox.js +9 -31
- package/dist/widgets/dropdown.d.ts +2 -5
- package/dist/widgets/dropdown.js +17 -51
- package/dist/widgets/event-router.d.ts +31 -40
- package/dist/widgets/event-router.js +52 -483
- package/dist/widgets/index.d.ts +2 -1
- package/dist/widgets/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +2 -5
- package/dist/widgets/slider.js +11 -34
- package/dist/widgets/text-input.d.ts +19 -10
- package/dist/widgets/text-input.js +156 -147
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +2 -5
- package/dist/widgets/toggle.js +8 -31
- package/dist/widgets/types.d.ts +8 -25
- package/dist/widgets/types.js +8 -0
- package/dist/widgets/widget-app.d.ts +24 -7
- package/dist/widgets/widget-app.js +35 -18
- package/dist/widgets/widget-base.d.ts +2 -0
- package/dist/widgets/widget-base.js +9 -2
- package/package.json +31 -17
package/dist/core/protocol.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Segment } from "./segment.js";
|
|
6
6
|
import { type Style, type StyleSyntaxError, type Theme } from "./style.js";
|
|
7
|
-
import
|
|
7
|
+
import { ColorDepth } from "./color.js";
|
|
8
|
+
import type { Highlighter } from "./highlighter.js";
|
|
8
9
|
/**
|
|
9
10
|
* What becomes of a line still wider than its width once wrapping is done.
|
|
10
11
|
* `"fold"` breaks it across lines, `"crop"` cuts it at the edge, `"ellipsis"`
|
|
@@ -41,8 +42,30 @@ export interface RenderOptions {
|
|
|
41
42
|
justify?: "left" | "center" | "right" | "full";
|
|
42
43
|
overflow?: OverflowMethod;
|
|
43
44
|
noWrap?: boolean;
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Whether a string handed to a renderable — a cell, a label, a pane — is
|
|
47
|
+
* read as markup when it is drawn. Absent, it is, as a `Console`'s is unless
|
|
48
|
+
* told otherwise. A `Console` passes its own setting, and `print`'s.
|
|
49
|
+
*/
|
|
50
|
+
markup?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether a string handed to a renderable is highlighted when it is drawn,
|
|
53
|
+
* by `highlighter`. Absent, it is, as a `Console`'s is unless told
|
|
54
|
+
* otherwise. A `Console` passes its own setting, and `print`'s; a renderable
|
|
55
|
+
* that draws its strings plain, as Rich's `Panel`, `Table` and `Tree` do by
|
|
56
|
+
* default, sets it false for what it renders — Rich's
|
|
57
|
+
* `options.update(highlight=False)` — and a table column's `highlight` sets
|
|
58
|
+
* it either way. `activeHighlighter` is the one reading of the pair.
|
|
59
|
+
*/
|
|
60
|
+
highlight?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* What highlights a string handed to a renderable, while `highlight` says
|
|
63
|
+
* to. A `Console` passes its highlighter whether or not its own `highlight`
|
|
64
|
+
* is on, as Rich's `render_str` reaches `console.highlighter`, so content
|
|
65
|
+
* that turns highlighting on finds the console's highlighter there. Absent,
|
|
66
|
+
* nothing highlights.
|
|
67
|
+
*/
|
|
68
|
+
highlighter?: Highlighter;
|
|
46
69
|
/**
|
|
47
70
|
* The depth the output will be encoded at, `null` when it carries no colour.
|
|
48
71
|
* A renderable choosing between two ways of drawing something — the strip's
|
|
@@ -73,6 +96,8 @@ export interface RenderOptions {
|
|
|
73
96
|
*/
|
|
74
97
|
onDraw?: (owner: object) => void;
|
|
75
98
|
}
|
|
99
|
+
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
100
|
+
export declare const isAscii: (text: string) => boolean;
|
|
76
101
|
/**
|
|
77
102
|
* `chosen` as this output can draw it: `ascii` in its place when the output is
|
|
78
103
|
* ASCII-only and `chosen` spends a glyph outside ASCII, otherwise `chosen`
|
|
@@ -177,6 +202,21 @@ export type StyleErrorHandler = (error: StyleSyntaxError, style: string) => void
|
|
|
177
202
|
* theme })` changed nothing.
|
|
178
203
|
*/
|
|
179
204
|
export declare function getStyle(options: RenderOptions, style: string | Style): Style;
|
|
205
|
+
/**
|
|
206
|
+
* `style` as the depth the render is encoded at can draw it. A style that
|
|
207
|
+
* paints its own ground in fixed colours chose its ink and ground as a pair;
|
|
208
|
+
* at sixteen colours the writer rounds a fixed colour to an ANSI slot, which
|
|
209
|
+
* the terminal paints in its own theme's colour, and no pair of slots reads in
|
|
210
|
+
* every bundled theme. So where the depth would turn a fixed colour of the
|
|
211
|
+
* pair into a slot, the pair is dropped and the run is drawn on the
|
|
212
|
+
* terminal's own foreground and background, which its theme does make
|
|
213
|
+
* readable; its attributes still set it apart. A style whose colours are
|
|
214
|
+
* slots already (`white on blue`), or that paints no ground (`bold magenta`),
|
|
215
|
+
* was written for the terminal's own colours and is drawn as written. Rich
|
|
216
|
+
* draws a highlighted line number the same way below 256 colours
|
|
217
|
+
* (`Syntax._get_number_styles`).
|
|
218
|
+
*/
|
|
219
|
+
export declare function groundedStyle(options: RenderOptions, style: Style): Style;
|
|
180
220
|
/**
|
|
181
221
|
* The options a renderable should work from: the caller's, with `maxWidth`
|
|
182
222
|
* parsed into an actual count of cells.
|
package/dist/core/protocol.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { cellCount } from "./cells.js";
|
|
6
6
|
import { DEFAULT_THEME } from "./style.js";
|
|
7
|
-
|
|
7
|
+
/** Whether `text` is all ASCII — what an `asciiOnly` output can draw. */
|
|
8
|
+
export const isAscii = (text) => /^[\x00-\x7F]*$/.test(text);
|
|
8
9
|
export function drawable(options, chosen, ascii, glyphs = String) {
|
|
9
|
-
return options.asciiOnly === true && !
|
|
10
|
+
return options.asciiOnly === true && !isAscii(glyphs(chosen)) ? ascii : chosen;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* The budget a renderable hands the one child filling its space: its own, less
|
|
@@ -60,6 +61,26 @@ export function fitHeight(lines, height) {
|
|
|
60
61
|
export function getStyle(options, style) {
|
|
61
62
|
return (options.theme ?? DEFAULT_THEME).resolve(style);
|
|
62
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* `style` as the depth the render is encoded at can draw it. A style that
|
|
66
|
+
* paints its own ground in fixed colours chose its ink and ground as a pair;
|
|
67
|
+
* at sixteen colours the writer rounds a fixed colour to an ANSI slot, which
|
|
68
|
+
* the terminal paints in its own theme's colour, and no pair of slots reads in
|
|
69
|
+
* every bundled theme. So where the depth would turn a fixed colour of the
|
|
70
|
+
* pair into a slot, the pair is dropped and the run is drawn on the
|
|
71
|
+
* terminal's own foreground and background, which its theme does make
|
|
72
|
+
* readable; its attributes still set it apart. A style whose colours are
|
|
73
|
+
* slots already (`white on blue`), or that paints no ground (`bold magenta`),
|
|
74
|
+
* was written for the terminal's own colours and is drawn as written. Rich
|
|
75
|
+
* draws a highlighted line number the same way below 256 colours
|
|
76
|
+
* (`Syntax._get_number_styles`).
|
|
77
|
+
*/
|
|
78
|
+
export function groundedStyle(options, style) {
|
|
79
|
+
const drawn = style.drawnColors(options.colorSystem ?? undefined);
|
|
80
|
+
const lost = (written, shown) => written?.fixedValue !== undefined && shown?.fixedValue === undefined;
|
|
81
|
+
const pairLost = style.bgcolor !== undefined && (lost(style.color, drawn.color) || lost(style.bgcolor, drawn.bgcolor));
|
|
82
|
+
return pairLost ? style.withoutColor : style;
|
|
83
|
+
}
|
|
63
84
|
/**
|
|
64
85
|
* The options a renderable should work from: the caller's, with `maxWidth`
|
|
65
86
|
* parsed into an actual count of cells.
|
package/dist/core/render.d.ts
CHANGED
|
@@ -10,50 +10,57 @@
|
|
|
10
10
|
* an explicit `ColorDepth` enum / non-`"auto"` spec, or supply `env` and
|
|
11
11
|
* `isTTY` in the options so detection does not consult ambient process state.
|
|
12
12
|
*
|
|
13
|
-
* [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `
|
|
14
|
-
* and is the single way
|
|
15
|
-
* `
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
13
|
+
* [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `segmentToString`
|
|
14
|
+
* and is the single way a segment becomes wire bytes. `segmentsToString` is
|
|
15
|
+
* that encoding joined, and `Console._writeSegments`, `Painter` (every `Live`
|
|
16
|
+
* and `App` frame) and `renderToString` all go through it, so terminal output,
|
|
17
|
+
* live and app frames and string export agree by construction.
|
|
18
18
|
*
|
|
19
19
|
* [LAW:one-source-of-truth] What the encoding needs from where it writes
|
|
20
20
|
* arrives as one `Destination` value. Each caller resolves its destination
|
|
21
21
|
* once — spec, detection, and its own overrides — and hands it through whole,
|
|
22
22
|
* so a depth is never paired with a link setting taken from somewhere else.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
24
|
+
* Every segment is written alone, exactly as Python Rich 9d8f9a3 writes it
|
|
25
|
+
* (`Console._render_buffer` calls `Style.render` per segment): its SGR codes
|
|
26
|
+
* open, its text, a reset, and an OSC 8 pair around all three when it carries
|
|
27
|
+
* a link. Two adjacent segments with equal codes are two runs, not one. That
|
|
28
|
+
* costs a reset and a reopen the terminal draws identically, and it is what
|
|
29
|
+
* lets a fixture generated from the reference hold any sequence of segments,
|
|
30
|
+
* rather than only those in which no two neighbours happen to share a style
|
|
31
|
+
* (rich-render-g5g8). The price of that is that where a renderable cuts its
|
|
32
|
+
* segments is now in the bytes, so a renderable pinned against the reference
|
|
33
|
+
* has to cut where the reference cuts, as `Box`'s rules and `Table`'s blank
|
|
34
|
+
* cell lines do. A link split across segments still hovers as one link,
|
|
35
|
+
* because every pair it becomes carries the id `osc8Open` derives from the URL.
|
|
32
36
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
+
* Colour and hyperlinks are two facts about the destination:
|
|
38
|
+
* `colorSystem === null` empties every segment's SGR codes, and
|
|
39
|
+
* `hyperlinks === false` drops every segment's link. A colour depth never
|
|
40
|
+
* removes a link — a hyperlink is not a colour, and a NO_COLOR terminal still
|
|
41
|
+
* follows OSC 8. This is where the port departs from the reference, whose
|
|
42
|
+
* `Style.render` returns bare text for a null colour system.
|
|
37
43
|
*/
|
|
38
44
|
import { ColorDepth } from "./color.js";
|
|
39
|
-
import type { Destination } from "./color.js";
|
|
45
|
+
import type { ColorSystemName, Destination } from "./color.js";
|
|
46
|
+
import type { Env } from "./env.js";
|
|
40
47
|
import type { Segment } from "./segment.js";
|
|
41
48
|
import type { Renderable, RenderOptions } from "./protocol.js";
|
|
42
|
-
export interface RenderToStringOptions extends Pick<RenderOptions, "asciiOnly" | "onStyleError"> {
|
|
49
|
+
export interface RenderToStringOptions extends Pick<RenderOptions, "asciiOnly" | "theme" | "onStyleError" | "highlight" | "highlighter"> {
|
|
43
50
|
/** Cell width to render into. Default 80. */
|
|
44
51
|
width?: number;
|
|
45
52
|
/**
|
|
46
|
-
* Color encoding to emit. Accepts a
|
|
47
|
-
* `"256"`, `"ansi"`, `"none"`), a `ColorDepth` enum value, or `null` to
|
|
53
|
+
* Color encoding to emit. Accepts a `ColorSystemName` (`"auto"`,
|
|
54
|
+
* `"truecolor"`, `"256"`, `"ansi"`, `"none"`), a `ColorDepth` enum value, or `null` to
|
|
48
55
|
* strip SGR colors/attributes. Hyperlinks are `hyperlinks`' concern. Default
|
|
49
56
|
* truecolor.
|
|
50
57
|
*/
|
|
51
|
-
colorSystem?:
|
|
58
|
+
colorSystem?: ColorSystemName | ColorDepth | null;
|
|
52
59
|
/**
|
|
53
60
|
* Environment to consult when `colorSystem` is `"auto"`. Defaults to
|
|
54
61
|
* `process.env`. Pass an explicit value to keep rendering deterministic.
|
|
55
62
|
*/
|
|
56
|
-
env?:
|
|
63
|
+
env?: Env;
|
|
57
64
|
/**
|
|
58
65
|
* Whether output is going to a TTY when `colorSystem` is `"auto"`. Defaults
|
|
59
66
|
* to `process.stdout?.isTTY`. Pass an explicit value to keep rendering
|
|
@@ -71,14 +78,10 @@ export interface RenderToStringOptions extends Pick<RenderOptions, "asciiOnly" |
|
|
|
71
78
|
hyperlinks?: boolean;
|
|
72
79
|
}
|
|
73
80
|
/**
|
|
74
|
-
* Encodes
|
|
75
|
-
* `
|
|
81
|
+
* Encodes one segment as ANSI bytes for `destination`: the reference's
|
|
82
|
+
* `Style.render` for that segment, and nothing for a control segment.
|
|
76
83
|
*/
|
|
77
84
|
export declare function segmentToString(segment: Segment, destination: Destination): string;
|
|
78
|
-
/**
|
|
79
|
-
* Encodes a sequence of segments as ANSI bytes for `destination`, coalescing
|
|
80
|
-
* adjacent same-SGR segments under a single SGR open/close pair, with OSC 8
|
|
81
|
-
* link pairs nested inside per same-link sub-run.
|
|
82
|
-
*/
|
|
85
|
+
/** Encodes a sequence of segments as ANSI bytes for `destination`, one segment at a time. */
|
|
83
86
|
export declare function segmentsToString(segments: Iterable<Segment>, destination: Destination): string;
|
|
84
87
|
export declare function renderToString(renderable: Renderable, options?: RenderToStringOptions): string;
|
package/dist/core/render.js
CHANGED
|
@@ -10,106 +10,63 @@
|
|
|
10
10
|
* an explicit `ColorDepth` enum / non-`"auto"` spec, or supply `env` and
|
|
11
11
|
* `isTTY` in the options so detection does not consult ambient process state.
|
|
12
12
|
*
|
|
13
|
-
* [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `
|
|
14
|
-
* and is the single way
|
|
15
|
-
* `
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
13
|
+
* [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `segmentToString`
|
|
14
|
+
* and is the single way a segment becomes wire bytes. `segmentsToString` is
|
|
15
|
+
* that encoding joined, and `Console._writeSegments`, `Painter` (every `Live`
|
|
16
|
+
* and `App` frame) and `renderToString` all go through it, so terminal output,
|
|
17
|
+
* live and app frames and string export agree by construction.
|
|
18
18
|
*
|
|
19
19
|
* [LAW:one-source-of-truth] What the encoding needs from where it writes
|
|
20
20
|
* arrives as one `Destination` value. Each caller resolves its destination
|
|
21
21
|
* once — spec, detection, and its own overrides — and hands it through whole,
|
|
22
22
|
* so a depth is never paired with a link setting taken from somewhere else.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
24
|
+
* Every segment is written alone, exactly as Python Rich 9d8f9a3 writes it
|
|
25
|
+
* (`Console._render_buffer` calls `Style.render` per segment): its SGR codes
|
|
26
|
+
* open, its text, a reset, and an OSC 8 pair around all three when it carries
|
|
27
|
+
* a link. Two adjacent segments with equal codes are two runs, not one. That
|
|
28
|
+
* costs a reset and a reopen the terminal draws identically, and it is what
|
|
29
|
+
* lets a fixture generated from the reference hold any sequence of segments,
|
|
30
|
+
* rather than only those in which no two neighbours happen to share a style
|
|
31
|
+
* (rich-render-g5g8). The price of that is that where a renderable cuts its
|
|
32
|
+
* segments is now in the bytes, so a renderable pinned against the reference
|
|
33
|
+
* has to cut where the reference cuts, as `Box`'s rules and `Table`'s blank
|
|
34
|
+
* cell lines do. A link split across segments still hovers as one link,
|
|
35
|
+
* because every pair it becomes carries the id `osc8Open` derives from the URL.
|
|
32
36
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
+
* Colour and hyperlinks are two facts about the destination:
|
|
38
|
+
* `colorSystem === null` empties every segment's SGR codes, and
|
|
39
|
+
* `hyperlinks === false` drops every segment's link. A colour depth never
|
|
40
|
+
* removes a link — a hyperlink is not a colour, and a NO_COLOR terminal still
|
|
41
|
+
* follows OSC 8. This is where the port departs from the reference, whose
|
|
42
|
+
* `Style.render` returns bare text for a null colour system.
|
|
37
43
|
*/
|
|
38
44
|
import { ColorDepth, resolveDestination } from "./color.js";
|
|
39
45
|
import { OSC8_CLOSE, osc8Open } from "./osc8.js";
|
|
40
46
|
const DEFAULT_WIDTH = 80;
|
|
41
|
-
function segmentToPiece(segment, destination) {
|
|
42
|
-
if (segment.isControl)
|
|
43
|
-
return undefined;
|
|
44
|
-
if (segment.text.length === 0)
|
|
45
|
-
return undefined;
|
|
46
|
-
const style = segment.style;
|
|
47
|
-
if (!style || style.isNull) {
|
|
48
|
-
return { text: segment.text, sgrCodes: "", link: undefined };
|
|
49
|
-
}
|
|
50
|
-
// [LAW:one-type-per-behavior] Colour depth governs SGR only; hyperlinks are
|
|
51
|
-
// their own fact. Coupling them let a colour setting delete every control.
|
|
52
|
-
return {
|
|
53
|
-
text: segment.text,
|
|
54
|
-
sgrCodes: destination.colorSystem === null ? "" : style.toSgrCodes(destination.colorSystem),
|
|
55
|
-
link: destination.hyperlinks ? style.link : undefined,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
47
|
/**
|
|
59
|
-
* Encodes
|
|
60
|
-
* `
|
|
48
|
+
* Encodes one segment as ANSI bytes for `destination`: the reference's
|
|
49
|
+
* `Style.render` for that segment, and nothing for a control segment.
|
|
61
50
|
*/
|
|
62
51
|
export function segmentToString(segment, destination) {
|
|
63
|
-
|
|
52
|
+
if (segment.isControl)
|
|
53
|
+
return "";
|
|
54
|
+
const { text, style } = segment;
|
|
55
|
+
// As the reference's `if not text` and `if style:` — no pair around nothing.
|
|
56
|
+
if (text.length === 0 || style === undefined)
|
|
57
|
+
return text;
|
|
58
|
+
// [LAW:one-type-per-behavior] Colour depth governs SGR only; hyperlinks are
|
|
59
|
+
// their own fact. Coupling them let a colour setting delete every control.
|
|
60
|
+
const codes = destination.colorSystem === null ? "" : style.toSgrCodes(destination.colorSystem);
|
|
61
|
+
const styled = codes.length > 0 ? `\x1b[${codes}m${text}\x1b[0m` : text;
|
|
62
|
+
const link = destination.hyperlinks ? style.link : undefined;
|
|
63
|
+
return link === undefined ? styled : `${osc8Open(link)}${styled}${OSC8_CLOSE}`;
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Encodes a sequence of segments as ANSI bytes for `destination`, coalescing
|
|
67
|
-
* adjacent same-SGR segments under a single SGR open/close pair, with OSC 8
|
|
68
|
-
* link pairs nested inside per same-link sub-run.
|
|
69
|
-
*/
|
|
65
|
+
/** Encodes a sequence of segments as ANSI bytes for `destination`, one segment at a time. */
|
|
70
66
|
export function segmentsToString(segments, destination) {
|
|
71
|
-
const pieces = [];
|
|
72
|
-
for (const s of segments) {
|
|
73
|
-
const p = segmentToPiece(s, destination);
|
|
74
|
-
if (p)
|
|
75
|
-
pieces.push(p);
|
|
76
|
-
}
|
|
77
|
-
if (pieces.length === 0)
|
|
78
|
-
return "";
|
|
79
|
-
// [LAW:dataflow-not-control-flow] One linear chunk accumulator for the
|
|
80
|
-
// entire output; SGR / OSC 8 boundaries and piece texts all push into it
|
|
81
|
-
// in order. No per-link-run intermediate string, no quadratic `+=` chains.
|
|
82
67
|
const parts = [];
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const sgr = pieces[i].sgrCodes;
|
|
86
|
-
let j = i + 1;
|
|
87
|
-
while (j < pieces.length && pieces[j].sgrCodes === sgr)
|
|
88
|
-
j++;
|
|
89
|
-
if (sgr.length > 0)
|
|
90
|
-
parts.push(`\x1b[${sgr}m`);
|
|
91
|
-
let k = i;
|
|
92
|
-
while (k < j) {
|
|
93
|
-
const link = pieces[k].link;
|
|
94
|
-
let l = k + 1;
|
|
95
|
-
while (l < j && pieces[l].link === link)
|
|
96
|
-
l++;
|
|
97
|
-
if (link) {
|
|
98
|
-
parts.push(osc8Open(link));
|
|
99
|
-
for (let m = k; m < l; m++)
|
|
100
|
-
parts.push(pieces[m].text);
|
|
101
|
-
parts.push(OSC8_CLOSE);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
for (let m = k; m < l; m++)
|
|
105
|
-
parts.push(pieces[m].text);
|
|
106
|
-
}
|
|
107
|
-
k = l;
|
|
108
|
-
}
|
|
109
|
-
if (sgr.length > 0)
|
|
110
|
-
parts.push("\x1b[0m");
|
|
111
|
-
i = j;
|
|
112
|
-
}
|
|
68
|
+
for (const segment of segments)
|
|
69
|
+
parts.push(segmentToString(segment, destination));
|
|
113
70
|
return parts.join("");
|
|
114
71
|
}
|
|
115
72
|
export function renderToString(renderable, options) {
|
|
@@ -136,7 +93,10 @@ export function renderToString(renderable, options) {
|
|
|
136
93
|
maxWidth: width,
|
|
137
94
|
isTerminal: false,
|
|
138
95
|
asciiOnly: options?.asciiOnly ?? false,
|
|
96
|
+
theme: options?.theme,
|
|
139
97
|
onStyleError: options?.onStyleError,
|
|
98
|
+
highlight: options?.highlight,
|
|
99
|
+
highlighter: options?.highlighter,
|
|
140
100
|
// [LAW:one-source-of-truth] The depth the segments below are encoded at,
|
|
141
101
|
// so a renderable measures what this very call will draw.
|
|
142
102
|
colorSystem: destination.colorSystem,
|
package/dist/core/segment.d.ts
CHANGED
|
@@ -33,12 +33,13 @@ export declare class Segment {
|
|
|
33
33
|
get hasText(): boolean;
|
|
34
34
|
get isControl(): boolean;
|
|
35
35
|
/**
|
|
36
|
-
* Splits at a cell position. Returns [left, right]
|
|
36
|
+
* Splits at a cell position. Returns [left, right]: the first `position`
|
|
37
|
+
* cells and the rest, so the two halves are exactly as wide as this one —
|
|
38
|
+
* `cutCells` blanks a wide glyph the cut goes through rather than keep it
|
|
39
|
+
* whole on a side it would widen by a cell.
|
|
37
40
|
*
|
|
38
|
-
* [LAW:single-enforcer] The right half starts
|
|
39
|
-
* whatever drew it
|
|
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.
|
|
41
|
+
* [LAW:single-enforcer] The right half starts `position` cells further into
|
|
42
|
+
* whatever drew it, and its anchor says so; see `./anchor.ts`.
|
|
42
43
|
*/
|
|
43
44
|
splitCells(position: CellCol): [Segment, Segment];
|
|
44
45
|
private static _line;
|
package/dist/core/segment.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Segment — the atomic rendering unit. Every piece of styled terminal output
|
|
3
3
|
* is represented as a Segment: (text, style?, control?).
|
|
4
4
|
*/
|
|
5
|
-
import { cellLen, splitText, asCellCol } from "./cells.js";
|
|
5
|
+
import { cellLen, cutCells, splitText, asCellCol } from "./cells.js";
|
|
6
6
|
import { Style } from "./style.js";
|
|
7
7
|
import { shiftAnchor } from "./anchor.js";
|
|
8
8
|
// --- ControlType ---
|
|
@@ -45,12 +45,13 @@ export class Segment {
|
|
|
45
45
|
return this.control !== undefined;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
* Splits at a cell position. Returns [left, right]
|
|
48
|
+
* Splits at a cell position. Returns [left, right]: the first `position`
|
|
49
|
+
* cells and the rest, so the two halves are exactly as wide as this one —
|
|
50
|
+
* `cutCells` blanks a wide glyph the cut goes through rather than keep it
|
|
51
|
+
* whole on a side it would widen by a cell.
|
|
49
52
|
*
|
|
50
|
-
* [LAW:single-enforcer] The right half starts
|
|
51
|
-
* whatever drew it
|
|
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.
|
|
53
|
+
* [LAW:single-enforcer] The right half starts `position` cells further into
|
|
54
|
+
* whatever drew it, and its anchor says so; see `./anchor.ts`.
|
|
54
55
|
*/
|
|
55
56
|
splitCells(position) {
|
|
56
57
|
const len = this.cellLength;
|
|
@@ -58,10 +59,10 @@ export class Segment {
|
|
|
58
59
|
return [this, new Segment("")];
|
|
59
60
|
if (position <= 0)
|
|
60
61
|
return [new Segment(""), this];
|
|
61
|
-
const [leftText, rightText] =
|
|
62
|
+
const [leftText, rightText] = cutCells(this.text, position);
|
|
62
63
|
return [
|
|
63
64
|
new Segment(leftText, this.style),
|
|
64
|
-
new Segment(rightText, this.style?.shiftedBy(
|
|
65
|
+
new Segment(rightText, this.style?.shiftedBy(position)),
|
|
65
66
|
];
|
|
66
67
|
}
|
|
67
68
|
// --- Static factories ---
|
|
@@ -394,13 +395,8 @@ export class Segment {
|
|
|
394
395
|
for (const cut of cuts) {
|
|
395
396
|
const section = [];
|
|
396
397
|
while (currentSegment && cellOffset + currentSegment.cellLength <= cut) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
section.push(currentSegment);
|
|
402
|
-
cellOffset += currentSegment.cellLength;
|
|
403
|
-
}
|
|
398
|
+
section.push(currentSegment);
|
|
399
|
+
cellOffset += currentSegment.cellLength;
|
|
404
400
|
segmentIndex++;
|
|
405
401
|
currentSegment = segments[segmentIndex];
|
|
406
402
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list cursor: one clamp, shared by every list that moves a selection —
|
|
3
|
+
* `ListView`'s callers and `Dropdown` alike — so it has one definition.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The selection `delta` items on from `index` in a list of `length`, held to
|
|
7
|
+
* the list's ends: `-Infinity` is the first item and `Infinity` the last. A
|
|
8
|
+
* move that cannot go anywhere returns `index` itself, so `next === index`
|
|
9
|
+
* says nothing moved and the caller can skip a repaint. An empty list has
|
|
10
|
+
* nowhere to move to and returns `index`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function moveSelection(index: number, delta: number, length: number): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list cursor: one clamp, shared by every list that moves a selection —
|
|
3
|
+
* `ListView`'s callers and `Dropdown` alike — so it has one definition.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The selection `delta` items on from `index` in a list of `length`, held to
|
|
7
|
+
* the list's ends: `-Infinity` is the first item and `Infinity` the last. A
|
|
8
|
+
* move that cannot go anywhere returns `index` itself, so `next === index`
|
|
9
|
+
* says nothing moved and the caller can skip a repaint. An empty list has
|
|
10
|
+
* nowhere to move to and returns `index`.
|
|
11
|
+
*/
|
|
12
|
+
export function moveSelection(index, delta, length) {
|
|
13
|
+
return length === 0 ? index : Math.max(0, Math.min(length - 1, index + delta));
|
|
14
|
+
}
|