@promptctl/rich-js 0.27.0 → 0.29.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 +7 -6
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +65 -46
- package/dist/core/animation.js +91 -67
- package/dist/core/ansi.d.ts +19 -12
- package/dist/core/ansi.js +114 -36
- 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 +94 -6
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/color.d.ts +56 -57
- package/dist/core/color.js +79 -81
- package/dist/core/console.d.ts +24 -7
- package/dist/core/console.js +27 -8
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/easing.d.ts +40 -40
- package/dist/core/easing.js +40 -40
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +5 -4
- package/dist/core/export-lines.d.ts +13 -11
- package/dist/core/export-lines.js +19 -15
- package/dist/core/export-svg.d.ts +3 -3
- package/dist/core/export-svg.js +10 -9
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +5 -4
- package/dist/core/memo.d.ts +1 -1
- package/dist/core/memo.js +1 -1
- package/dist/core/oklch.d.ts +14 -14
- package/dist/core/oklch.js +10 -10
- 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/paint.d.ts +9 -9
- package/dist/core/paint.js +3 -3
- package/dist/core/pretty.d.ts +1 -1
- package/dist/core/pretty.js +23 -19
- package/dist/core/protocol.d.ts +47 -26
- package/dist/core/protocol.js +31 -23
- package/dist/core/render.d.ts +19 -14
- package/dist/core/render.js +30 -17
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +12 -4
- package/dist/core/segment.js +16 -6
- package/dist/core/strip.d.ts +83 -83
- package/dist/core/strip.js +70 -70
- package/dist/core/style.d.ts +46 -18
- package/dist/core/style.js +92 -56
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +12 -2
- package/dist/core/text.js +136 -90
- package/dist/core/timeline.d.ts +5 -5
- package/dist/core/timeline.js +11 -11
- package/dist/core/tween.d.ts +6 -6
- package/dist/core/tween.js +10 -10
- package/dist/core/view.d.ts +5 -5
- package/dist/core/view.js +15 -14
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +50 -9
- package/dist/host/app.js +120 -15
- package/dist/host/host-environment.d.ts +2 -2
- package/dist/host/host-environment.js +2 -2
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +42 -9
- package/dist/host/input.js +91 -7
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +4 -3
- package/dist/host/terminal-host.js +1 -1
- package/dist/index.d.ts +17 -16
- package/dist/index.js +11 -11
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.js +1 -1
- package/dist/node/terminal-host.d.ts +1 -1
- package/dist/node/traceback.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +6 -8
- package/dist/renderables/effect.d.ts +88 -87
- package/dist/renderables/effect.js +124 -114
- package/dist/renderables/effects.d.ts +105 -76
- package/dist/renderables/effects.js +309 -176
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +2 -2
- package/dist/renderables/flexStrip.d.ts +7 -7
- package/dist/renderables/flexStrip.js +15 -15
- package/dist/renderables/frames.d.ts +7 -16
- package/dist/renderables/frames.js +12 -27
- package/dist/renderables/glyph-effect.d.ts +22 -23
- package/dist/renderables/glyph-effect.js +43 -41
- package/dist/renderables/glyph-effects.d.ts +33 -14
- package/dist/renderables/glyph-effects.js +90 -22
- package/dist/renderables/html-entities.d.ts +1 -1
- package/dist/renderables/html-entities.js +1 -1
- package/dist/renderables/lifecycle.d.ts +8 -10
- package/dist/renderables/lifecycle.js +17 -18
- package/dist/renderables/live.d.ts +2 -2
- package/dist/renderables/live.js +7 -7
- package/dist/renderables/markdown-parse.js +3 -3
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +7 -5
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +1 -1
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +21 -18
- package/dist/renderables/prompt.d.ts +1 -1
- package/dist/renderables/scroll.d.ts +1 -1
- package/dist/renderables/scroll.js +3 -3
- package/dist/renderables/spinner.d.ts +4 -4
- package/dist/renderables/spinner.js +10 -10
- package/dist/renderables/syntax.d.ts +2 -2
- package/dist/renderables/syntax.js +9 -4
- package/dist/renderables/table.js +5 -4
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +1 -1
- package/dist/renderables/tree.js +3 -3
- package/dist/template-bindings/animation-funcs.d.ts +60 -29
- package/dist/template-bindings/animation-funcs.js +126 -43
- package/dist/template-bindings/color-funcs.d.ts +14 -14
- package/dist/template-bindings/color-funcs.js +25 -25
- package/dist/template-bindings/index.d.ts +16 -14
- package/dist/template-bindings/index.js +17 -14
- package/dist/template-bindings/palette-funcs.d.ts +2 -2
- package/dist/template-bindings/palette-funcs.js +4 -4
- package/dist/template-bindings/style-funcs.d.ts +5 -5
- package/dist/template-bindings/style-funcs.js +16 -16
- package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
- package/dist/themes/{transpose.js → adjust.js} +28 -28
- package/dist/themes/buildPalette.d.ts +6 -5
- package/dist/themes/buildPalette.js +22 -22
- package/dist/themes/colorMath.d.ts +16 -16
- package/dist/themes/colorMath.js +32 -32
- package/dist/themes/data/ansi-tables.d.ts +2 -2
- package/dist/themes/data/ansi-tables.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/data/types.d.ts +4 -4
- package/dist/themes/data/types.js +2 -2
- package/dist/themes/palette.d.ts +12 -12
- package/dist/themes/palette.js +16 -16
- package/dist/themes/ramp.d.ts +17 -17
- package/dist/themes/ramp.js +12 -12
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +6 -8
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +3 -3
- package/dist/widgets/button.js +8 -8
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +3 -3
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/event-router.d.ts +6 -6
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/focus-manager.d.ts +3 -3
- package/dist/widgets/focus-manager.js +3 -3
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/ink.d.ts +3 -3
- package/dist/widgets/ink.js +4 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/static-item.d.ts +2 -2
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +5 -5
- package/dist/widgets/themed-widget.js +4 -4
- package/dist/widgets/toggle.d.ts +4 -4
- package/dist/widgets/toggle.js +6 -6
- package/dist/widgets/types.d.ts +6 -6
- package/dist/widgets/types.js +2 -2
- package/dist/widgets/widget-app.d.ts +10 -10
- package/dist/widgets/widget-app.js +8 -8
- package/dist/widgets/widget-base.d.ts +2 -2
- package/package.json +5 -4
package/dist/core/view.d.ts
CHANGED
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
* anything it draws. What the drawn frame says about when it changes is on
|
|
23
23
|
* the timeline's clock, and `drawView` carries it back onto the host's by
|
|
24
24
|
* running the frame through the same timeline: the frame is a sample held
|
|
25
|
-
* for the intersection of its cells' holds (`Segment.
|
|
26
|
-
* `Segment.
|
|
27
|
-
* (`RenderOptions.
|
|
25
|
+
* for the intersection of its cells' holds (`Segment.validFrom`,
|
|
26
|
+
* `Segment.validUntil`) and the holds it was told that no cell carries
|
|
27
|
+
* (`RenderOptions.onValidity`), and `Timeline.run` is the one place a hold is
|
|
28
28
|
* mapped onto host time, forwards, backwards or paused.
|
|
29
29
|
*
|
|
30
30
|
* What a host does with that is its own policy, and it reads it off the
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* auto-refresh, polls every frame.
|
|
34
34
|
*
|
|
35
35
|
* Tier 7 of `src/core/`: `./animation.ts`'s `Animation`, `./timeline.ts`,
|
|
36
|
-
* `./segment.ts`, `./protocol.ts` and `./style.ts`'s
|
|
36
|
+
* `./segment.ts`, `./protocol.ts` and `./style.ts`'s holds, nothing else.
|
|
37
37
|
* [LAW:one-way-deps]
|
|
38
38
|
*/
|
|
39
39
|
import { type HostFrame, type Renderable, type RenderOptions } from "./protocol.js";
|
|
@@ -86,7 +86,7 @@ export interface FrameSays {
|
|
|
86
86
|
/** The host time the frame was drawn at. */
|
|
87
87
|
readonly t: number;
|
|
88
88
|
/** The first host moment, never before `t`, at which the frame may stop showing this: `t` while it moves, `Infinity` once it never will. */
|
|
89
|
-
readonly
|
|
89
|
+
readonly validUntil: number;
|
|
90
90
|
}
|
|
91
91
|
/** A frame drawn from a view: its lines, and what it says. */
|
|
92
92
|
export interface DrawnView extends FrameSays {
|
package/dist/core/view.js
CHANGED
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
* anything it draws. What the drawn frame says about when it changes is on
|
|
23
23
|
* the timeline's clock, and `drawView` carries it back onto the host's by
|
|
24
24
|
* running the frame through the same timeline: the frame is a sample held
|
|
25
|
-
* for the intersection of its cells' holds (`Segment.
|
|
26
|
-
* `Segment.
|
|
27
|
-
* (`RenderOptions.
|
|
25
|
+
* for the intersection of its cells' holds (`Segment.validFrom`,
|
|
26
|
+
* `Segment.validUntil`) and the holds it was told that no cell carries
|
|
27
|
+
* (`RenderOptions.onValidity`), and `Timeline.run` is the one place a hold is
|
|
28
28
|
* mapped onto host time, forwards, backwards or paused.
|
|
29
29
|
*
|
|
30
30
|
* What a host does with that is its own policy, and it reads it off the
|
|
@@ -33,11 +33,12 @@
|
|
|
33
33
|
* auto-refresh, polls every frame.
|
|
34
34
|
*
|
|
35
35
|
* Tier 7 of `src/core/`: `./animation.ts`'s `Animation`, `./timeline.ts`,
|
|
36
|
-
* `./segment.ts`, `./protocol.ts` and `./style.ts`'s
|
|
36
|
+
* `./segment.ts`, `./protocol.ts` and `./style.ts`'s holds, nothing else.
|
|
37
37
|
* [LAW:one-way-deps]
|
|
38
38
|
*/
|
|
39
39
|
import { activePass, render } from "./protocol.js";
|
|
40
40
|
import { Segment } from "./segment.js";
|
|
41
|
+
import { intersection, NEVER_CHANGES } from "./style.js";
|
|
41
42
|
import { Timeline } from "./timeline.js";
|
|
42
43
|
/** `view` as the renderable a host holds: a function is asked each time it is drawn. */
|
|
43
44
|
export function hostView(view) {
|
|
@@ -77,9 +78,9 @@ export function frameNow(host, time) {
|
|
|
77
78
|
return { time: frameTime(time), host };
|
|
78
79
|
}
|
|
79
80
|
/** What a frame not yet drawn at host time `t` says: nothing. */
|
|
80
|
-
export const undrawn = (t) => ({ t,
|
|
81
|
+
export const undrawn = (t) => ({ t, validUntil: Number.POSITIVE_INFINITY });
|
|
81
82
|
/** The hold of the frame `lines` draw, told `told` besides: the intersection of its cells' holds and it. */
|
|
82
|
-
const
|
|
83
|
+
const frameValidity = (lines, told) => lines.reduce((validity, row) => intersection(validity, { validFrom: Segment.validFrom(row), validUntil: Segment.validUntil(row) }), told);
|
|
83
84
|
/**
|
|
84
85
|
* `view` drawn by `drawnBy` at `time` under `options`, its lines shaped by
|
|
85
86
|
* `draw` — the host's own render under those options, with its height and
|
|
@@ -91,17 +92,17 @@ const frameHold = (lines, told) => lines.reduce((hold, row) => ({ lastChange: Ma
|
|
|
91
92
|
*/
|
|
92
93
|
export function drawView(view, time, drawnBy, options, draw) {
|
|
93
94
|
const { now: host, timeline } = time;
|
|
94
|
-
let told =
|
|
95
|
-
const
|
|
96
|
-
told =
|
|
95
|
+
let told = NEVER_CHANGES;
|
|
96
|
+
const onValidity = (validity) => {
|
|
97
|
+
told = intersection(told, validity);
|
|
97
98
|
};
|
|
98
99
|
// Made before the render opens: the one time the frame has.
|
|
99
100
|
const frame = frameNow(drawnBy, time);
|
|
100
|
-
const lines = draw(view, { ...options, frame,
|
|
101
|
-
const
|
|
101
|
+
const lines = draw(view, { ...options, frame, onValidity });
|
|
102
|
+
const validity = frameValidity(lines, told);
|
|
102
103
|
// The frame as the sample of the view it is, on the timeline's clock; the
|
|
103
104
|
// timeline says when the host's clock leaves its hold.
|
|
104
|
-
const sample = { at: () => lines,
|
|
105
|
-
const
|
|
106
|
-
return { t: host, lines,
|
|
105
|
+
const sample = { at: () => lines, validUntil: () => validity.validUntil, validFrom: () => validity.validFrom, reduced: () => sample };
|
|
106
|
+
const validUntil = Timeline.run(timeline, sample).validUntil(host);
|
|
107
|
+
return { t: host, lines, validUntil };
|
|
107
108
|
}
|
package/dist/core/wrap.d.ts
CHANGED
|
@@ -18,16 +18,30 @@
|
|
|
18
18
|
* line still too wide once wrapping is done. That order is the whole reason a
|
|
19
19
|
* long sentence in a table cell grows the row while an unbreakable word in the
|
|
20
20
|
* same cell still ellipsizes.
|
|
21
|
+
*
|
|
22
|
+
* Rich breaks only at whitespace, and that is what happens to any word holding
|
|
23
|
+
* nothing below: `test/core/text-wrap.golden.txt` pins it. Past that, a word
|
|
24
|
+
* breaks where its text says it may (UAX #14): after ZWSP, at a soft hyphen, and
|
|
25
|
+
* between the words of a script written without spaces. U+2060 and U+FEFF forbid
|
|
26
|
+
* a break, which here needs nothing but not being one.
|
|
21
27
|
*/
|
|
22
|
-
import { type CellCol } from "./cells.js";
|
|
28
|
+
import { type CellCol, type CodePoint } from "./cells.js";
|
|
29
|
+
/** One place a line of text was cut. */
|
|
30
|
+
export interface LineBreak {
|
|
31
|
+
/** Where the line is cut, in cells. */
|
|
32
|
+
readonly cell: CellCol;
|
|
33
|
+
/** The soft hyphen this break draws as `-`. Absent at any other break, and where no hyphen fits. */
|
|
34
|
+
readonly hyphen: CodePoint | undefined;
|
|
35
|
+
}
|
|
23
36
|
/**
|
|
24
|
-
* The cells each
|
|
25
|
-
* width `divideLine` can wrap it to without cutting a word.
|
|
37
|
+
* The cells each piece of `text` needs, in order — the widest is the narrowest
|
|
38
|
+
* width `divideLine` can wrap it to without cutting a word. A hyphen is not in
|
|
39
|
+
* them: where it does not fit, the break goes without it.
|
|
26
40
|
*/
|
|
27
41
|
export declare function wordWidths(text: string): number[];
|
|
28
42
|
/**
|
|
29
|
-
* The
|
|
30
|
-
* `
|
|
43
|
+
* The breaks that wrap `text` into lines of at most `width` cells, for
|
|
44
|
+
* `Segment.divide` to apply to the styled line.
|
|
31
45
|
*
|
|
32
46
|
* `fold` decides what happens to a word that fits on no line at all: folded,
|
|
33
47
|
* it is chopped across as many lines as it needs; unfolded, it is left whole
|
|
@@ -36,6 +50,10 @@ export declare function wordWidths(text: string): number[];
|
|
|
36
50
|
* can truncate it, which is what makes an unbreakable word ellipsize while the
|
|
37
51
|
* sentence around it merely wraps.
|
|
38
52
|
*
|
|
53
|
+
* A piece that ends in a soft hyphen stays on a line only with room for the
|
|
54
|
+
* hyphen, so a line it closes holds it, and a word that fits whole needs none. A
|
|
55
|
+
* piece as wide as the line breaks with no hyphen rather than lose a letter to one.
|
|
56
|
+
*
|
|
39
57
|
* Requires `width >= 1`. A zero-cell canvas gets no cuts, because "no cut
|
|
40
58
|
* fits" and "no cell fits" are different facts and only one of them is a list
|
|
41
59
|
* of offsets — the caller answers the second one by cropping the single
|
|
@@ -43,4 +61,4 @@ export declare function wordWidths(text: string): number[];
|
|
|
43
61
|
*/
|
|
44
62
|
export declare function divideLine(text: string, width: CellCol, options: {
|
|
45
63
|
fold: boolean;
|
|
46
|
-
}):
|
|
64
|
+
}): LineBreak[];
|
package/dist/core/wrap.js
CHANGED
|
@@ -18,8 +18,28 @@
|
|
|
18
18
|
* line still too wide once wrapping is done. That order is the whole reason a
|
|
19
19
|
* long sentence in a table cell grows the row while an unbreakable word in the
|
|
20
20
|
* same cell still ellipsizes.
|
|
21
|
+
*
|
|
22
|
+
* Rich breaks only at whitespace, and that is what happens to any word holding
|
|
23
|
+
* nothing below: `test/core/text-wrap.golden.txt` pins it. Past that, a word
|
|
24
|
+
* breaks where its text says it may (UAX #14): after ZWSP, at a soft hyphen, and
|
|
25
|
+
* between the words of a script written without spaces. U+2060 and U+FEFF forbid
|
|
26
|
+
* a break, which here needs nothing but not being one.
|
|
21
27
|
*/
|
|
22
|
-
import { cellLen, chopCells
|
|
28
|
+
import { asCellCol, asCodePoint, cellLen, chopCells } from "./cells.js";
|
|
29
|
+
import { pattern } from "./pattern.js";
|
|
30
|
+
const SOFT_HYPHEN = "\u00ad";
|
|
31
|
+
const ZWSP = "\u200b";
|
|
32
|
+
// The scripts UAX #14 sends to a dictionary (line break class SA) and ICU has one
|
|
33
|
+
// for. Which words it finds varies by ICU, and a dictionary cannot be vendored as
|
|
34
|
+
// the width table is; a word it misses is folded like any other.
|
|
35
|
+
const UNSPACED = "[\\p{Script_Extensions=Thai}\\p{Script_Extensions=Lao}\\p{Script_Extensions=Khmer}\\p{Script_Extensions=Myanmar}]+";
|
|
36
|
+
const BREAKABLE = pattern(new RegExp(`[${SOFT_HYPHEN}${ZWSP}]|${UNSPACED}`, "u"));
|
|
37
|
+
const WORD_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "word" });
|
|
38
|
+
// A break needs a glyph on both sides of it: with none before, nothing breaks away
|
|
39
|
+
// or takes a hyphen, and with none after, the word was already over.
|
|
40
|
+
const UNDRAWN = "\\s\\p{Cf}\\p{Mn}";
|
|
41
|
+
const OPENING = pattern(new RegExp(`^[${UNDRAWN}]*`, "u"));
|
|
42
|
+
const THROUGH_LAST_GLYPH = pattern(new RegExp(`^[^]*[^${UNDRAWN}]`, "u"));
|
|
23
43
|
/**
|
|
24
44
|
* Each word of `text` in order, where a "word" carries its own trailing
|
|
25
45
|
* whitespace — the run of spaces that follows it belongs to it, because that
|
|
@@ -34,7 +54,9 @@ function* words(text) {
|
|
|
34
54
|
// Constructed per call rather than hoisted: a sticky regex carries its
|
|
35
55
|
// `lastIndex` as mutable state, and a shared one would corrupt any two
|
|
36
56
|
// interleaved walks. [LAW:no-shared-mutable-globals]
|
|
37
|
-
|
|
57
|
+
// U+FEFF is outside it, as it is outside Python's `\s` and Rich's words: it is a
|
|
58
|
+
// no-break space that JavaScript's `\s` counts as a space.
|
|
59
|
+
const re = /[^\S\ufeff]*[\S\ufeff]+[^\S\ufeff]*/y;
|
|
38
60
|
for (let match = re.exec(text); match !== null; match = re.exec(text)) {
|
|
39
61
|
yield match[0];
|
|
40
62
|
}
|
|
@@ -47,12 +69,46 @@ function* words(text) {
|
|
|
47
69
|
function widthOf(word) {
|
|
48
70
|
return cellLen(word.trimEnd());
|
|
49
71
|
}
|
|
72
|
+
/** The offsets inside `word`, ascending, that it may break at. */
|
|
73
|
+
function breaksIn(word) {
|
|
74
|
+
if (word.match(BREAKABLE) === null)
|
|
75
|
+
return [];
|
|
76
|
+
const found = new Set();
|
|
77
|
+
// One break to a run of marks: a second would open a piece with nothing in it.
|
|
78
|
+
for (const { 0: mark, index } of word.matchAll(new RegExp(`(?<![${SOFT_HYPHEN}${ZWSP}])[${SOFT_HYPHEN}${ZWSP}]`, "gu")))
|
|
79
|
+
found.add(index + mark.length);
|
|
80
|
+
for (const { 0: run, index } of word.matchAll(new RegExp(UNSPACED, "gu"))) {
|
|
81
|
+
// A run's start is where the segmenter's input began, not a boundary it found:
|
|
82
|
+
// UAX #14 has these scripts break as letters do against what comes before them.
|
|
83
|
+
for (const { index: at } of WORD_SEGMENTER.segment(run))
|
|
84
|
+
if (at > 0)
|
|
85
|
+
found.add(index + at);
|
|
86
|
+
}
|
|
87
|
+
const start = OPENING.exec(word)[0].length;
|
|
88
|
+
const end = THROUGH_LAST_GLYPH.exec(word)?.[0].length ?? 0;
|
|
89
|
+
return [...found].filter((at) => at > start && at < end).sort((a, b) => a - b);
|
|
90
|
+
}
|
|
91
|
+
/** `text` as what wraps: its words, tiled further wherever one may break. */
|
|
92
|
+
function* piecesOf(text) {
|
|
93
|
+
for (const word of words(text)) {
|
|
94
|
+
let start = 0;
|
|
95
|
+
for (const at of breaksIn(word)) {
|
|
96
|
+
const piece = word.slice(start, at);
|
|
97
|
+
const cells = widthOf(piece);
|
|
98
|
+
yield { text: piece, cells, hyphenates: cells > 0 && piece.endsWith(SOFT_HYPHEN) };
|
|
99
|
+
start = at;
|
|
100
|
+
}
|
|
101
|
+
const last = word.slice(start);
|
|
102
|
+
yield { text: last, cells: widthOf(last), hyphenates: false };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
50
105
|
/**
|
|
51
|
-
* The cells each
|
|
52
|
-
* width `divideLine` can wrap it to without cutting a word.
|
|
106
|
+
* The cells each piece of `text` needs, in order — the widest is the narrowest
|
|
107
|
+
* width `divideLine` can wrap it to without cutting a word. A hyphen is not in
|
|
108
|
+
* them: where it does not fit, the break goes without it.
|
|
53
109
|
*/
|
|
54
110
|
export function wordWidths(text) {
|
|
55
|
-
return Array.from(
|
|
111
|
+
return Array.from(piecesOf(text), (piece) => piece.cells);
|
|
56
112
|
}
|
|
57
113
|
/**
|
|
58
114
|
* A word chopped into pieces of at most `width` cells, as the reference chops
|
|
@@ -78,8 +134,8 @@ function foldWord(word, width) {
|
|
|
78
134
|
return cellLen(pieces[0]) > width ? ["", ...pieces] : pieces;
|
|
79
135
|
}
|
|
80
136
|
/**
|
|
81
|
-
* The
|
|
82
|
-
* `
|
|
137
|
+
* The breaks that wrap `text` into lines of at most `width` cells, for
|
|
138
|
+
* `Segment.divide` to apply to the styled line.
|
|
83
139
|
*
|
|
84
140
|
* `fold` decides what happens to a word that fits on no line at all: folded,
|
|
85
141
|
* it is chopped across as many lines as it needs; unfolded, it is left whole
|
|
@@ -88,6 +144,10 @@ function foldWord(word, width) {
|
|
|
88
144
|
* can truncate it, which is what makes an unbreakable word ellipsize while the
|
|
89
145
|
* sentence around it merely wraps.
|
|
90
146
|
*
|
|
147
|
+
* A piece that ends in a soft hyphen stays on a line only with room for the
|
|
148
|
+
* hyphen, so a line it closes holds it, and a word that fits whole needs none. A
|
|
149
|
+
* piece as wide as the line breaks with no hyphen rather than lose a letter to one.
|
|
150
|
+
*
|
|
91
151
|
* Requires `width >= 1`. A zero-cell canvas gets no cuts, because "no cut
|
|
92
152
|
* fits" and "no cell fits" are different facts and only one of them is a list
|
|
93
153
|
* of offsets — the caller answers the second one by cropping the single
|
|
@@ -96,19 +156,22 @@ function foldWord(word, width) {
|
|
|
96
156
|
export function divideLine(text, width, options) {
|
|
97
157
|
if (width <= 0)
|
|
98
158
|
return [];
|
|
99
|
-
const
|
|
159
|
+
const breaks = [];
|
|
100
160
|
// A cut at the start of the text is not a cut: it would open the line with
|
|
101
161
|
// an empty piece. [LAW:single-enforcer] for that rule, so neither arm below
|
|
102
162
|
// has to restate it.
|
|
103
163
|
const cutAt = (cell) => {
|
|
104
164
|
if (cell > 0)
|
|
105
|
-
|
|
165
|
+
breaks.push({ cell: asCellCol(cell), hyphen: width - used > 0 ? softHyphen : undefined });
|
|
106
166
|
};
|
|
107
167
|
let used = 0; // cells already committed to the line being filled
|
|
108
168
|
let wordStart = 0; // absolute cell offset where the current word begins
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
169
|
+
let offset = 0; // code units before the current word
|
|
170
|
+
let softHyphen; // the one the line being filled ends in
|
|
171
|
+
for (const piece of piecesOf(text)) {
|
|
172
|
+
const word = piece.text;
|
|
173
|
+
const wordWidth = piece.cells;
|
|
174
|
+
if (width - used >= wordWidth + (piece.hyphenates ? 1 : 0)) {
|
|
112
175
|
used += cellLen(word);
|
|
113
176
|
}
|
|
114
177
|
else if (options.fold && wordWidth > width) {
|
|
@@ -119,13 +182,14 @@ export function divideLine(text, width, options) {
|
|
|
119
182
|
let pieceStart = wordStart;
|
|
120
183
|
for (let index = 0; index < pieces.length; index += 1) {
|
|
121
184
|
cutAt(pieceStart);
|
|
122
|
-
|
|
185
|
+
softHyphen = undefined;
|
|
186
|
+
const fold = pieces[index];
|
|
123
187
|
// The last piece is what the *next* word finds already on the line;
|
|
124
188
|
// the rest are whole lines of their own.
|
|
125
189
|
if (index === pieces.length - 1)
|
|
126
|
-
used = cellLen(
|
|
190
|
+
used = cellLen(fold);
|
|
127
191
|
else
|
|
128
|
-
pieceStart += cellLen(
|
|
192
|
+
pieceStart += cellLen(fold);
|
|
129
193
|
}
|
|
130
194
|
}
|
|
131
195
|
else {
|
|
@@ -135,6 +199,8 @@ export function divideLine(text, width, options) {
|
|
|
135
199
|
used = cellLen(word);
|
|
136
200
|
}
|
|
137
201
|
wordStart += cellLen(word);
|
|
202
|
+
offset += word.length;
|
|
203
|
+
softHyphen = piece.hyphenates ? asCodePoint(offset - 1) : undefined;
|
|
138
204
|
}
|
|
139
|
-
return
|
|
205
|
+
return breaks;
|
|
140
206
|
}
|
package/dist/host/app.d.ts
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* — a focused field in a `WidgetApp` included — can swallow them.
|
|
32
32
|
*/
|
|
33
33
|
import { type Clock, type FrameRate } from "../core/clock.js";
|
|
34
|
+
import { type TerminalColors } from "../core/color.js";
|
|
34
35
|
import { type ConsoleOptions } from "../core/console.js";
|
|
35
36
|
import { Segment } from "../core/segment.js";
|
|
36
37
|
import { type Surface } from "../core/paint.js";
|
|
@@ -38,7 +39,7 @@ import { type HostFrame, type FrameHost } from "../core/protocol.js";
|
|
|
38
39
|
import type { Unsubscribe } from "../core/subscription.js";
|
|
39
40
|
import { Timeline } from "../core/timeline.js";
|
|
40
41
|
import { type View } from "../core/view.js";
|
|
41
|
-
import { type
|
|
42
|
+
import { type KeyEvent, type TerminalMouseEvent, type TerminalReply } from "./input.js";
|
|
42
43
|
import type { TerminalHost } from "./terminal-host.js";
|
|
43
44
|
/**
|
|
44
45
|
* Where an app is in its life. `idle` has not run; `running` holds the
|
|
@@ -46,7 +47,7 @@ import type { TerminalHost } from "./terminal-host.js";
|
|
|
46
47
|
* the program; `stopped` has handed it back for good.
|
|
47
48
|
*/
|
|
48
49
|
export type AppPhase = "idle" | "running" | "suspended" | "stopped";
|
|
49
|
-
declare const DRAW_OPTIONS: readonly ["asciiOnly", "reducedMotion", "theme", "onStyleError", "markup", "highlight", "highlighter"];
|
|
50
|
+
declare const DRAW_OPTIONS: readonly ["asciiOnly", "reducedMotion", "textOrder", "terminal", "theme", "onStyleError", "markup", "highlight", "highlighter"];
|
|
50
51
|
type DrawOption = (typeof DRAW_OPTIONS)[number];
|
|
51
52
|
type DrawOptions = Readonly<Pick<ConsoleOptions, DrawOption>>;
|
|
52
53
|
export interface AppOptions extends DrawOptions {
|
|
@@ -88,7 +89,7 @@ export interface AppOptions extends DrawOptions {
|
|
|
88
89
|
* A turn of the wheel, where the pointer was when it turned: `lines` is
|
|
89
90
|
* positive towards the end of the content, a line a notch.
|
|
90
91
|
*/
|
|
91
|
-
export interface
|
|
92
|
+
export interface WheelEvent {
|
|
92
93
|
readonly x: number;
|
|
93
94
|
readonly y: number;
|
|
94
95
|
readonly lines: number;
|
|
@@ -97,11 +98,26 @@ declare const WHEEL_LINES: {
|
|
|
97
98
|
readonly scroll_up: -1;
|
|
98
99
|
readonly scroll_down: 1;
|
|
99
100
|
};
|
|
100
|
-
type WheelTurn = Extract<
|
|
101
|
+
type WheelTurn = Extract<TerminalMouseEvent, {
|
|
101
102
|
type: keyof typeof WHEEL_LINES;
|
|
102
103
|
}>;
|
|
103
104
|
/** A pointer event `onMouse` hears: every one but the vertical wheel, which is `onWheel`'s. */
|
|
104
|
-
export type AppMouseEvent = Exclude<
|
|
105
|
+
export type AppMouseEvent = Exclude<TerminalMouseEvent, WheelTurn>;
|
|
106
|
+
/**
|
|
107
|
+
* How an ask ended, with the replies heard before it did. `answered`: the
|
|
108
|
+
* terminal answered DA1, so a query with no reply among `replies` is one it
|
|
109
|
+
* does not support. `timeout`: it answered nothing in time, DA1 included.
|
|
110
|
+
* `left`: the app handed the terminal back first. `unasked`: the host writes
|
|
111
|
+
* to no terminal, so nothing was asked.
|
|
112
|
+
*/
|
|
113
|
+
export interface Asked {
|
|
114
|
+
readonly ended: "answered" | "timeout" | "left" | "unasked";
|
|
115
|
+
readonly replies: readonly TerminalReply[];
|
|
116
|
+
}
|
|
117
|
+
export interface AskOptions {
|
|
118
|
+
/** Seconds to wait for a terminal that answers nothing. Half a second, unless given. */
|
|
119
|
+
readonly timeout?: number;
|
|
120
|
+
}
|
|
105
121
|
export declare class App implements FrameHost {
|
|
106
122
|
private readonly host;
|
|
107
123
|
private readonly surface;
|
|
@@ -113,7 +129,11 @@ export declare class App implements FrameHost {
|
|
|
113
129
|
private ticking;
|
|
114
130
|
private said;
|
|
115
131
|
private waking;
|
|
116
|
-
private
|
|
132
|
+
private console;
|
|
133
|
+
private drawing;
|
|
134
|
+
private asking;
|
|
135
|
+
private asked;
|
|
136
|
+
private owedDa1;
|
|
117
137
|
private _phase;
|
|
118
138
|
private _frame;
|
|
119
139
|
private readonly painter;
|
|
@@ -127,6 +147,26 @@ export declare class App implements FrameHost {
|
|
|
127
147
|
private readonly input;
|
|
128
148
|
private settle;
|
|
129
149
|
constructor(options: AppOptions);
|
|
150
|
+
/**
|
|
151
|
+
* The terminal the app draws on: the colors a cell that sets none is drawn
|
|
152
|
+
* in, and the sixteen an ANSI color names — as given, or as the color
|
|
153
|
+
* depth assumes (`resolveTerminal`).
|
|
154
|
+
*/
|
|
155
|
+
get terminal(): TerminalColors;
|
|
156
|
+
/** Draw on `terminal` from the next frame on — what `terminalColors` learned, say. */
|
|
157
|
+
set terminal(terminal: TerminalColors);
|
|
158
|
+
/**
|
|
159
|
+
* Ask the terminal: write `queries`, then DA1, and resolve with the replies
|
|
160
|
+
* heard before DA1's, or before the timeout or the app handing the terminal
|
|
161
|
+
* back. It rejects only when writing to the terminal throws. Asks run one
|
|
162
|
+
* after another, and only while the app runs: it holds raw mode and reads
|
|
163
|
+
* the input, so a reply reaches the ask and never a key handler. A reply no
|
|
164
|
+
* ask is waiting for is dropped. `queries` must not ask for DA1 itself: its
|
|
165
|
+
* reply would end the ask early.
|
|
166
|
+
*/
|
|
167
|
+
ask(queries: readonly string[], options?: AskOptions): Promise<Asked>;
|
|
168
|
+
private drawingConsole;
|
|
169
|
+
private askNow;
|
|
130
170
|
get phase(): AppPhase;
|
|
131
171
|
/** The clock the app reads the time from: where the host time a `Timeline` operation takes is read. */
|
|
132
172
|
get clock(): Clock;
|
|
@@ -186,10 +226,10 @@ export declare class App implements FrameHost {
|
|
|
186
226
|
*/
|
|
187
227
|
onPaint(handler: (frame: readonly (readonly Segment[])[]) => void): Unsubscribe;
|
|
188
228
|
/**
|
|
189
|
-
* Hear every key the app does not quit on, by the `
|
|
229
|
+
* Hear every key the app does not quit on, by the `KeyEvent` the terminal
|
|
190
230
|
* reported; `keyName` names it. A handler that throws fails the app.
|
|
191
231
|
*/
|
|
192
|
-
onKey(handler: (press:
|
|
232
|
+
onKey(handler: (press: KeyEvent) => void): Unsubscribe;
|
|
193
233
|
/**
|
|
194
234
|
* Hear every pointer event but the vertical wheel, which is `onWheel`'s,
|
|
195
235
|
* in terminal cells. Only the alternate surface reports the pointer. A
|
|
@@ -204,7 +244,7 @@ export declare class App implements FrameHost {
|
|
|
204
244
|
* and the first that moves something takes it. A handler that throws fails
|
|
205
245
|
* the app.
|
|
206
246
|
*/
|
|
207
|
-
onWheel(handler: (wheel:
|
|
247
|
+
onWheel(handler: (wheel: WheelEvent) => boolean): Unsubscribe;
|
|
208
248
|
/** Hand the terminal back for good; `run` resolves. */
|
|
209
249
|
stop(): void;
|
|
210
250
|
/**
|
|
@@ -217,6 +257,7 @@ export declare class App implements FrameHost {
|
|
|
217
257
|
private enter;
|
|
218
258
|
private leave;
|
|
219
259
|
private end;
|
|
260
|
+
private reply;
|
|
220
261
|
private key;
|
|
221
262
|
private mouse;
|
|
222
263
|
private hear;
|