@promptctl/rich-js 0.17.0 → 0.18.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 +13 -26
- package/package.json +7 -3
- package/dist/core/box.d.ts +0 -144
- package/dist/core/box.js +0 -391
- package/dist/core/cells.d.ts +0 -148
- package/dist/core/cells.js +0 -266
- package/dist/core/color.d.ts +0 -250
- package/dist/core/color.js +0 -1029
- package/dist/core/console.d.ts +0 -156
- package/dist/core/console.js +0 -472
- package/dist/core/emoji.d.ts +0 -24
- package/dist/core/emoji.js +0 -391
- package/dist/core/export-html.d.ts +0 -24
- package/dist/core/export-html.js +0 -106
- package/dist/core/export-lines.d.ts +0 -121
- package/dist/core/export-lines.js +0 -141
- package/dist/core/highlighter.d.ts +0 -33
- package/dist/core/highlighter.js +0 -124
- package/dist/core/json.d.ts +0 -27
- package/dist/core/json.js +0 -53
- package/dist/core/markup.d.ts +0 -97
- package/dist/core/markup.js +0 -619
- package/dist/core/measure.d.ts +0 -27
- package/dist/core/measure.js +0 -61
- package/dist/core/oklch.d.ts +0 -161
- package/dist/core/oklch.js +0 -312
- package/dist/core/osc8.d.ts +0 -68
- package/dist/core/osc8.js +0 -90
- package/dist/core/pretty.d.ts +0 -141
- package/dist/core/pretty.js +0 -472
- package/dist/core/protocol.d.ts +0 -137
- package/dist/core/protocol.js +0 -101
- package/dist/core/render.d.ts +0 -84
- package/dist/core/render.js +0 -145
- package/dist/core/segment.d.ts +0 -142
- package/dist/core/segment.js +0 -433
- package/dist/core/spinnerData.d.ts +0 -10
- package/dist/core/spinnerData.js +0 -115
- package/dist/core/strip.d.ts +0 -145
- package/dist/core/strip.js +0 -308
- package/dist/core/style.d.ts +0 -144
- package/dist/core/style.js +0 -723
- package/dist/core/subscription.d.ts +0 -12
- package/dist/core/subscription.js +0 -12
- package/dist/core/text.d.ts +0 -255
- package/dist/core/text.js +0 -990
- package/dist/core/wrap.d.ts +0 -41
- package/dist/core/wrap.js +0 -127
- package/dist/host/host-stream.d.ts +0 -30
- package/dist/host/host-stream.js +0 -35
- package/dist/host/index.d.ts +0 -3
- package/dist/host/index.js +0 -2
- package/dist/host/terminal-host.d.ts +0 -143
- package/dist/host/terminal-host.js +0 -132
- package/dist/index.d.ts +0 -87
- package/dist/index.js +0 -104
- package/dist/node/prompt.d.ts +0 -21
- package/dist/node/prompt.js +0 -30
- package/dist/node/save.d.ts +0 -17
- package/dist/node/save.js +0 -21
- package/dist/node/terminal-host.d.ts +0 -84
- package/dist/node/terminal-host.js +0 -160
- package/dist/node/traceback.d.ts +0 -27
- package/dist/node/traceback.js +0 -112
- package/dist/renderables/align.d.ts +0 -16
- package/dist/renderables/align.js +0 -46
- package/dist/renderables/columns.d.ts +0 -61
- package/dist/renderables/columns.js +0 -168
- package/dist/renderables/constrain.d.ts +0 -15
- package/dist/renderables/constrain.js +0 -37
- package/dist/renderables/flexStrip.d.ts +0 -41
- package/dist/renderables/flexStrip.js +0 -200
- package/dist/renderables/group.d.ts +0 -11
- package/dist/renderables/group.js +0 -15
- package/dist/renderables/layout.d.ts +0 -92
- package/dist/renderables/layout.js +0 -315
- package/dist/renderables/live.d.ts +0 -47
- package/dist/renderables/live.js +0 -131
- package/dist/renderables/markdown.d.ts +0 -24
- package/dist/renderables/markdown.js +0 -182
- package/dist/renderables/padding.d.ts +0 -38
- package/dist/renderables/padding.js +0 -140
- package/dist/renderables/panel.d.ts +0 -116
- package/dist/renderables/panel.js +0 -322
- package/dist/renderables/progress.d.ts +0 -90
- package/dist/renderables/progress.js +0 -241
- package/dist/renderables/progressBar.d.ts +0 -31
- package/dist/renderables/progressBar.js +0 -52
- package/dist/renderables/prompt.d.ts +0 -49
- package/dist/renderables/prompt.js +0 -125
- package/dist/renderables/rule.d.ts +0 -24
- package/dist/renderables/rule.js +0 -90
- package/dist/renderables/spinner.d.ts +0 -29
- package/dist/renderables/spinner.js +0 -64
- package/dist/renderables/status.d.ts +0 -23
- package/dist/renderables/status.js +0 -60
- package/dist/renderables/syntax.d.ts +0 -35
- package/dist/renderables/syntax.js +0 -114
- package/dist/renderables/table.d.ts +0 -182
- package/dist/renderables/table.js +0 -697
- package/dist/renderables/traceback.d.ts +0 -31
- package/dist/renderables/traceback.js +0 -109
- package/dist/renderables/tree.d.ts +0 -54
- package/dist/renderables/tree.js +0 -159
- package/dist/template-bindings/color-funcs.d.ts +0 -87
- package/dist/template-bindings/color-funcs.js +0 -219
- package/dist/template-bindings/helpers.d.ts +0 -24
- package/dist/template-bindings/helpers.js +0 -48
- package/dist/template-bindings/index.d.ts +0 -107
- package/dist/template-bindings/index.js +0 -144
- package/dist/template-bindings/palette-funcs.d.ts +0 -94
- package/dist/template-bindings/palette-funcs.js +0 -151
- package/dist/template-bindings/style-funcs.d.ts +0 -28
- package/dist/template-bindings/style-funcs.js +0 -151
- package/dist/themes/buildPalette.d.ts +0 -36
- package/dist/themes/buildPalette.js +0 -56
- package/dist/themes/colorMath.d.ts +0 -82
- package/dist/themes/colorMath.js +0 -254
- package/dist/themes/colorRef.d.ts +0 -51
- package/dist/themes/colorRef.js +0 -96
- package/dist/themes/data/atom-one-dark.d.ts +0 -3
- package/dist/themes/data/atom-one-dark.js +0 -158
- package/dist/themes/data/atom-one-light.d.ts +0 -3
- package/dist/themes/data/atom-one-light.js +0 -158
- package/dist/themes/data/catppuccin-frappe.d.ts +0 -3
- package/dist/themes/data/catppuccin-frappe.js +0 -158
- package/dist/themes/data/catppuccin-latte.d.ts +0 -3
- package/dist/themes/data/catppuccin-latte.js +0 -158
- package/dist/themes/data/catppuccin-macchiato.d.ts +0 -3
- package/dist/themes/data/catppuccin-macchiato.js +0 -158
- package/dist/themes/data/catppuccin-mocha.d.ts +0 -3
- package/dist/themes/data/catppuccin-mocha.js +0 -158
- package/dist/themes/data/cyberpunk.d.ts +0 -17
- package/dist/themes/data/cyberpunk.js +0 -191
- package/dist/themes/data/default.d.ts +0 -3
- package/dist/themes/data/default.js +0 -158
- package/dist/themes/data/dracula.d.ts +0 -3
- package/dist/themes/data/dracula.js +0 -158
- package/dist/themes/data/flexoki.d.ts +0 -3
- package/dist/themes/data/flexoki.js +0 -158
- package/dist/themes/data/gruvbox.d.ts +0 -3
- package/dist/themes/data/gruvbox.js +0 -158
- package/dist/themes/data/index.d.ts +0 -35
- package/dist/themes/data/index.js +0 -64
- package/dist/themes/data/monokai.d.ts +0 -3
- package/dist/themes/data/monokai.js +0 -158
- package/dist/themes/data/nord.d.ts +0 -3
- package/dist/themes/data/nord.js +0 -158
- package/dist/themes/data/rose-pine-dawn.d.ts +0 -3
- package/dist/themes/data/rose-pine-dawn.js +0 -158
- package/dist/themes/data/rose-pine-moon.d.ts +0 -3
- package/dist/themes/data/rose-pine-moon.js +0 -158
- package/dist/themes/data/rose-pine.d.ts +0 -3
- package/dist/themes/data/rose-pine.js +0 -158
- package/dist/themes/data/solarized-dark.d.ts +0 -3
- package/dist/themes/data/solarized-dark.js +0 -158
- package/dist/themes/data/solarized-light.d.ts +0 -3
- package/dist/themes/data/solarized-light.js +0 -158
- package/dist/themes/data/svg-export.d.ts +0 -3
- package/dist/themes/data/svg-export.js +0 -158
- package/dist/themes/data/textual-ansi.d.ts +0 -3
- package/dist/themes/data/textual-ansi.js +0 -158
- package/dist/themes/data/textual-dark.d.ts +0 -3
- package/dist/themes/data/textual-dark.js +0 -158
- package/dist/themes/data/textual-light.d.ts +0 -3
- package/dist/themes/data/textual-light.js +0 -158
- package/dist/themes/data/tokyo-night.d.ts +0 -3
- package/dist/themes/data/tokyo-night.js +0 -158
- package/dist/themes/data/types.d.ts +0 -15
- package/dist/themes/data/types.js +0 -1
- package/dist/themes/palette.d.ts +0 -21
- package/dist/themes/palette.js +0 -28
- package/dist/themes/ramp.d.ts +0 -83
- package/dist/themes/ramp.js +0 -122
- package/dist/themes/registry.d.ts +0 -43
- package/dist/themes/registry.js +0 -82
- package/dist/themes/terminalThemes.d.ts +0 -54
- package/dist/themes/terminalThemes.js +0 -68
- package/dist/themes/transpose.d.ts +0 -87
- package/dist/themes/transpose.js +0 -138
- package/dist/widgets/button.d.ts +0 -49
- package/dist/widgets/button.js +0 -206
- package/dist/widgets/checkbox.d.ts +0 -41
- package/dist/widgets/checkbox.js +0 -155
- package/dist/widgets/dropdown.d.ts +0 -67
- package/dist/widgets/dropdown.js +0 -453
- package/dist/widgets/event-router.d.ts +0 -78
- package/dist/widgets/event-router.js +0 -622
- package/dist/widgets/focus-manager.d.ts +0 -26
- package/dist/widgets/focus-manager.js +0 -193
- package/dist/widgets/index.d.ts +0 -22
- package/dist/widgets/index.js +0 -15
- package/dist/widgets/screen.d.ts +0 -98
- package/dist/widgets/screen.js +0 -530
- package/dist/widgets/slider.d.ts +0 -57
- package/dist/widgets/slider.js +0 -270
- package/dist/widgets/static-item.d.ts +0 -40
- package/dist/widgets/static-item.js +0 -60
- package/dist/widgets/text-input.d.ts +0 -274
- package/dist/widgets/text-input.js +0 -1162
- package/dist/widgets/toggle.d.ts +0 -44
- package/dist/widgets/toggle.js +0 -179
- package/dist/widgets/types.d.ts +0 -108
- package/dist/widgets/types.js +0 -31
- package/dist/widgets/widget-base.d.ts +0 -42
- package/dist/widgets/widget-base.js +0 -173
package/dist/core/markup.js
DELETED
|
@@ -1,619 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Markup — BBCode-inspired markup parser for inline styling.
|
|
3
|
-
* Parses `[bold red]text[/bold red]` into RichText with styled spans.
|
|
4
|
-
*
|
|
5
|
-
* Plugin tags ([LAW:locality-or-seam]): a `MarkupRegistry` lets consumers
|
|
6
|
-
* register tag handlers without forking the parser. When the parser sees
|
|
7
|
-
* `[name attrs...]inner[/name]` and `name` is registered, it parses attrs,
|
|
8
|
-
* recursively renders the inner markup as a child Renderable, and calls the
|
|
9
|
-
* handler — splicing the handler's returned Renderable into the output. The
|
|
10
|
-
* built-in style dialect and the plugin dialect are routed by the registry,
|
|
11
|
-
* which is the single trust boundary between them.
|
|
12
|
-
*
|
|
13
|
-
* Plugin pairs must nest. The built-in dialect admits non-strict nesting
|
|
14
|
-
* (`[bold]a[italic]b[/bold]c[/italic]`) because a style is an annotation and
|
|
15
|
-
* annotations may overlap freely; a plugin tag is a replacement whose handler
|
|
16
|
-
* takes one contiguous `inner`, so an overlapping pair has no slice to hand it
|
|
17
|
-
* and is rejected with a `MarkupSyntaxError`.
|
|
18
|
-
*/
|
|
19
|
-
import { cellLen } from "./cells.js";
|
|
20
|
-
import { Style, StyleSyntaxError } from "./style.js";
|
|
21
|
-
import { RichText, Span } from "./text.js";
|
|
22
|
-
import { emojiReplace } from "./emoji.js";
|
|
23
|
-
// --- Tag ---
|
|
24
|
-
export class Tag {
|
|
25
|
-
name;
|
|
26
|
-
parameters;
|
|
27
|
-
constructor(name, parameters) {
|
|
28
|
-
this.name = name;
|
|
29
|
-
this.parameters = parameters;
|
|
30
|
-
}
|
|
31
|
-
toString() {
|
|
32
|
-
return this.parameters !== undefined
|
|
33
|
-
? `${this.name} ${this.parameters}`
|
|
34
|
-
: this.name;
|
|
35
|
-
}
|
|
36
|
-
get markup() {
|
|
37
|
-
return this.parameters !== undefined
|
|
38
|
-
? `[${this.name}=${this.parameters}]`
|
|
39
|
-
: `[${this.name}]`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// --- MarkupError ---
|
|
43
|
-
export class MarkupError extends Error {
|
|
44
|
-
constructor(message) {
|
|
45
|
-
super(message);
|
|
46
|
-
this.name = "MarkupError";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// --- MarkupSyntaxError ---
|
|
50
|
-
/**
|
|
51
|
-
* A markup string that cannot be parsed, with where it went wrong.
|
|
52
|
-
*
|
|
53
|
-
* [LAW:types-are-the-program] A subclass rather than optional fields on
|
|
54
|
-
* `MarkupError`, because the parent is also what `MarkupRegistry.register`
|
|
55
|
-
* throws, and a registration has no source text to point into. Every error this
|
|
56
|
-
* class describes has a location, so none of its fields is optional and no
|
|
57
|
-
* caller has to ask whether one is present — `instanceof` is the only question.
|
|
58
|
-
*
|
|
59
|
-
* [LAW:one-source-of-truth] The constructor takes the facts the parser holds —
|
|
60
|
-
* the string, the offset of the offending tag, what was open — and derives
|
|
61
|
-
* `line`, `column` and the message from them, so the numbers a caller reads
|
|
62
|
-
* and the caret the message draws cannot disagree.
|
|
63
|
-
*/
|
|
64
|
-
export class MarkupSyntaxError extends MarkupError {
|
|
65
|
-
/** The problem alone, with no location, for callers building their own message. */
|
|
66
|
-
reason;
|
|
67
|
-
/** The whole markup string that failed, as the caller passed it. */
|
|
68
|
-
markup;
|
|
69
|
-
/** Index into `markup` of the tag the parser rejected. */
|
|
70
|
-
offset;
|
|
71
|
-
/** 1-based line of `offset`; lines are separated by `\n`. */
|
|
72
|
-
line;
|
|
73
|
-
/** 1-based column of `offset` within its line, in UTF-16 code units. */
|
|
74
|
-
column;
|
|
75
|
-
/**
|
|
76
|
-
* The opening tags still open at `offset`, outermost first, as written. For
|
|
77
|
-
* overlapping plugin tags, only plugin tags are named.
|
|
78
|
-
*/
|
|
79
|
-
openTags;
|
|
80
|
-
constructor(reason, markup, offset, openTags) {
|
|
81
|
-
const lineStart = markup.lastIndexOf("\n", offset - 1) + 1;
|
|
82
|
-
const lineEnd = markup.indexOf("\n", offset);
|
|
83
|
-
const text = markup.slice(lineStart, lineEnd === -1 ? markup.length : lineEnd);
|
|
84
|
-
const line = countNewlines(markup.slice(0, lineStart)) + 1;
|
|
85
|
-
const column = offset - lineStart + 1;
|
|
86
|
-
const open = openTags.length > 0 ? openTags.join(" ") : "none";
|
|
87
|
-
super(`${printable(reason)} (line ${line}, column ${column})\n` +
|
|
88
|
-
`${excerpt(text, offset - lineStart)}\n` +
|
|
89
|
-
`Open tags: ${printable(open)}`);
|
|
90
|
-
this.name = "MarkupSyntaxError";
|
|
91
|
-
this.reason = reason;
|
|
92
|
-
this.markup = markup;
|
|
93
|
-
this.offset = offset;
|
|
94
|
-
this.line = line;
|
|
95
|
-
this.column = column;
|
|
96
|
-
this.openTags = openTags;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function countNewlines(text) {
|
|
100
|
-
return text.split("\n").length - 1;
|
|
101
|
-
}
|
|
102
|
-
// Code points shown either side of the error before the excerpt is cut and
|
|
103
|
-
// marked with an ellipsis. Enough to recognise the surrounding tags on a line
|
|
104
|
-
// that is long, templated, or machine-assembled, without printing all of it.
|
|
105
|
-
const EXCERPT_CONTEXT = 30;
|
|
106
|
-
/**
|
|
107
|
-
* `text` with each C0 control and DEL shown as one space.
|
|
108
|
-
*
|
|
109
|
-
* [LAW:single-enforcer] Every part of the message that quotes the caller's
|
|
110
|
-
* markup passes through here: the reason and the open tags quote tag text, and
|
|
111
|
-
* the tag grammar admits control characters, so an `ESC ]` inside a tag would
|
|
112
|
-
* otherwise open an OSC sequence in the terminal printing the error. In the
|
|
113
|
-
* excerpt it also keeps the caret aligned, since a control occupies no cell.
|
|
114
|
-
* The fields keep the text as written; only the message is made printable.
|
|
115
|
-
*/
|
|
116
|
-
function printable(text) {
|
|
117
|
-
return text.replace(/[\x00-\x1f\x7f]/g, " ");
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Two lines: the source line around `index`, and a caret under it.
|
|
121
|
-
*
|
|
122
|
-
* The window is cut in code points, so a surrogate pair is never split, and the
|
|
123
|
-
* caret is placed by cell width, so a wide character before the error still
|
|
124
|
-
* leaves it under the right cell.
|
|
125
|
-
*/
|
|
126
|
-
function excerpt(text, index) {
|
|
127
|
-
const chars = Array.from(printable(text));
|
|
128
|
-
const at = Array.from(text.slice(0, index)).length;
|
|
129
|
-
const from = Math.max(0, at - EXCERPT_CONTEXT);
|
|
130
|
-
const to = Math.min(chars.length, at + EXCERPT_CONTEXT);
|
|
131
|
-
const head = from > 0 ? "…" : "";
|
|
132
|
-
const tail = to < chars.length ? "…" : "";
|
|
133
|
-
const before = head + chars.slice(from, at).join("");
|
|
134
|
-
const shown = before + chars.slice(at, to).join("") + tail;
|
|
135
|
-
return ` ${shown}\n ${" ".repeat(cellLen(before))}^`;
|
|
136
|
-
}
|
|
137
|
-
// --- escape ---
|
|
138
|
-
/**
|
|
139
|
-
* Escapes markup characters so they render as literal text.
|
|
140
|
-
*/
|
|
141
|
-
export function escape(text) {
|
|
142
|
-
return text.replace(/\[/g, "\\[");
|
|
143
|
-
}
|
|
144
|
-
// --- render ---
|
|
145
|
-
// Fast path: if no `[` in text, skip parsing entirely
|
|
146
|
-
const HAS_TAG_RE = /\[/;
|
|
147
|
-
// [LAW:one-source-of-truth] The authority for this grammar is `RE_TAGS` in
|
|
148
|
-
// `rich/markup.py`, and this is deliberately its expression rather than a
|
|
149
|
-
// re-derivation, so the two can be read side by side; `[^[]*?` is lazy against
|
|
150
|
-
// the following `]`, which is what stops the body at the first one.
|
|
151
|
-
//
|
|
152
|
-
// The enumeration it replaced — `[a-zA-Z#][a-zA-Z0-9_.# -]*` — was a second
|
|
153
|
-
// drawing of the same territory and had drifted both ways at once: it invented
|
|
154
|
-
// uppercase, so `[INFO]` was taken as an opening tag and its text vanished from
|
|
155
|
-
// every `Console.print` with no error, and it omitted `@` and the punctuation
|
|
156
|
-
// the reference admits, so `[a+b]` stayed literal (rich-markup-sec).
|
|
157
|
-
// `markup-grammar.golden.txt` pins both directions.
|
|
158
|
-
// The leading alternative handles an escaped bracket: \[
|
|
159
|
-
const TAG_RE = /(?:\\\[)|(\[[a-z#/@][^[]*?\])/g;
|
|
160
|
-
function parseTags(markup) {
|
|
161
|
-
const tags = [];
|
|
162
|
-
const re = new RegExp(TAG_RE.source, TAG_RE.flags);
|
|
163
|
-
let match;
|
|
164
|
-
while ((match = re.exec(markup)) !== null) {
|
|
165
|
-
// Escaped bracket
|
|
166
|
-
if (match[0] === "\\[")
|
|
167
|
-
continue;
|
|
168
|
-
const captured = match[1];
|
|
169
|
-
if (!captured)
|
|
170
|
-
continue;
|
|
171
|
-
const inner = captured.slice(1, -1); // Remove [ ]
|
|
172
|
-
const isClose = inner.startsWith("/");
|
|
173
|
-
const stylePart = isClose ? inner.slice(1) : inner;
|
|
174
|
-
// Check for parameters (name=value)
|
|
175
|
-
const eqIdx = stylePart.indexOf("=");
|
|
176
|
-
const styleName = eqIdx >= 0 ? stylePart.slice(0, eqIdx).trim() : stylePart.trim();
|
|
177
|
-
const parameters = eqIdx >= 0 ? stylePart.slice(eqIdx + 1) : undefined;
|
|
178
|
-
// [LAW:types-are-the-program] What separates `[/]` from `[/red]` is whether
|
|
179
|
-
// a name survives the slash, which is how the reference asks it too
|
|
180
|
-
// (`style_name = tag.name[1:].strip()`). Testing the raw tag text against
|
|
181
|
-
// the literal `"/"` answered a narrower question, and the widened grammar
|
|
182
|
-
// is what exposed the gap: `[/ ]` reaches here now, and under the old test
|
|
183
|
-
// it was an explicit close of the empty style rather than an implicit one.
|
|
184
|
-
const isImplicitClose = isClose && styleName === "";
|
|
185
|
-
const isClosing = isClose && !isImplicitClose;
|
|
186
|
-
tags.push({
|
|
187
|
-
fullMatch: captured,
|
|
188
|
-
isClosing,
|
|
189
|
-
isImplicitClose,
|
|
190
|
-
styleName,
|
|
191
|
-
parameters,
|
|
192
|
-
start: match.index,
|
|
193
|
-
end: match.index + match[0].length,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
return tags;
|
|
197
|
-
}
|
|
198
|
-
function wholeString(markup) {
|
|
199
|
-
return { source: markup, base: 0, enclosing: [] };
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Parses the built-in style dialect — `[bold red]text[/bold red]` — into a
|
|
203
|
-
* `RichText` with styled spans. Module-private on purpose: `renderMarkup` is
|
|
204
|
-
* this module's one crossing, and it delegates straight here the moment a
|
|
205
|
-
* string carries no paired plugin tag.
|
|
206
|
-
*
|
|
207
|
-
* [LAW:single-enforcer] Exporting this is what let a caller bind to the inner
|
|
208
|
-
* layer, and two of them did — `console.ts` and `prompt.ts` imported it as
|
|
209
|
-
* `render as renderMarkup`, so the import line read identically to the
|
|
210
|
-
* plugin-aware sites and the difference was invisible at every point of use. A
|
|
211
|
-
* tag registered on `globalMarkupRegistry` resolved in a table cell and was
|
|
212
|
-
* silently eaten by `console.print`, the path almost every consumer takes
|
|
213
|
-
* (rich-markup-pcp). Unexported, that drift is a compile error rather than a
|
|
214
|
-
* rule someone has to keep remembering.
|
|
215
|
-
*
|
|
216
|
-
* [LAW:dataflow-not-control-flow] Rendering *without* plugins stays reachable
|
|
217
|
-
* as a value rather than a second name: `renderMarkup(s, { registry: new
|
|
218
|
-
* MarkupRegistry() })`. Two exported functions put that variability in the
|
|
219
|
-
* function names; an empty registry puts it in the data, where the one
|
|
220
|
-
* boundary admits both.
|
|
221
|
-
*/
|
|
222
|
-
function render(markup, origin, baseStyle, options) {
|
|
223
|
-
// Fast path: no brackets at all
|
|
224
|
-
if (!HAS_TAG_RE.test(markup)) {
|
|
225
|
-
let text = markup;
|
|
226
|
-
if (options?.emoji !== false) {
|
|
227
|
-
text = emojiReplace(text);
|
|
228
|
-
}
|
|
229
|
-
const result = new RichText(text);
|
|
230
|
-
if (baseStyle)
|
|
231
|
-
result.stylize(baseStyle);
|
|
232
|
-
return result;
|
|
233
|
-
}
|
|
234
|
-
const tags = parseTags(markup);
|
|
235
|
-
const doEmoji = options?.emoji !== false;
|
|
236
|
-
// Build plain text and track spans
|
|
237
|
-
let plainText = "";
|
|
238
|
-
const spans = [];
|
|
239
|
-
const openStack = [];
|
|
240
|
-
const unparsable = (reason, tag) => new MarkupSyntaxError(reason, origin.source, origin.base + tag.start, [
|
|
241
|
-
...origin.enclosing,
|
|
242
|
-
...openStack.map((opened) => opened.tag),
|
|
243
|
-
]);
|
|
244
|
-
let lastEnd = 0;
|
|
245
|
-
for (const tag of tags) {
|
|
246
|
-
// Add text before this tag
|
|
247
|
-
const textBefore = markup.slice(lastEnd, tag.start);
|
|
248
|
-
const processed = unescapeBrackets(doEmoji ? emojiReplace(textBefore) : textBefore);
|
|
249
|
-
plainText += processed;
|
|
250
|
-
if (tag.isImplicitClose) {
|
|
251
|
-
// [/] — close the most recent open tag
|
|
252
|
-
if (openStack.length === 0) {
|
|
253
|
-
// [/] never closes a plugin tag, so an enclosing plugin tag can be open
|
|
254
|
-
// here while no style tag is.
|
|
255
|
-
throw unparsable(`Closing tag ${tag.fullMatch} has no open style tag to close`, tag);
|
|
256
|
-
}
|
|
257
|
-
const opened = openStack.pop();
|
|
258
|
-
spans.push(new Span(opened.textStart, plainText.length, openTagStyle(opened)));
|
|
259
|
-
}
|
|
260
|
-
else if (tag.isClosing) {
|
|
261
|
-
// [/style] — find and close matching open tag
|
|
262
|
-
const idx = findLastOpen(openStack, tag.styleName);
|
|
263
|
-
if (idx === -1) {
|
|
264
|
-
throw unparsable(`Closing tag ${tag.fullMatch} doesn't match any open tag`, tag);
|
|
265
|
-
}
|
|
266
|
-
const opened = openStack[idx];
|
|
267
|
-
spans.push(new Span(opened.textStart, plainText.length, openTagStyle(opened)));
|
|
268
|
-
openStack.splice(idx, 1);
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
// Opening tag
|
|
272
|
-
openStack.push({
|
|
273
|
-
tag: tag.fullMatch,
|
|
274
|
-
styleName: tag.styleName,
|
|
275
|
-
parameters: tag.parameters,
|
|
276
|
-
textStart: plainText.length,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
lastEnd = tag.end;
|
|
280
|
-
}
|
|
281
|
-
// Add remaining text after last tag
|
|
282
|
-
const trailing = markup.slice(lastEnd);
|
|
283
|
-
const processedTrailing = unescapeBrackets(doEmoji ? emojiReplace(trailing) : trailing);
|
|
284
|
-
plainText += processedTrailing;
|
|
285
|
-
// Auto-close what is still open, innermost first. The reference pops its
|
|
286
|
-
// stack here (`while style_stack: start, tag = style_stack.pop()`), and
|
|
287
|
-
// popping is what keeps every span in this list in the order its tag closed
|
|
288
|
-
// — the one order the sort below is defined against. Walking the stack
|
|
289
|
-
// forwards instead put the outermost unclosed tag in first, and that lone
|
|
290
|
-
// disagreement was invisible while nothing sorted: it cancelled the missing
|
|
291
|
-
// sort, so `[red][blue]x` was the one nesting this port already got right.
|
|
292
|
-
while (openStack.length > 0) {
|
|
293
|
-
const opened = openStack.pop();
|
|
294
|
-
spans.push(new Span(opened.textStart, plainText.length, openTagStyle(opened)));
|
|
295
|
-
}
|
|
296
|
-
// [LAW:one-source-of-truth] The paint order is the reference's, expressed
|
|
297
|
-
// rather than re-derived: `sorted(spans[::-1], key=attrgetter("start"))` in
|
|
298
|
-
// `rich/markup.py`. `RichText` applies spans in list order and each one adds
|
|
299
|
-
// over the last, so this list's order *is* which style wins where two
|
|
300
|
-
// overlap; sorting by start lays the outer span down first and lets the
|
|
301
|
-
// inner one repaint the run it sits in.
|
|
302
|
-
//
|
|
303
|
-
// The reversal is the whole of the tie-break, not decoration.
|
|
304
|
-
// `[red][blue]x[/blue][/red]` yields two spans that both start at 0, and a
|
|
305
|
-
// stable sort leaves those in the order they arrived — closing order,
|
|
306
|
-
// innermost first — which is the outer colour winning. Reversed first, the
|
|
307
|
-
// outer arrives first and the inner repaints it (rich-markup-krk).
|
|
308
|
-
spans.reverse();
|
|
309
|
-
spans.sort((a, b) => a.start - b.start);
|
|
310
|
-
const result = new RichText(plainText);
|
|
311
|
-
if (baseStyle)
|
|
312
|
-
result.stylize(baseStyle);
|
|
313
|
-
// Apply link parameters as link styles
|
|
314
|
-
for (const span of spans) {
|
|
315
|
-
const style = typeof span.style === "string" ? span.style : span.style.toString();
|
|
316
|
-
// Check if this is a link tag (name=url pattern was parsed)
|
|
317
|
-
const linkMatch = /^link\s+(.+)$/.exec(style);
|
|
318
|
-
if (linkMatch) {
|
|
319
|
-
result.stylize(new Style({ link: linkMatch[1] }), span.start, span.end);
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
result.stylize(style, span.start, span.end);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return result;
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* The style string an open tag closes into — `name`, or `name parameters` when
|
|
329
|
-
* the tag carried an `=`.
|
|
330
|
-
*
|
|
331
|
-
* [LAW:one-source-of-truth] One answer for the four sites that ask it: the three
|
|
332
|
-
* ways a tag can close (`[/name]`, `[/]`, and end of input) and the search that
|
|
333
|
-
* matches `[/red on blue]` back to `[red on blue]`. It was written out at each,
|
|
334
|
-
* which is how the three closing paths could — and did — drift apart on
|
|
335
|
-
* something none of the four copies was about.
|
|
336
|
-
*/
|
|
337
|
-
function openTagStyle(opened) {
|
|
338
|
-
return opened.parameters !== undefined
|
|
339
|
-
? `${opened.styleName} ${opened.parameters}`
|
|
340
|
-
: opened.styleName;
|
|
341
|
-
}
|
|
342
|
-
function findLastOpen(stack, name) {
|
|
343
|
-
// Handle "on" in style names for closing: [/red on blue] should match [red on blue]
|
|
344
|
-
const normalized = name.trim();
|
|
345
|
-
for (let i = stack.length - 1; i >= 0; i--) {
|
|
346
|
-
const entry = stack[i];
|
|
347
|
-
if (openTagStyle(entry) === normalized || entry.styleName === normalized)
|
|
348
|
-
return i;
|
|
349
|
-
}
|
|
350
|
-
return -1;
|
|
351
|
-
}
|
|
352
|
-
function unescapeBrackets(text) {
|
|
353
|
-
return text.replace(/\\\[/g, "[");
|
|
354
|
-
}
|
|
355
|
-
// [LAW:one-source-of-truth] The one answer to "what is a legal plugin tag
|
|
356
|
-
// name?". Both sites that need it are derived from this fragment rather than
|
|
357
|
-
// each carrying its own scanner: `register` anchors it to reject a name markup
|
|
358
|
-
// could never address, and the match site anchors it at the head with a
|
|
359
|
-
// boundary lookahead. When those two disagreed, registering `table` silently
|
|
360
|
-
// destroyed every `table.*` built-in style and a name containing a dot could
|
|
361
|
-
// never fire.
|
|
362
|
-
//
|
|
363
|
-
// It opens on `a-z` because a plugin name has to be a name `TAG_RE` can reach,
|
|
364
|
-
// and that is strictly narrower than "a letter": nothing lowercases the tag
|
|
365
|
-
// text on the way in, so `[Foo]` is literal and a handler registered as `Foo`
|
|
366
|
-
// could never fire. This tracked `[A-Za-z]` while `TAG_RE` wrongly admitted
|
|
367
|
-
// uppercase, which is the same drift, one subset down (rich-markup-sec).
|
|
368
|
-
const PLUGIN_TAG_NAME_SRC = "[a-z][A-Za-z0-9_-]*";
|
|
369
|
-
const LEGAL_PLUGIN_TAG_NAME = new RegExp(`^${PLUGIN_TAG_NAME_SRC}$`);
|
|
370
|
-
export class MarkupRegistry {
|
|
371
|
-
_handlers = new Map();
|
|
372
|
-
register(name, handler) {
|
|
373
|
-
// [LAW:no-silent-failure] Registration used to accept any string, including
|
|
374
|
-
// names markup can never address (`a.b` stops the tag scan at the dot), and
|
|
375
|
-
// install a handler that could never fire.
|
|
376
|
-
if (!LEGAL_PLUGIN_TAG_NAME.test(name)) {
|
|
377
|
-
throw new MarkupError(`Cannot register markup tag "${name}": a tag name must be a lowercase letter followed by letters, digits, "_" or "-", so markup could never address this name.`);
|
|
378
|
-
}
|
|
379
|
-
if (isReservedTagName(name)) {
|
|
380
|
-
throw new MarkupError(`Cannot register markup tag "${name}": name is reserved by a built-in style.`);
|
|
381
|
-
}
|
|
382
|
-
this._handlers.set(name, handler);
|
|
383
|
-
}
|
|
384
|
-
unregister(name) {
|
|
385
|
-
this._handlers.delete(name);
|
|
386
|
-
}
|
|
387
|
-
has(name) {
|
|
388
|
-
return this._handlers.has(name);
|
|
389
|
-
}
|
|
390
|
-
get(name) {
|
|
391
|
-
return this._handlers.get(name);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
// [LAW:one-source-of-truth] Reserved-name detection delegates to Style.parse —
|
|
395
|
-
// if a name is a valid built-in style, it cannot be hijacked by a plugin.
|
|
396
|
-
// There is no second list of "reserved names" to drift out of sync.
|
|
397
|
-
function isReservedTagName(name) {
|
|
398
|
-
try {
|
|
399
|
-
Style.parse(name);
|
|
400
|
-
return true;
|
|
401
|
-
}
|
|
402
|
-
catch (err) {
|
|
403
|
-
if (err instanceof StyleSyntaxError)
|
|
404
|
-
return false;
|
|
405
|
-
throw err;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
// [LAW:no-shared-mutable-globals] The process-wide default tag set, which
|
|
409
|
-
// `renderMarkup` falls back to when a caller passes no registry. It has one
|
|
410
|
-
// owner and one explicit API — `MarkupRegistry`'s own methods. There is
|
|
411
|
-
// deliberately no free-function façade over it: `registerMarkupTag(name, fn)`
|
|
412
|
-
// forwarding to `.register(name, fn)` would be a second way to say one thing,
|
|
413
|
-
// and the two would document the same invariants in two places.
|
|
414
|
-
export const globalMarkupRegistry = new MarkupRegistry();
|
|
415
|
-
// --- Plugin-aware tag parsing ---
|
|
416
|
-
function isAlpha(c) {
|
|
417
|
-
const cc = c.charCodeAt(0);
|
|
418
|
-
return (cc >= 65 && cc <= 90) || (cc >= 97 && cc <= 122);
|
|
419
|
-
}
|
|
420
|
-
function isIdentChar(c) {
|
|
421
|
-
if (isAlpha(c))
|
|
422
|
-
return true;
|
|
423
|
-
const cc = c.charCodeAt(0);
|
|
424
|
-
return (cc >= 48 && cc <= 57) || c === "_" || c === "-";
|
|
425
|
-
}
|
|
426
|
-
function isSpace(c) {
|
|
427
|
-
return c === " " || c === "\t";
|
|
428
|
-
}
|
|
429
|
-
// A registered name addresses a plugin only when the tag text ends there or
|
|
430
|
-
// continues with the whitespace that introduces attributes. Anything else —
|
|
431
|
-
// `[table.header]`, `[table=x]` — is the built-in dialect's, and falls through
|
|
432
|
-
// exactly as an unregistered name would.
|
|
433
|
-
const PLUGIN_TAG_HEAD = new RegExp(`^(${PLUGIN_TAG_NAME_SRC})(?=$|[ \\t])`);
|
|
434
|
-
function parsePluginAttrs(after) {
|
|
435
|
-
// Hand-written tokenizer. Walks `key=value` pairs separated by whitespace;
|
|
436
|
-
// values may be bare, single-quoted, or double-quoted. Matches the syntax
|
|
437
|
-
// the regex it replaces accepted.
|
|
438
|
-
const attrs = {};
|
|
439
|
-
let i = 0;
|
|
440
|
-
while (i < after.length) {
|
|
441
|
-
while (i < after.length && isSpace(after[i]))
|
|
442
|
-
i++;
|
|
443
|
-
if (i >= after.length)
|
|
444
|
-
break;
|
|
445
|
-
if (!isAlpha(after[i])) {
|
|
446
|
-
// Skip a stray non-name character to avoid infinite loops on malformed
|
|
447
|
-
// input — the legacy regex would simply fail to match here.
|
|
448
|
-
i++;
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
const nameStart = i;
|
|
452
|
-
while (i < after.length && isIdentChar(after[i]))
|
|
453
|
-
i++;
|
|
454
|
-
const name = after.slice(nameStart, i);
|
|
455
|
-
while (i < after.length && isSpace(after[i]))
|
|
456
|
-
i++;
|
|
457
|
-
if (after[i] !== "=")
|
|
458
|
-
continue;
|
|
459
|
-
i++;
|
|
460
|
-
while (i < after.length && isSpace(after[i]))
|
|
461
|
-
i++;
|
|
462
|
-
let value = "";
|
|
463
|
-
const quote = after[i];
|
|
464
|
-
if (quote === '"' || quote === "'") {
|
|
465
|
-
i++;
|
|
466
|
-
const valStart = i;
|
|
467
|
-
while (i < after.length && after[i] !== quote)
|
|
468
|
-
i++;
|
|
469
|
-
value = after.slice(valStart, i);
|
|
470
|
-
if (i < after.length)
|
|
471
|
-
i++;
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
const valStart = i;
|
|
475
|
-
while (i < after.length && !isSpace(after[i]) && after[i] !== "]")
|
|
476
|
-
i++;
|
|
477
|
-
value = after.slice(valStart, i);
|
|
478
|
-
}
|
|
479
|
-
attrs[name] = value;
|
|
480
|
-
}
|
|
481
|
-
return attrs;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Plugin-aware markup render. Always returns a `RichText`. Built-in style
|
|
485
|
-
* tags become spans; registered plugin tags are resolved by their handler,
|
|
486
|
-
* whose returned `RichText` is appended (text + spans) into the output. The
|
|
487
|
-
* recursion is over markup *slices* — between top-level plugin tag pairs and
|
|
488
|
-
* inside each pair — and every recursion node returns a `RichText`, so the
|
|
489
|
-
* append path is uniform.
|
|
490
|
-
*/
|
|
491
|
-
export function renderMarkup(markup, options) {
|
|
492
|
-
return renderSlice(markup, wholeString(markup), options);
|
|
493
|
-
}
|
|
494
|
-
function renderSlice(markup, origin, options) {
|
|
495
|
-
const registry = options?.registry ?? globalMarkupRegistry;
|
|
496
|
-
const baseStyle = options?.baseStyle;
|
|
497
|
-
const doEmoji = options?.emoji !== false;
|
|
498
|
-
// Fast path: no `[` at all → no possible tags.
|
|
499
|
-
if (!HAS_TAG_RE.test(markup)) {
|
|
500
|
-
return render(markup, origin, baseStyle, { emoji: doEmoji });
|
|
501
|
-
}
|
|
502
|
-
const tags = parseTags(markup);
|
|
503
|
-
// Pair each opening plugin tag with its matching closer up-front, so the
|
|
504
|
-
// splice walk can just iterate top-level pairs in source order with no
|
|
505
|
-
// nested-state book-keeping.
|
|
506
|
-
const { annotated, topLevel: tagPairs } = pairPluginTags(tags, registry, origin);
|
|
507
|
-
if (tagPairs.size === 0) {
|
|
508
|
-
return render(markup, origin, baseStyle, { emoji: doEmoji });
|
|
509
|
-
}
|
|
510
|
-
// [LAW:one-type-per-behavior] Always assemble a single RichText. Fragments
|
|
511
|
-
// between plugin pairs are parsed by the built-in `render`, plugin pairs
|
|
512
|
-
// hand their inner slice (recursively) to a handler; both produce RichText,
|
|
513
|
-
// both get appended into one accumulator.
|
|
514
|
-
const out = new RichText("");
|
|
515
|
-
let cursor = 0;
|
|
516
|
-
for (const [openIdx, closeIdx] of tagPairs) {
|
|
517
|
-
const open = annotated[openIdx];
|
|
518
|
-
const close = annotated[closeIdx];
|
|
519
|
-
if (open.start > cursor) {
|
|
520
|
-
out.append(render(markup.slice(cursor, open.start), offsetBy(origin, cursor), baseStyle, { emoji: doEmoji }));
|
|
521
|
-
}
|
|
522
|
-
const innerRaw = markup.slice(open.end, close.start);
|
|
523
|
-
const innerRichText = renderSlice(innerRaw, { ...offsetBy(origin, open.end), enclosing: [...origin.enclosing, open.fullMatch] }, options);
|
|
524
|
-
const handler = registry.get(open.pluginName);
|
|
525
|
-
out.append(handler({ attrs: open.attrs, children: innerRichText, raw: innerRaw }));
|
|
526
|
-
cursor = close.end;
|
|
527
|
-
}
|
|
528
|
-
if (cursor < markup.length) {
|
|
529
|
-
out.append(render(markup.slice(cursor), offsetBy(origin, cursor), baseStyle, { emoji: doEmoji }));
|
|
530
|
-
}
|
|
531
|
-
if (baseStyle)
|
|
532
|
-
out.stylize(baseStyle);
|
|
533
|
-
return out;
|
|
534
|
-
}
|
|
535
|
-
/** The origin of a sub-slice starting `offset` into the slice `origin` describes. */
|
|
536
|
-
function offsetBy(origin, offset) {
|
|
537
|
-
return { ...origin, base: origin.base + offset };
|
|
538
|
-
}
|
|
539
|
-
function pairPluginTags(tags, registry, origin) {
|
|
540
|
-
// Annotate tags with plugin info, then pair openers with closers. Only
|
|
541
|
-
// top-level pairs are returned; inner pairs will be re-discovered by the
|
|
542
|
-
// recursive `renderMarkup` call on the inner slice.
|
|
543
|
-
const annotated = tags.map((t) => annotatePluginTag(t, registry));
|
|
544
|
-
const stack = [];
|
|
545
|
-
const pairs = new Map();
|
|
546
|
-
for (let i = 0; i < annotated.length; i++) {
|
|
547
|
-
const t = annotated[i];
|
|
548
|
-
if (!t.pluginName)
|
|
549
|
-
continue;
|
|
550
|
-
if (t.isImplicitClose)
|
|
551
|
-
continue;
|
|
552
|
-
if (t.isClosing) {
|
|
553
|
-
// Find matching open in stack.
|
|
554
|
-
for (let j = stack.length - 1; j >= 0; j--) {
|
|
555
|
-
const openIdx = stack[j];
|
|
556
|
-
if (annotated[openIdx].pluginName === t.pluginName) {
|
|
557
|
-
pairs.set(openIdx, i);
|
|
558
|
-
stack.splice(j, 1);
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
stack.push(i);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// Filter pairs to top-level only. A pair that opens inside the current one is
|
|
568
|
-
// either contained — re-discovered when the recursion renders the outer
|
|
569
|
-
// pair's inner slice — or overlapping, and only its *closing* position tells
|
|
570
|
-
// the two apart. Testing the open position alone conflated them and dropped
|
|
571
|
-
// the overlapping pair, which orphaned its closing tag into the trailing
|
|
572
|
-
// slice, where the built-in parser blamed the wrong tag for the wrong reason.
|
|
573
|
-
const topLevel = new Map();
|
|
574
|
-
let outerEnd = -1;
|
|
575
|
-
let outerOpenIdx = -1;
|
|
576
|
-
const sortedOpens = [...pairs.keys()].sort((a, b) => a - b);
|
|
577
|
-
for (const openIdx of sortedOpens) {
|
|
578
|
-
const closeIdx = pairs.get(openIdx);
|
|
579
|
-
if (annotated[openIdx].start < outerEnd) {
|
|
580
|
-
// [LAW:no-silent-failure] Overlap is unrepresentable here rather than
|
|
581
|
-
// unimplemented: a handler receives `children` as one contiguous slice,
|
|
582
|
-
// so a region straddling another pair's closing boundary has nothing to
|
|
583
|
-
// hand it. A style span may overlap because it annotates; a plugin pair
|
|
584
|
-
// may not because it replaces.
|
|
585
|
-
if (annotated[closeIdx].end > outerEnd) {
|
|
586
|
-
const inner = annotated[openIdx];
|
|
587
|
-
const outer = annotated[outerOpenIdx];
|
|
588
|
-
// The caret goes under the outer pair's closing tag, the point where
|
|
589
|
-
// nesting breaks. This pass sees plugin tags only; of those, a pair is
|
|
590
|
-
// open there when it opens before that tag and closes at or after it.
|
|
591
|
-
const caret = annotated[pairs.get(outerOpenIdx)].start;
|
|
592
|
-
const openAtCaret = sortedOpens
|
|
593
|
-
.filter((o) => annotated[o].start < caret && annotated[pairs.get(o)].start >= caret)
|
|
594
|
-
.map((o) => annotated[o].fullMatch);
|
|
595
|
-
throw new MarkupSyntaxError(`Plugin tag [${inner.pluginName}] overlaps [${outer.pluginName}]: plugin tags must nest, ` +
|
|
596
|
-
`because a handler receives one contiguous slice. ` +
|
|
597
|
-
`Close [/${inner.pluginName}] before [/${outer.pluginName}].`, origin.source, origin.base + caret, [...origin.enclosing, ...openAtCaret]);
|
|
598
|
-
}
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
topLevel.set(openIdx, closeIdx);
|
|
602
|
-
outerOpenIdx = openIdx;
|
|
603
|
-
outerEnd = annotated[closeIdx].end;
|
|
604
|
-
}
|
|
605
|
-
return { annotated, topLevel };
|
|
606
|
-
}
|
|
607
|
-
function annotatePluginTag(tag, registry) {
|
|
608
|
-
// The legacy parser splits at the first `=`, so tag.styleName for
|
|
609
|
-
// `[click verb=foo]` is "click verb". Re-read the tag text for a name that
|
|
610
|
-
// actually addresses a plugin.
|
|
611
|
-
const inner = tag.fullMatch.slice(1, -1).replace(/^\//, "");
|
|
612
|
-
const head = PLUGIN_TAG_HEAD.exec(inner);
|
|
613
|
-
const name = head?.[1];
|
|
614
|
-
if (name === undefined || !registry.has(name))
|
|
615
|
-
return tag;
|
|
616
|
-
const after = inner.slice(name.length);
|
|
617
|
-
const attrs = tag.isClosing ? {} : parsePluginAttrs(after);
|
|
618
|
-
return { ...tag, pluginName: name, attrs };
|
|
619
|
-
}
|
package/dist/core/measure.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Measurement — min/max cell width calculation for renderables.
|
|
3
|
-
*/
|
|
4
|
-
import type { Measurable, RenderOptions } from "./protocol.js";
|
|
5
|
-
export declare class Measurement {
|
|
6
|
-
readonly minimum: number;
|
|
7
|
-
readonly maximum: number;
|
|
8
|
-
constructor(minimum: number, maximum: number);
|
|
9
|
-
get span(): number;
|
|
10
|
-
/**
|
|
11
|
-
* A range with no cell meaning becomes one with the nearest meaning there is:
|
|
12
|
-
* negative floors to zero, an inverted pair collapses to its ceiling, and NaN
|
|
13
|
-
* reads as zero cells the same way `cellCount` reads it.
|
|
14
|
-
*
|
|
15
|
-
* NaN and Infinity are not treated alike here, and the difference is the whole
|
|
16
|
-
* point. `Infinity` is a maximum a renderable means — `Table` reports it when
|
|
17
|
-
* a column asks for every cell there is, and `withBoundedWidth` throws on it
|
|
18
|
-
* so a caller learns their offer was unanswerable. Flooring it to zero would
|
|
19
|
-
* turn that loud failure into a table measured at no width at all.
|
|
20
|
-
*/
|
|
21
|
-
normalize(): Measurement;
|
|
22
|
-
withMaximum(width: number): Measurement;
|
|
23
|
-
withMinimum(width: number): Measurement;
|
|
24
|
-
clamp(minWidth: number, maxWidth: number): Measurement;
|
|
25
|
-
static get(options: RenderOptions, measurable: Measurable): Measurement;
|
|
26
|
-
}
|
|
27
|
-
export declare function measureRenderables(options: RenderOptions, measurables: Measurable[]): Measurement;
|