@promptctl/rich-js 0.27.0 → 0.28.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 +1 -0
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +36 -17
- package/dist/core/animation.js +61 -37
- package/dist/core/ansi.d.ts +15 -8
- package/dist/core/ansi.js +106 -28
- package/dist/core/bidi-data.d.ts +14 -0
- package/dist/core/bidi-data.js +385 -0
- package/dist/core/bidi.d.ts +89 -0
- package/dist/core/bidi.js +615 -0
- package/dist/core/box.d.ts +9 -1
- package/dist/core/box.js +11 -0
- package/dist/core/cell-widths.d.ts +3 -2
- package/dist/core/cell-widths.js +93 -93
- package/dist/core/cells.d.ts +20 -0
- package/dist/core/cells.js +93 -5
- package/dist/core/color.d.ts +6 -7
- package/dist/core/color.js +11 -13
- package/dist/core/console.d.ts +18 -1
- package/dist/core/console.js +24 -5
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +4 -3
- package/dist/core/export-lines.d.ts +2 -0
- package/dist/core/export-lines.js +7 -3
- package/dist/core/export-svg.js +6 -5
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +4 -3
- package/dist/core/orient.d.ts +68 -0
- package/dist/core/orient.js +224 -0
- package/dist/core/osc8.d.ts +12 -5
- package/dist/core/osc8.js +14 -7
- package/dist/core/pretty.js +22 -18
- package/dist/core/protocol.d.ts +27 -6
- package/dist/core/protocol.js +14 -6
- package/dist/core/render.d.ts +8 -3
- package/dist/core/render.js +18 -5
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +9 -1
- package/dist/core/segment.js +11 -1
- package/dist/core/style.d.ts +29 -1
- package/dist/core/style.js +41 -5
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +10 -0
- package/dist/core/text.js +133 -87
- package/dist/core/timeline.js +6 -6
- package/dist/core/view.d.ts +1 -1
- package/dist/core/view.js +5 -4
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +44 -3
- package/dist/host/app.js +111 -6
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +35 -2
- package/dist/host/input.js +88 -4
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +2 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +4 -6
- package/dist/renderables/effect.d.ts +60 -59
- package/dist/renderables/effect.js +91 -81
- package/dist/renderables/effects.d.ts +37 -8
- package/dist/renderables/effects.js +185 -52
- package/dist/renderables/frames.d.ts +1 -10
- package/dist/renderables/frames.js +1 -16
- package/dist/renderables/glyph-effect.d.ts +12 -13
- package/dist/renderables/glyph-effect.js +34 -32
- package/dist/renderables/glyph-effects.d.ts +27 -8
- package/dist/renderables/glyph-effects.js +75 -7
- package/dist/renderables/lifecycle.d.ts +5 -7
- package/dist/renderables/lifecycle.js +6 -7
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +6 -4
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +16 -13
- package/dist/renderables/syntax.js +7 -2
- package/dist/renderables/table.js +2 -1
- package/dist/template-bindings/animation-funcs.d.ts +49 -18
- package/dist/template-bindings/animation-funcs.js +115 -32
- package/dist/template-bindings/index.d.ts +7 -5
- package/dist/template-bindings/index.js +8 -5
- package/dist/template-bindings/palette-funcs.d.ts +1 -1
- package/dist/template-bindings/palette-funcs.js +1 -1
- package/dist/themes/buildPalette.d.ts +3 -2
- package/dist/themes/buildPalette.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/ramp.d.ts +1 -1
- package/dist/themes/ramp.js +1 -1
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +2 -4
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +1 -1
- package/dist/widgets/button.js +6 -6
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +1 -1
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/themed-widget.js +3 -3
- package/dist/widgets/toggle.d.ts +1 -1
- package/dist/widgets/toggle.js +5 -5
- package/package.json +5 -4
package/dist/core/console.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Segment } from "./segment.js";
|
|
5
5
|
import { NULL_STYLE, DEFAULT_THEME } from "./style.js";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveDestination } from "./color.js";
|
|
7
|
+
import { isDumbTerminal } from "./env.js";
|
|
7
8
|
import { encodeHtml } from "./export-html.js";
|
|
8
9
|
import { encodeSvg } from "./export-svg.js";
|
|
9
10
|
import { RichText } from "./text.js";
|
|
@@ -17,6 +18,7 @@ import { ReprHighlighter, NullHighlighter } from "./highlighter.js";
|
|
|
17
18
|
// rather than being restated here; see `rule()`.
|
|
18
19
|
import { Rule } from "../renderables/rule.js";
|
|
19
20
|
import { segmentsToString, shownOn } from "./render.js";
|
|
21
|
+
import { sealed } from "./seal.js";
|
|
20
22
|
import { placeBlock } from "./place.js";
|
|
21
23
|
import { activePass, isRenderable, measure, render } from "./protocol.js";
|
|
22
24
|
import { ListState, State, refuseRenderWrite } from "./state.js";
|
|
@@ -181,6 +183,7 @@ export class Console {
|
|
|
181
183
|
_lastLogTime = new State(null);
|
|
182
184
|
_style;
|
|
183
185
|
_isTerminal;
|
|
186
|
+
_attended;
|
|
184
187
|
_interactive;
|
|
185
188
|
_file;
|
|
186
189
|
// [LAW:one-source-of-truth] The resolved output stream, not the `stderr`
|
|
@@ -191,6 +194,7 @@ export class Console {
|
|
|
191
194
|
_markup;
|
|
192
195
|
_highlight;
|
|
193
196
|
_theme;
|
|
197
|
+
_terminal;
|
|
194
198
|
_onStyleError;
|
|
195
199
|
_asciiOnly;
|
|
196
200
|
_highlighter;
|
|
@@ -211,7 +215,7 @@ export class Console {
|
|
|
211
215
|
// [LAW:dataflow-not-control-flow] `isTTY` and `env` are forwarded
|
|
212
216
|
// unconditionally, so detection stays inside the console's injected
|
|
213
217
|
// environment rather than the ambient process's.
|
|
214
|
-
const resolved = resolveDestination(options?.colorSystem === undefined ? "auto" : options.colorSystem, { isTTY: this._isTerminal, env: environment.env, reducedMotion: options?.reducedMotion });
|
|
218
|
+
const resolved = resolveDestination(options?.colorSystem === undefined ? "auto" : options.colorSystem, { isTTY: this._isTerminal, env: environment.env, reducedMotion: options?.reducedMotion, ...(options?.textOrder === undefined ? {} : { textOrder: options.textOrder }) });
|
|
215
219
|
// [LAW:single-enforcer] The console's one override, an explicit
|
|
216
220
|
// `hyperlinks`, is applied here and nowhere else; every write encodes for
|
|
217
221
|
// this value whole. [LAW:one-source-of-truth] The reader's motion
|
|
@@ -223,9 +227,12 @@ export class Console {
|
|
|
223
227
|
// [LAW:no-ambient-temporal-coupling] The theme is assigned before the
|
|
224
228
|
// style, because the console's own style may be one of the theme's names.
|
|
225
229
|
this._theme = options?.theme ?? DEFAULT_THEME;
|
|
230
|
+
this._terminal = options?.terminal;
|
|
226
231
|
this._style = this._theme.resolve(options?.style ?? NULL_STYLE);
|
|
227
|
-
// Rich's rule: a terminal is interactive unless it is a dumb one.
|
|
228
|
-
|
|
232
|
+
// Rich's rule: a terminal is interactive unless it is a dumb one. Forcing that
|
|
233
|
+
// is animation policy, and says nothing of who is at the terminal.
|
|
234
|
+
this._attended = this._isTerminal && !isDumbTerminal(environment.env);
|
|
235
|
+
this._interactive = options?.forceInteractive ?? this._attended;
|
|
229
236
|
this._file = options?.file;
|
|
230
237
|
this._record = options?.record ?? false;
|
|
231
238
|
this._markup = options?.markup !== false;
|
|
@@ -290,6 +297,8 @@ export class Console {
|
|
|
290
297
|
theme: this._theme,
|
|
291
298
|
onStyleError: this._onStyleError,
|
|
292
299
|
colorSystem: this._destination.colorSystem,
|
|
300
|
+
textOrder: this._destination.textOrder,
|
|
301
|
+
terminal: this._terminal,
|
|
293
302
|
markup: this._markup,
|
|
294
303
|
highlight: this._highlight,
|
|
295
304
|
highlighter: this._highlighter,
|
|
@@ -495,6 +504,16 @@ export class Console {
|
|
|
495
504
|
// stamp, so the next row's must show.
|
|
496
505
|
this._lastLogTime.set(stamped);
|
|
497
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Sounds the terminal's bell, Rich's `Console.bell`, for whoever is at it: a
|
|
509
|
+
* pipe, a dumb terminal and a capture get nothing. It goes straight to the sink
|
|
510
|
+
* because a bell moves no cursor, and a live region would repaint its frame.
|
|
511
|
+
*/
|
|
512
|
+
bell() {
|
|
513
|
+
refuseRenderWrite(this._madeIn);
|
|
514
|
+
if (this._attended && this._capture.get() === null)
|
|
515
|
+
this.file.write("\x07");
|
|
516
|
+
}
|
|
498
517
|
// [LAW:one-source-of-truth] `RuleOptions` is `Rule`'s, not a restatement of
|
|
499
518
|
// it. `console.ts` used to declare its own copy and field-by-field forward
|
|
500
519
|
// into this constructor, which made a second map of one shape — identical
|
|
@@ -547,7 +566,7 @@ export class Console {
|
|
|
547
566
|
}
|
|
548
567
|
// --- Export (when record:true) ---
|
|
549
568
|
exportText({ clear = true } = {}) {
|
|
550
|
-
const text = this._recorded.get().map((s) => s.text).join("");
|
|
569
|
+
const text = [...sealed(this._recorded.get(), "plain")].map((s) => s.text).join("");
|
|
551
570
|
if (clear)
|
|
552
571
|
this._recorded.set([]);
|
|
553
572
|
return text;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The census of code points that do not print: every one in Unicode's general
|
|
3
|
+
* categories Cc, Cf, Zl and Zp, and the variation selectors, with what `RichText`
|
|
4
|
+
* does when content carries it. [LAW:one-source-of-truth] The sanitizer
|
|
5
|
+
* (`sanitized`), the wire (`wireText`) and the cell-width measure
|
|
6
|
+
* (`cellLen`) all read this table, so whether content keeps a code point has one
|
|
7
|
+
* statement. `docs/control-codes.md` is each family's use and harm, and what real
|
|
8
|
+
* terminals were measured doing with it.
|
|
9
|
+
*
|
|
10
|
+
* A row does not carry a width. A kept code point takes the cells Rich's table
|
|
11
|
+
* gives it (`cell-widths.ts`), and a second statement of them here would be one
|
|
12
|
+
* that could drift.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* What `RichText` does with a code point. `strip` deletes it from content. `keep`
|
|
16
|
+
* holds it as a character. `line-break` is a line ending, so content holds an LF
|
|
17
|
+
* in its place. `pictured` means nothing inside a span, so content holds the
|
|
18
|
+
* stand-in `pictureOf` gives in its place and the reader sees something was there.
|
|
19
|
+
* `withheld` is held in content and measured as nothing, and `wireText` decides
|
|
20
|
+
* from its neighbours whether it is written.
|
|
21
|
+
*
|
|
22
|
+
* Only `keep`, and a `withheld` code point `wireText` lets by, reaches the wire. A
|
|
23
|
+
* raw `Segment` is not content, so what it holds of the others is deleted there.
|
|
24
|
+
*/
|
|
25
|
+
export type Disposition = "strip" | "keep" | "line-break" | "pictured" | "withheld";
|
|
26
|
+
/**
|
|
27
|
+
* The code points `first` to `last`, inclusive, and what is done with each. A
|
|
28
|
+
* `pictured` row carries what content shows in place of each of its code points.
|
|
29
|
+
*/
|
|
30
|
+
export type ControlCodeRow = {
|
|
31
|
+
readonly first: number;
|
|
32
|
+
readonly last: number;
|
|
33
|
+
readonly disposition: Exclude<Disposition, "pictured">;
|
|
34
|
+
} | {
|
|
35
|
+
readonly first: number;
|
|
36
|
+
readonly last: number;
|
|
37
|
+
readonly disposition: "pictured";
|
|
38
|
+
readonly picture: (codePoint: number) => string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Ascending and disjoint, every Cc, Cf, Zl, Zp and Variation_Selector code point
|
|
42
|
+
* in exactly one row (`test/core/control-codes.test.ts`).
|
|
43
|
+
*
|
|
44
|
+
* Rich's `Text` strips only `STRIP_CONTROL_CODES` (BEL, BS, VT, FF, CR) and writes
|
|
45
|
+
* every other code point to the wire. Here a directive reaches the wire only
|
|
46
|
+
* through the library's own constructs: `Style`, a link, `Console.bell`, the paint
|
|
47
|
+
* layer.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CONTROL_CODES: readonly ControlCodeRow[];
|
|
50
|
+
/** What content shows in place of `codePoint`, whose disposition is `pictured`. */
|
|
51
|
+
export declare function pictureOf(codePoint: number): string;
|
|
52
|
+
/** A regular expression class of the code points in rows `chosen` picks, for `u`. */
|
|
53
|
+
export declare function controlClass(chosen: (disposition: Disposition) => boolean): string;
|
|
54
|
+
/**
|
|
55
|
+
* UTS #51's subdivision flag, as a `u` source: the black flag, a CLDR subdivision
|
|
56
|
+
* spelled in tag letters and digits, and CANCEL TAG. The one sequence tag
|
|
57
|
+
* characters have a reader for.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SUBDIVISION_FLAG = "\uD83C\uDFF4[\uDB40\uDC30-\uDB40\uDC39\uDB40\uDC61-\uDB40\uDC7A]{3,6}\uDB40\uDC7F";
|
|
60
|
+
/** What happens to `codePoint`. One in no row is kept. */
|
|
61
|
+
export declare function dispositionOf(codePoint: number): Disposition;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The census of code points that do not print: every one in Unicode's general
|
|
3
|
+
* categories Cc, Cf, Zl and Zp, and the variation selectors, with what `RichText`
|
|
4
|
+
* does when content carries it. [LAW:one-source-of-truth] The sanitizer
|
|
5
|
+
* (`sanitized`), the wire (`wireText`) and the cell-width measure
|
|
6
|
+
* (`cellLen`) all read this table, so whether content keeps a code point has one
|
|
7
|
+
* statement. `docs/control-codes.md` is each family's use and harm, and what real
|
|
8
|
+
* terminals were measured doing with it.
|
|
9
|
+
*
|
|
10
|
+
* A row does not carry a width. A kept code point takes the cells Rich's table
|
|
11
|
+
* gives it (`cell-widths.ts`), and a second statement of them here would be one
|
|
12
|
+
* that could drift.
|
|
13
|
+
*/
|
|
14
|
+
const strip = (first, last) => ({ first, last, disposition: "strip" });
|
|
15
|
+
const keep = (first, last) => ({ first, last, disposition: "keep" });
|
|
16
|
+
const lineBreak = (first, last) => ({ first, last, disposition: "line-break" });
|
|
17
|
+
const pictured = (first, last, picture) => ({ first, last, disposition: "pictured", picture });
|
|
18
|
+
/**
|
|
19
|
+
* The stand-ins. A C0 control's Control Pictures symbol is U+2400 up, DEL's is
|
|
20
|
+
* U+2421. Unicode gives C1 no pictures, so each shows U+2426 SYMBOL FOR SUBSTITUTE:
|
|
21
|
+
* one cell under every East Asian width setting, which U+FFFD is not, and apart
|
|
22
|
+
* from a real decoding error. Each is one code unit, so offsets hold.
|
|
23
|
+
*/
|
|
24
|
+
const controlPicture = (codePoint) => String.fromCharCode(0x2400 + codePoint);
|
|
25
|
+
const deletePicture = () => "\u2421";
|
|
26
|
+
const substitute = () => "\u2426";
|
|
27
|
+
const withheld = (first, last) => ({ first, last, disposition: "withheld" });
|
|
28
|
+
/**
|
|
29
|
+
* Ascending and disjoint, every Cc, Cf, Zl, Zp and Variation_Selector code point
|
|
30
|
+
* in exactly one row (`test/core/control-codes.test.ts`).
|
|
31
|
+
*
|
|
32
|
+
* Rich's `Text` strips only `STRIP_CONTROL_CODES` (BEL, BS, VT, FF, CR) and writes
|
|
33
|
+
* every other code point to the wire. Here a directive reaches the wire only
|
|
34
|
+
* through the library's own constructs: `Style`, a link, `Console.bell`, the paint
|
|
35
|
+
* layer.
|
|
36
|
+
*/
|
|
37
|
+
export const CONTROL_CODES = [
|
|
38
|
+
// NUL to BS. BEL's alert is `Console.bell`, and BS's overstrike, bold and
|
|
39
|
+
// underline in nroff output, is `decodeAnsi`: taken a code point at a time, text
|
|
40
|
+
// can turn neither into what it means.
|
|
41
|
+
pictured(0x00, 0x08, controlPicture),
|
|
42
|
+
keep(0x09, 0x0a),
|
|
43
|
+
// VT and FF, mandatory breaks in UAX #14 as LS and PS are.
|
|
44
|
+
lineBreak(0x0b, 0x0c),
|
|
45
|
+
// CR. A CRLF's LF ends its line however the two were chunked, and a CR's
|
|
46
|
+
// overwrite is `decodeAnsi`'s. What is given up is a classic Mac file's lines.
|
|
47
|
+
strip(0x0d, 0x0d),
|
|
48
|
+
// SO and SI switch the terminal's character set; DLE to SUB are transmission
|
|
49
|
+
// controls, and CAN and SUB abort a sequence; ESC begins one, and content that
|
|
50
|
+
// means styling says so with `decodeAnsi`; FS to US are data separators.
|
|
51
|
+
pictured(0x0e, 0x1f, controlPicture),
|
|
52
|
+
// DEL.
|
|
53
|
+
pictured(0x7f, 0x7f, deletePicture),
|
|
54
|
+
// The C1 controls. VTE, WezTerm and xterm.js read U+009B as CSI and U+009D as
|
|
55
|
+
// OSC; xterm, kitty and others ignore them. None has a meaning in text but NEL,
|
|
56
|
+
// which Unicode treats as CR, LF and CRLF are treated (§5.8). A stray one is
|
|
57
|
+
// often windows-1252 decoded as Latin-1, but which, a code point alone cannot
|
|
58
|
+
// say, so mojibake is the caller's to repair. `TextInput` refuses them as keys.
|
|
59
|
+
pictured(0x80, 0x84, substitute),
|
|
60
|
+
lineBreak(0x85, 0x85),
|
|
61
|
+
pictured(0x86, 0x9f, substitute),
|
|
62
|
+
// SOFT HYPHEN: a place a word may break, drawn as a hyphen only where one does.
|
|
63
|
+
// Terminals disagree on its cell, so it never reaches them.
|
|
64
|
+
withheld(0xad, 0xad),
|
|
65
|
+
// The Arabic and Syriac number signs and other prepended concatenation marks,
|
|
66
|
+
// which draw a glyph.
|
|
67
|
+
keep(0x600, 0x605),
|
|
68
|
+
// ARABIC LETTER MARK, a bidi mark like LRM and RLM below: it holds no state,
|
|
69
|
+
// so right-to-left text can use it.
|
|
70
|
+
keep(0x61c, 0x61c),
|
|
71
|
+
// More prepended concatenation marks.
|
|
72
|
+
keep(0x6dd, 0x6dd),
|
|
73
|
+
keep(0x70f, 0x70f),
|
|
74
|
+
keep(0x890, 0x891),
|
|
75
|
+
keep(0x8e2, 0x8e2),
|
|
76
|
+
// The Mongolian free variation selectors.
|
|
77
|
+
withheld(0x180b, 0x180d),
|
|
78
|
+
// MONGOLIAN VOWEL SEPARATOR, which shapes the vowel after it.
|
|
79
|
+
keep(0x180e, 0x180e),
|
|
80
|
+
withheld(0x180f, 0x180f),
|
|
81
|
+
// ZWSP, a place a line may break (`wrap.ts`); ZWNJ, which Persian and Urdu spell
|
|
82
|
+
// with; ZWJ, for emoji and conjuncts; and the bidi marks LRM and RLM, which hold
|
|
83
|
+
// no state, so right-to-left text can use them. ZWJ's joining is Rich's width
|
|
84
|
+
// rule, which `cells.ts` holds apart from this row.
|
|
85
|
+
keep(0x200b, 0x200f),
|
|
86
|
+
// LINE SEPARATOR and PARAGRAPH SEPARATOR.
|
|
87
|
+
lineBreak(0x2028, 0x2029),
|
|
88
|
+
// The bidi embeddings and overrides with their closer PDF, and the isolates
|
|
89
|
+
// below. Content holds them; what reaches the wire is `sealed`'s to decide.
|
|
90
|
+
keep(0x202a, 0x202e),
|
|
91
|
+
// WORD JOINER, which only asks that nothing break here, and the invisible
|
|
92
|
+
// mathematical operators.
|
|
93
|
+
keep(0x2060, 0x2064),
|
|
94
|
+
// The bidi isolates with their closer PDI.
|
|
95
|
+
keep(0x2066, 0x2069),
|
|
96
|
+
// The deprecated shaping and swapping switches. Each holds state to the end of
|
|
97
|
+
// the text, Unicode deprecates them, and no terminal was found honouring one.
|
|
98
|
+
strip(0x206a, 0x206f),
|
|
99
|
+
// VARIATION SELECTOR-1 to 16: glyph variants, and text or emoji presentation.
|
|
100
|
+
withheld(0xfe00, 0xfe0f),
|
|
101
|
+
// ZERO WIDTH NO-BREAK SPACE, the byte order mark, as a word joiner.
|
|
102
|
+
keep(0xfeff, 0xfeff),
|
|
103
|
+
// Interlinear annotation. No terminal draws ruby, the annotating text stays
|
|
104
|
+
// visible, and the anchors hold no state, so there is nothing to contain.
|
|
105
|
+
keep(0xfff9, 0xfffb),
|
|
106
|
+
// The Kaithi number signs, which draw a glyph.
|
|
107
|
+
keep(0x110bd, 0x110bd),
|
|
108
|
+
keep(0x110cd, 0x110cd),
|
|
109
|
+
// Egyptian hieroglyph, shorthand and musical format controls.
|
|
110
|
+
keep(0x13430, 0x1343f),
|
|
111
|
+
keep(0x1bca0, 0x1bca3),
|
|
112
|
+
keep(0x1d173, 0x1d17a),
|
|
113
|
+
// LANGUAGE TAG, deprecated.
|
|
114
|
+
strip(0xe0001, 0xe0001),
|
|
115
|
+
// The tag characters: an emoji subdivision flag, or text no reader can see.
|
|
116
|
+
withheld(0xe0020, 0xe007f),
|
|
117
|
+
// The ideographic variation selectors: Japanese and Chinese glyph variants.
|
|
118
|
+
withheld(0xe0100, 0xe01ef),
|
|
119
|
+
];
|
|
120
|
+
/** What content shows in place of `codePoint`, whose disposition is `pictured`. */
|
|
121
|
+
export function pictureOf(codePoint) {
|
|
122
|
+
const row = rowOf(codePoint);
|
|
123
|
+
if (row?.disposition !== "pictured")
|
|
124
|
+
throw new RangeError(`U+${codePoint.toString(16).padStart(4, "0")} is not pictured`);
|
|
125
|
+
return row.picture(codePoint);
|
|
126
|
+
}
|
|
127
|
+
/** A regular expression class of the code points in rows `chosen` picks, for `u`. */
|
|
128
|
+
export function controlClass(chosen) {
|
|
129
|
+
const hex = (codePoint) => `\\u{${codePoint.toString(16)}}`;
|
|
130
|
+
return `[${CONTROL_CODES.filter((row) => chosen(row.disposition)).map((row) => `${hex(row.first)}-${hex(row.last)}`).join("")}]`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* UTS #51's subdivision flag, as a `u` source: the black flag, a CLDR subdivision
|
|
134
|
+
* spelled in tag letters and digits, and CANCEL TAG. The one sequence tag
|
|
135
|
+
* characters have a reader for.
|
|
136
|
+
*/
|
|
137
|
+
export const SUBDIVISION_FLAG = "\u{1f3f4}[\u{e0030}-\u{e0039}\u{e0061}-\u{e007a}]{3,6}\u{e007f}";
|
|
138
|
+
/** What happens to `codePoint`. One in no row is kept. */
|
|
139
|
+
export function dispositionOf(codePoint) {
|
|
140
|
+
return rowOf(codePoint)?.disposition ?? "keep";
|
|
141
|
+
}
|
|
142
|
+
/** The row holding `codePoint`, if any. */
|
|
143
|
+
function rowOf(codePoint) {
|
|
144
|
+
let low = 0;
|
|
145
|
+
let high = CONTROL_CODES.length - 1;
|
|
146
|
+
while (low <= high) {
|
|
147
|
+
const mid = (low + high) >> 1;
|
|
148
|
+
const row = CONTROL_CODES[mid];
|
|
149
|
+
if (codePoint < row.first)
|
|
150
|
+
high = mid - 1;
|
|
151
|
+
else if (codePoint > row.last)
|
|
152
|
+
low = mid + 1;
|
|
153
|
+
else
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
package/dist/core/env.d.ts
CHANGED
|
@@ -14,3 +14,9 @@
|
|
|
14
14
|
* `NodeJS` namespace. `test/seam/browser-types.test.ts` holds that line.
|
|
15
15
|
*/
|
|
16
16
|
export type Env = Readonly<Record<string, string | undefined>>;
|
|
17
|
+
/**
|
|
18
|
+
* A terminal that says it cannot be drawn on: `TERM` is `dumb` or `unknown`,
|
|
19
|
+
* as Emacs' shell mode sets it. It takes no colour and nothing that moves the
|
|
20
|
+
* cursor.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isDumbTerminal(env: Env): boolean;
|
package/dist/core/env.js
CHANGED
|
@@ -13,4 +13,12 @@
|
|
|
13
13
|
* without `@types/node` — the ordinary browser project — cannot resolve the
|
|
14
14
|
* `NodeJS` namespace. `test/seam/browser-types.test.ts` holds that line.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A terminal that says it cannot be drawn on: `TERM` is `dumb` or `unknown`,
|
|
18
|
+
* as Emacs' shell mode sets it. It takes no colour and nothing that moves the
|
|
19
|
+
* cursor.
|
|
20
|
+
*/
|
|
21
|
+
export function isDumbTerminal(env) {
|
|
22
|
+
const term = env["TERM"];
|
|
23
|
+
return term === "dumb" || term === "unknown";
|
|
24
|
+
}
|
package/dist/core/export-html.js
CHANGED
|
@@ -121,13 +121,14 @@ function onGrid(text, draw) {
|
|
|
121
121
|
* blinks with the glyph. A wide glyph's box sits between the two: it inherits
|
|
122
122
|
* the double underline, and the inner span inside it draws the other lines.
|
|
123
123
|
*/
|
|
124
|
-
function runHtml({ text, look }) {
|
|
124
|
+
function runHtml({ text, look, inVisualOrder }) {
|
|
125
125
|
const drawn = look.underline === "double"
|
|
126
126
|
? span([...paintCss(look), "text-decoration-line:underline", "text-decoration-style:double", ...BLINK[look.blink]], onGrid(text, (glyph) => span([`color:${look.foreground.hex}`, ...glyphCss(look)], glyph)))
|
|
127
127
|
: span([...paintCss(look), ...glyphCss(look)], onGrid(text, (glyph) => glyph));
|
|
128
|
+
const ordered = inVisualOrder ? span(["unicode-bidi:bidi-override"], drawn) : drawn;
|
|
128
129
|
return look.href === null
|
|
129
|
-
?
|
|
130
|
-
: `<a href="${escapeAttribute(look.href)}" style="${ANCHOR_CSS}">${
|
|
130
|
+
? ordered
|
|
131
|
+
: `<a href="${escapeAttribute(look.href)}" style="${ANCHOR_CSS}">${ordered}</a>`;
|
|
131
132
|
}
|
|
132
133
|
/**
|
|
133
134
|
* The CSS a page includes once, wherever fragments appear: what an inline
|
|
@@ -65,6 +65,8 @@ export interface ExportLook {
|
|
|
65
65
|
export interface ExportRun {
|
|
66
66
|
readonly text: string;
|
|
67
67
|
readonly look: ExportLook;
|
|
68
|
+
/** Its cells are in display order already, so a browser must not reorder them. */
|
|
69
|
+
readonly inVisualOrder: boolean;
|
|
68
70
|
}
|
|
69
71
|
/** One terminal row, left to right. An empty array is a blank row. */
|
|
70
72
|
export type ExportLine = readonly ExportRun[];
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import { blendRgb, ColorSpec } from "./color.js";
|
|
33
33
|
import { NULL_STYLE } from "./style.js";
|
|
34
|
+
import { sealed } from "./seal.js";
|
|
34
35
|
/**
|
|
35
36
|
* The URL schemes an export will link.
|
|
36
37
|
*
|
|
@@ -146,13 +147,16 @@ export function resolveLook(style, theme) {
|
|
|
146
147
|
*/
|
|
147
148
|
export function exportLines(segments, theme) {
|
|
148
149
|
const rows = [[]];
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
// A browser is a display that reorders and holds the controls at zero width.
|
|
151
|
+
for (const segment of sealed(segments, "logical")) {
|
|
152
|
+
const style = segment.style ?? NULL_STYLE;
|
|
153
|
+
const look = resolveLook(style, theme);
|
|
154
|
+
const inVisualOrder = style.bidi?.order === "visual";
|
|
151
155
|
segment.text.split("\n").forEach((piece, index) => {
|
|
152
156
|
if (index > 0)
|
|
153
157
|
rows.push([]);
|
|
154
158
|
if (piece.length > 0)
|
|
155
|
-
rows[rows.length - 1].push({ text: piece, look });
|
|
159
|
+
rows[rows.length - 1].push({ text: piece, look, inVisualOrder });
|
|
156
160
|
});
|
|
157
161
|
}
|
|
158
162
|
return rows[rows.length - 1].length === 0 ? rows.slice(0, -1) : rows;
|
package/dist/core/export-svg.js
CHANGED
|
@@ -87,10 +87,10 @@ function chunk(text, column) {
|
|
|
87
87
|
function place(rows) {
|
|
88
88
|
return rows.flatMap((runs, row) => {
|
|
89
89
|
let column = 0;
|
|
90
|
-
return runs.map(({ text, look }) => {
|
|
90
|
+
return runs.map(({ text, look, inVisualOrder }) => {
|
|
91
91
|
const chunks = chunk(text, column);
|
|
92
92
|
const cells = chunks.reduce((sum, c) => sum + c.cells, 0);
|
|
93
|
-
const placed = { row, column, cells, look, chunks };
|
|
93
|
+
const placed = { row, column, cells, look, inVisualOrder, chunks };
|
|
94
94
|
column += cells;
|
|
95
95
|
return placed;
|
|
96
96
|
});
|
|
@@ -132,10 +132,11 @@ function backgroundSvg(run) {
|
|
|
132
132
|
? []
|
|
133
133
|
: [`<rect fill="${run.look.background.hex}" ${cellBox(run)} shape-rendering="crispEdges"/>`];
|
|
134
134
|
}
|
|
135
|
-
function textSvg({ text, column, cells }, baseline) {
|
|
135
|
+
function textSvg({ text, column, cells }, baseline, inVisualOrder) {
|
|
136
136
|
// A chunk of no cells has no length to stretch to; it takes the font's.
|
|
137
137
|
const length = cells === 0 ? "" : ` textLength="${n(cells * CELL_WIDTH)}" lengthAdjust="spacingAndGlyphs"`;
|
|
138
|
-
|
|
138
|
+
const order = inVisualOrder ? ` unicode-bidi="bidi-override"` : "";
|
|
139
|
+
return `<text x="${n(column * CELL_WIDTH)}" y="${n(baseline)}"${length}${order} xml:space="preserve">${escapeText(text)}</text>`;
|
|
139
140
|
}
|
|
140
141
|
/**
|
|
141
142
|
* One run: its glyphs, then its lines and outline over them, in a group whose
|
|
@@ -155,7 +156,7 @@ function runSvg(run, className) {
|
|
|
155
156
|
];
|
|
156
157
|
const radius = OUTLINE_RADIUS[look.outline];
|
|
157
158
|
const drawn = [
|
|
158
|
-
...run.chunks.map((c) => textSvg(c, baseline)),
|
|
159
|
+
...run.chunks.map((c) => textSvg(c, baseline, run.inVisualOrder)),
|
|
159
160
|
...lineYs.map((y) => `<rect x="${x}" y="${n(y)}" width="${width}" height="${LINE_THICKNESS}"/>`),
|
|
160
161
|
...(radius === null
|
|
161
162
|
? []
|
package/dist/core/json.js
CHANGED
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
* imports core primitives only. `Console.printJson` delegates here, so this is
|
|
7
7
|
* where the console can reach it without an upward edge. [LAW:one-way-deps]
|
|
8
8
|
*/
|
|
9
|
+
import { escaped } from "./bidi.js";
|
|
9
10
|
import { RichText } from "./text.js";
|
|
10
11
|
import { JSONHighlighter } from "./highlighter.js";
|
|
11
12
|
import { measure, render } from "./protocol.js";
|
|
12
13
|
const highlighter = new JSONHighlighter();
|
|
14
|
+
/** `character` as JSON escapes, a surrogate pair for one past the BMP. */
|
|
15
|
+
const jsonEscape = (character) => Array.from({ length: character.length }, (_, at) => `\\u${character.charCodeAt(at).toString(16).padStart(4, "0")}`).join("");
|
|
13
16
|
export class JSONRenderable {
|
|
14
17
|
text;
|
|
15
18
|
constructor(text) {
|
|
@@ -38,8 +41,12 @@ export class JSONRenderable {
|
|
|
38
41
|
return value;
|
|
39
42
|
}
|
|
40
43
|
: undefined;
|
|
41
|
-
|
|
42
|
-
const
|
|
44
|
+
// `undefined` for what JSON has no form of, a function or a symbol, whatever its type says.
|
|
45
|
+
const jsonStr = JSON.stringify(data, replacer, indent) ?? "";
|
|
46
|
+
// Data on show: one string holds every key and value, so a control left in
|
|
47
|
+
// one would act on the rest of its line, and what content deletes or the wire
|
|
48
|
+
// withholds would leave a document that no longer parses back to this data.
|
|
49
|
+
const text = new RichText(escaped(jsonStr, jsonEscape));
|
|
43
50
|
if (doHighlight) {
|
|
44
51
|
highlighter.highlight(text);
|
|
45
52
|
}
|
package/dist/core/markup.js
CHANGED
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
* and is rejected with a `MarkupSyntaxError`. For the same reason a style tag
|
|
18
18
|
* may not open on one side of a plugin tag and close on the other.
|
|
19
19
|
*/
|
|
20
|
+
import { closed } from "./bidi.js";
|
|
20
21
|
import { cellLen } from "./cells.js";
|
|
21
22
|
import { Style, StyleSyntaxError } from "./style.js";
|
|
22
|
-
import { RichText,
|
|
23
|
+
import { RichText, sanitized } from "./text.js";
|
|
23
24
|
import { emojiReplace } from "./emoji.js";
|
|
24
25
|
import { pattern } from "./pattern.js";
|
|
25
26
|
import { State } from "./state.js";
|
|
@@ -545,7 +546,7 @@ function walk(tokens, source, registry, emoji) {
|
|
|
545
546
|
// counted along the way — a span, a splice point — indexes the text it
|
|
546
547
|
// lands on. Stripped only by the constructor, a control character
|
|
547
548
|
// shifted every style after it one cell right.
|
|
548
|
-
write(out,
|
|
549
|
+
write(out, sanitized(emoji ? emojiReplace(text) : text));
|
|
549
550
|
continue;
|
|
550
551
|
}
|
|
551
552
|
const plugin = pluginOf(token, registry);
|
|
@@ -580,7 +581,7 @@ function walk(tokens, source, registry, emoji) {
|
|
|
580
581
|
const children = compose(takeFrom(out, target.mark));
|
|
581
582
|
const text = handler({ attrs, children, raw: source.slice(target.tag.end, token.start) });
|
|
582
583
|
out.spliced.push({ at: out.length, text });
|
|
583
|
-
write(out, text.plain);
|
|
584
|
+
write(out, closed(text.plain));
|
|
584
585
|
}
|
|
585
586
|
// Close what is still open, innermost first. The reference pops its stack
|
|
586
587
|
// here (`while style_stack: start, tag = style_stack.pop()`), and popping is
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orienting a drawn line for its destination's `TextOrder`.
|
|
3
|
+
*
|
|
4
|
+
* Levels are resolved over the whole logical line and a row is ordered alone,
|
|
5
|
+
* because UAX #9 runs L1 and L2 after line breaking. So `directed` runs ahead of
|
|
6
|
+
* wrapping and each row's `orient` after it, and cuts fall on logical offsets in
|
|
7
|
+
* every order: what survives a cut is a logical prefix of the sentence, which is
|
|
8
|
+
* what a reader of either direction expects.
|
|
9
|
+
*
|
|
10
|
+
* [LAW:dataflow-not-control-flow] The order chooses values and never steps: a
|
|
11
|
+
* `plain` line passes through both.
|
|
12
|
+
*/
|
|
13
|
+
import { Segment } from "./segment.js";
|
|
14
|
+
import { type Style } from "./style.js";
|
|
15
|
+
import type { TextOrder } from "./text-order.js";
|
|
16
|
+
/**
|
|
17
|
+
* The cells that end `text` and are no content: its whitespace, once what draws no
|
|
18
|
+
* cell among it is gone. A mark drawn on a space is not in it, because that space
|
|
19
|
+
* is a cell with something on it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function drawnTail(text: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The cells of trailing whitespace on a wrapped line.
|
|
24
|
+
*
|
|
25
|
+
* A wrap cuts before a word, so the line it closes ends with the whitespace
|
|
26
|
+
* that followed *its* last word — padding the break created, not content the
|
|
27
|
+
* author wrote. Measuring it is what lets the overflow method tell "this line
|
|
28
|
+
* was cut" from "this line ends in spaces" and stamp an ellipsis only on the
|
|
29
|
+
* first, and what keeps a centred line from drifting half a space off true.
|
|
30
|
+
*
|
|
31
|
+
* Asked of the line's text rather than walked back through its segments,
|
|
32
|
+
* because where a line ends is a fact about the characters and not about how
|
|
33
|
+
* they were split. Walking the segments made it a fact about both: it read a
|
|
34
|
+
* segment with no trailing whitespace as content and stopped there, which an
|
|
35
|
+
* empty segment also looks like — and a crop leaves one behind. A styled line
|
|
36
|
+
* whose last cells were cropped therefore reported no hanging whitespace at
|
|
37
|
+
* all and was centred as though it were content to the edge, so a span landing
|
|
38
|
+
* anywhere in a wrap's whitespace un-centred the line it closed. What draws no
|
|
39
|
+
* cell after the whitespace is no content either: `closed` writes a bidi control
|
|
40
|
+
* exactly there, and a cut leaves a zero-width code point that opened the next
|
|
41
|
+
* word on the row before it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function hangingWhitespace(line: Segment[]): number;
|
|
44
|
+
/** What a row of a directed line is, given the text it started as and its offset in the line. */
|
|
45
|
+
export interface Directed {
|
|
46
|
+
/** The line to cut: X9's removals gone where the terminal will not need them. */
|
|
47
|
+
readonly line: readonly Segment[];
|
|
48
|
+
/** Whether a row's cells may not be merged with the padding beside them. */
|
|
49
|
+
readonly oriented: boolean;
|
|
50
|
+
/** A row of `source` at `start`: the segments it became, ready for padding. */
|
|
51
|
+
orient(shown: readonly Segment[], start: number, source: string): Segment[];
|
|
52
|
+
}
|
|
53
|
+
/** What `order` does with `line`, one logical line of drawn segments. */
|
|
54
|
+
export declare function directed(line: readonly Segment[], order: TextOrder): Directed;
|
|
55
|
+
/** `text` as `order` measures it: what `directed` removes ahead of wrapping is no word. */
|
|
56
|
+
export declare function measured(text: string, order: TextOrder): string;
|
|
57
|
+
/** The offsets in `text`, one row, of the characters `order` shows at its left and right edges. */
|
|
58
|
+
export declare function edges(text: string, order: TextOrder): {
|
|
59
|
+
readonly left: number;
|
|
60
|
+
readonly right: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A label set between `before` and `after`, which are frame and stay where they
|
|
64
|
+
* are, for a row drawn with no `RichText`: only the label is oriented, or a
|
|
65
|
+
* button's brackets would be mirrored around it. A label `directed` leaves alone
|
|
66
|
+
* is one segment with its frame, as it always was.
|
|
67
|
+
*/
|
|
68
|
+
export declare function labelled(before: string, label: string, after: string, style: Style | undefined, order: TextOrder | undefined): Segment[];
|