@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.
Files changed (185) hide show
  1. package/README.md +7 -6
  2. package/THIRD-PARTY-NOTICES +42 -0
  3. package/dist/core/animation.d.ts +65 -46
  4. package/dist/core/animation.js +91 -67
  5. package/dist/core/ansi.d.ts +19 -12
  6. package/dist/core/ansi.js +114 -36
  7. package/dist/core/bidi-data.d.ts +14 -0
  8. package/dist/core/bidi-data.js +385 -0
  9. package/dist/core/bidi.d.ts +89 -0
  10. package/dist/core/bidi.js +615 -0
  11. package/dist/core/box.d.ts +9 -1
  12. package/dist/core/box.js +11 -0
  13. package/dist/core/cell-widths.d.ts +3 -2
  14. package/dist/core/cell-widths.js +93 -93
  15. package/dist/core/cells.d.ts +20 -0
  16. package/dist/core/cells.js +94 -6
  17. package/dist/core/clock.d.ts +1 -1
  18. package/dist/core/color.d.ts +56 -57
  19. package/dist/core/color.js +79 -81
  20. package/dist/core/console.d.ts +24 -7
  21. package/dist/core/console.js +27 -8
  22. package/dist/core/control-codes.d.ts +61 -0
  23. package/dist/core/control-codes.js +157 -0
  24. package/dist/core/easing.d.ts +40 -40
  25. package/dist/core/easing.js +40 -40
  26. package/dist/core/env.d.ts +6 -0
  27. package/dist/core/env.js +9 -1
  28. package/dist/core/export-html.js +5 -4
  29. package/dist/core/export-lines.d.ts +13 -11
  30. package/dist/core/export-lines.js +19 -15
  31. package/dist/core/export-svg.d.ts +3 -3
  32. package/dist/core/export-svg.js +10 -9
  33. package/dist/core/json.js +9 -2
  34. package/dist/core/markup.js +5 -4
  35. package/dist/core/memo.d.ts +1 -1
  36. package/dist/core/memo.js +1 -1
  37. package/dist/core/oklch.d.ts +14 -14
  38. package/dist/core/oklch.js +10 -10
  39. package/dist/core/orient.d.ts +68 -0
  40. package/dist/core/orient.js +224 -0
  41. package/dist/core/osc8.d.ts +12 -5
  42. package/dist/core/osc8.js +14 -7
  43. package/dist/core/paint.d.ts +9 -9
  44. package/dist/core/paint.js +3 -3
  45. package/dist/core/pretty.d.ts +1 -1
  46. package/dist/core/pretty.js +23 -19
  47. package/dist/core/protocol.d.ts +47 -26
  48. package/dist/core/protocol.js +31 -23
  49. package/dist/core/render.d.ts +19 -14
  50. package/dist/core/render.js +30 -17
  51. package/dist/core/seal.d.ts +26 -0
  52. package/dist/core/seal.js +75 -0
  53. package/dist/core/segment.d.ts +12 -4
  54. package/dist/core/segment.js +16 -6
  55. package/dist/core/strip.d.ts +83 -83
  56. package/dist/core/strip.js +70 -70
  57. package/dist/core/style.d.ts +46 -18
  58. package/dist/core/style.js +92 -56
  59. package/dist/core/text-order.d.ts +51 -0
  60. package/dist/core/text-order.js +50 -0
  61. package/dist/core/text.d.ts +12 -2
  62. package/dist/core/text.js +136 -90
  63. package/dist/core/timeline.d.ts +5 -5
  64. package/dist/core/timeline.js +11 -11
  65. package/dist/core/tween.d.ts +6 -6
  66. package/dist/core/tween.js +10 -10
  67. package/dist/core/view.d.ts +5 -5
  68. package/dist/core/view.js +15 -14
  69. package/dist/core/wrap.d.ts +24 -6
  70. package/dist/core/wrap.js +82 -16
  71. package/dist/host/app.d.ts +50 -9
  72. package/dist/host/app.js +120 -15
  73. package/dist/host/host-environment.d.ts +2 -2
  74. package/dist/host/host-environment.js +2 -2
  75. package/dist/host/index.d.ts +3 -2
  76. package/dist/host/index.js +1 -0
  77. package/dist/host/input.d.ts +42 -9
  78. package/dist/host/input.js +91 -7
  79. package/dist/host/terminal-colors.d.ts +18 -0
  80. package/dist/host/terminal-colors.js +46 -0
  81. package/dist/host/terminal-host.d.ts +4 -3
  82. package/dist/host/terminal-host.js +1 -1
  83. package/dist/index.d.ts +17 -16
  84. package/dist/index.js +11 -11
  85. package/dist/node/prompt.d.ts +1 -1
  86. package/dist/node/prompt.js +1 -1
  87. package/dist/node/terminal-host.d.ts +1 -1
  88. package/dist/node/traceback.js +2 -2
  89. package/dist/renderables/change.d.ts +1 -3
  90. package/dist/renderables/change.js +6 -8
  91. package/dist/renderables/effect.d.ts +88 -87
  92. package/dist/renderables/effect.js +124 -114
  93. package/dist/renderables/effects.d.ts +105 -76
  94. package/dist/renderables/effects.js +309 -176
  95. package/dist/renderables/embed.d.ts +1 -1
  96. package/dist/renderables/embed.js +2 -2
  97. package/dist/renderables/flexStrip.d.ts +7 -7
  98. package/dist/renderables/flexStrip.js +15 -15
  99. package/dist/renderables/frames.d.ts +7 -16
  100. package/dist/renderables/frames.js +12 -27
  101. package/dist/renderables/glyph-effect.d.ts +22 -23
  102. package/dist/renderables/glyph-effect.js +43 -41
  103. package/dist/renderables/glyph-effects.d.ts +33 -14
  104. package/dist/renderables/glyph-effects.js +90 -22
  105. package/dist/renderables/html-entities.d.ts +1 -1
  106. package/dist/renderables/html-entities.js +1 -1
  107. package/dist/renderables/lifecycle.d.ts +8 -10
  108. package/dist/renderables/lifecycle.js +17 -18
  109. package/dist/renderables/live.d.ts +2 -2
  110. package/dist/renderables/live.js +7 -7
  111. package/dist/renderables/markdown-parse.js +3 -3
  112. package/dist/renderables/markdown.js +4 -2
  113. package/dist/renderables/panel.js +7 -5
  114. package/dist/renderables/progress.d.ts +1 -1
  115. package/dist/renderables/progress.js +1 -1
  116. package/dist/renderables/progressBar.d.ts +3 -3
  117. package/dist/renderables/progressBar.js +21 -18
  118. package/dist/renderables/prompt.d.ts +1 -1
  119. package/dist/renderables/scroll.d.ts +1 -1
  120. package/dist/renderables/scroll.js +3 -3
  121. package/dist/renderables/spinner.d.ts +4 -4
  122. package/dist/renderables/spinner.js +10 -10
  123. package/dist/renderables/syntax.d.ts +2 -2
  124. package/dist/renderables/syntax.js +9 -4
  125. package/dist/renderables/table.js +5 -4
  126. package/dist/renderables/traceback.d.ts +1 -1
  127. package/dist/renderables/traceback.js +1 -1
  128. package/dist/renderables/tree.js +3 -3
  129. package/dist/template-bindings/animation-funcs.d.ts +60 -29
  130. package/dist/template-bindings/animation-funcs.js +126 -43
  131. package/dist/template-bindings/color-funcs.d.ts +14 -14
  132. package/dist/template-bindings/color-funcs.js +25 -25
  133. package/dist/template-bindings/index.d.ts +16 -14
  134. package/dist/template-bindings/index.js +17 -14
  135. package/dist/template-bindings/palette-funcs.d.ts +2 -2
  136. package/dist/template-bindings/palette-funcs.js +4 -4
  137. package/dist/template-bindings/style-funcs.d.ts +5 -5
  138. package/dist/template-bindings/style-funcs.js +16 -16
  139. package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
  140. package/dist/themes/{transpose.js → adjust.js} +28 -28
  141. package/dist/themes/buildPalette.d.ts +6 -5
  142. package/dist/themes/buildPalette.js +22 -22
  143. package/dist/themes/colorMath.d.ts +16 -16
  144. package/dist/themes/colorMath.js +32 -32
  145. package/dist/themes/data/ansi-tables.d.ts +2 -2
  146. package/dist/themes/data/ansi-tables.js +2 -2
  147. package/dist/themes/data/index.js +1 -4
  148. package/dist/themes/data/textual-ansi.js +0 -143
  149. package/dist/themes/data/types.d.ts +4 -4
  150. package/dist/themes/data/types.js +2 -2
  151. package/dist/themes/palette.d.ts +12 -12
  152. package/dist/themes/palette.js +16 -16
  153. package/dist/themes/ramp.d.ts +17 -17
  154. package/dist/themes/ramp.js +12 -12
  155. package/dist/themes/registry.d.ts +6 -8
  156. package/dist/themes/registry.js +6 -8
  157. package/dist/themes/terminalThemes.d.ts +4 -11
  158. package/dist/themes/terminalThemes.js +31 -47
  159. package/dist/widgets/button.d.ts +3 -3
  160. package/dist/widgets/button.js +8 -8
  161. package/dist/widgets/checkbox.d.ts +2 -2
  162. package/dist/widgets/checkbox.js +4 -4
  163. package/dist/widgets/dropdown.d.ts +3 -3
  164. package/dist/widgets/dropdown.js +7 -5
  165. package/dist/widgets/event-router.d.ts +6 -6
  166. package/dist/widgets/event-router.js +2 -2
  167. package/dist/widgets/focus-manager.d.ts +3 -3
  168. package/dist/widgets/focus-manager.js +3 -3
  169. package/dist/widgets/index.d.ts +2 -2
  170. package/dist/widgets/index.js +1 -1
  171. package/dist/widgets/ink.d.ts +3 -3
  172. package/dist/widgets/ink.js +4 -4
  173. package/dist/widgets/slider.d.ts +2 -2
  174. package/dist/widgets/static-item.d.ts +2 -2
  175. package/dist/widgets/text-input.d.ts +2 -2
  176. package/dist/widgets/text-input.js +5 -5
  177. package/dist/widgets/themed-widget.js +4 -4
  178. package/dist/widgets/toggle.d.ts +4 -4
  179. package/dist/widgets/toggle.js +6 -6
  180. package/dist/widgets/types.d.ts +6 -6
  181. package/dist/widgets/types.js +2 -2
  182. package/dist/widgets/widget-app.d.ts +10 -10
  183. package/dist/widgets/widget-app.js +8 -8
  184. package/dist/widgets/widget-base.d.ts +2 -2
  185. package/package.json +5 -4
@@ -0,0 +1,615 @@
1
+ /**
2
+ * The Unicode Bidirectional Algorithm (UAX #9): the embedding level of every
3
+ * character of a paragraph, and the visual order of a line of them.
4
+ *
5
+ * Levels are resolved over a whole paragraph and reordered a line at a time,
6
+ * because rules L1 and L2 run after line breaking (UAX #9 section 3.4). So a
7
+ * caller resolves first, cuts however it cuts, and asks for each piece's order
8
+ * with the levels it took along. Rule numbers below are the standard's.
9
+ *
10
+ * Levels index UTF-16 code units, as every offset into a JavaScript string does;
11
+ * both units of a surrogate pair carry one level. `REMOVED` is rule X9: a
12
+ * character the algorithm takes out of the text, which has no level.
13
+ */
14
+ import { BIDI_CLASSES, BRACKET_PAIRS, CLASS_RANGES, JOINING_RANGES, JOINING_TYPES, MIRRORS, PRESENTATION_FORMS } from "./bidi-data.js";
15
+ import { replaceZeroWidth } from "./cells.js";
16
+ import { pattern } from "./pattern.js";
17
+ /** The level of a character rule X9 removes. */
18
+ export const REMOVED = -1;
19
+ /** BD2: the deepest embedding the algorithm represents. */
20
+ const MAX_DEPTH = 125;
21
+ /** BD16: the bracket pairs one isolating run sequence may hold open. */
22
+ const MAX_OPEN_BRACKETS = 63;
23
+ /** The value `ranges`, ascending and disjoint, give `codePoint`. */
24
+ function inRanges(ranges, codePoint) {
25
+ let low = 0;
26
+ let high = ranges.length - 1;
27
+ while (low <= high) {
28
+ const mid = (low + high) >> 1;
29
+ const [first, last, index] = ranges[mid];
30
+ if (codePoint < first)
31
+ high = mid - 1;
32
+ else if (codePoint > last)
33
+ low = mid + 1;
34
+ else
35
+ return index;
36
+ }
37
+ return undefined;
38
+ }
39
+ function classOf(codePoint) {
40
+ const index = inRanges(CLASS_RANGES, codePoint);
41
+ return index === undefined ? "L" : BIDI_CLASSES[index];
42
+ }
43
+ const CLOSING_OF = new Map(BRACKET_PAIRS);
44
+ const OPENING_OF = new Map(BRACKET_PAIRS.map(([open, close]) => [close, open]));
45
+ /** BD16: the angle brackets U+2329 and U+232A are canonically U+3008 and U+3009. */
46
+ const CANONICAL_BRACKET = new Map([[0x2329, 0x3008], [0x232a, 0x3009]]);
47
+ const MIRROR_OF = new Map(MIRRORS);
48
+ /** `codePoint`'s Bidi_Mirroring_Glyph, or itself. */
49
+ export function mirrored(codePoint) {
50
+ return MIRROR_OF.get(codePoint) ?? codePoint;
51
+ }
52
+ const hex = (codePoint) => `\\u{${codePoint.toString(16)}}`;
53
+ /**
54
+ * What can raise a level in a left-to-right paragraph or that the algorithm
55
+ * works on: R, AL, AN and the explicit formatting characters, closers included.
56
+ * A line holding none is level 0 throughout, which is every line most programs
57
+ * ever draw, so one match answers for it.
58
+ */
59
+ const ACTIVE_CLASSES = new Set(["R", "AL", "AN", "LRE", "RLE", "LRO", "RLO", "PDF", "LRI", "RLI", "FSI", "PDI"]);
60
+ const ACTIVE = pattern(new RegExp(`[${CLASS_RANGES.filter(([, , index]) => ACTIVE_CLASSES.has(BIDI_CLASSES[index]))
61
+ .map(([first, last]) => `${hex(first)}-${hex(last)}`)
62
+ .join("")}]`, "u"));
63
+ // LRE, RLE, PDF, LRO, RLO, LRI, RLI, FSI, PDI.
64
+ export const EXPLICIT = pattern(/[\u202a-\u202e\u2066-\u2069]/);
65
+ const MARKS = pattern(/[\u200e\u200f\u061c]/);
66
+ /** `text` with every explicit formatting character gone. */
67
+ export function withoutExplicit(text) {
68
+ return text.match(EXPLICIT) === null ? text : text.replace(new RegExp(EXPLICIT.source, "g"), "");
69
+ }
70
+ /** `text` as data on show: what the wire would drop, and every explicit control, spelled by `escape`. */
71
+ export function escaped(text, escape) {
72
+ return replaceZeroWidth(text, escape).replace(new RegExp(EXPLICIT.source, "gu"), escape);
73
+ }
74
+ /** `text` with LRM, RLM and ALM gone. */
75
+ export function withoutMarks(text) {
76
+ return text.match(MARKS) === null ? text : text.replace(new RegExp(MARKS.source, "g"), "");
77
+ }
78
+ const isIsolateOpener = (opener) => isIsolateInitiator(classOf(opener.codePointAt(0)));
79
+ /** `openers` in force, as a row's start states them. */
80
+ export function opened(openers = "") {
81
+ const open = { stack: [], isolates: 0 };
82
+ for (const character of openers)
83
+ advance(open, character);
84
+ return open;
85
+ }
86
+ /**
87
+ * `character` applied to `open`, the openers in force, outermost first: an
88
+ * opener joins them, a closer takes what it closes, and a line feed ends them
89
+ * all, as X8 does at a paragraph's end. A line is the paragraph here, so U+2029
90
+ * ends nothing: `paragraph` resolves it inside the line that holds it. False for
91
+ * a closer that matches nothing, which closes nothing: PDF closes an embedding
92
+ * opened since the innermost isolate, and PDI the innermost isolate with every
93
+ * embedding inside it.
94
+ */
95
+ export function advance(open, character) {
96
+ const { stack } = open;
97
+ const type = classOf(character.codePointAt(0));
98
+ if (character === "\n") {
99
+ stack.length = 0;
100
+ open.isolates = 0;
101
+ }
102
+ else if (type === "LRE" || type === "RLE" || type === "LRO" || type === "RLO")
103
+ stack.push(character);
104
+ else if (isIsolateInitiator(type)) {
105
+ stack.push(character);
106
+ open.isolates += 1;
107
+ }
108
+ else if (type === "PDF") {
109
+ if (stack.length === 0 || isIsolateOpener(stack.at(-1)))
110
+ return false;
111
+ stack.pop();
112
+ }
113
+ else if (type === "PDI") {
114
+ if (open.isolates === 0)
115
+ return false;
116
+ while (!isIsolateOpener(stack.pop()))
117
+ ;
118
+ open.isolates -= 1;
119
+ }
120
+ return true;
121
+ }
122
+ /** The openers `text` leaves open at its end, outermost first. */
123
+ export function openersOf(text) {
124
+ const open = opened();
125
+ for (const character of text)
126
+ advance(open, character);
127
+ return open.stack.join("");
128
+ }
129
+ /**
130
+ * What reopens `text`'s state at an offset: read forwards, since rows ask in
131
+ * order, and from the start again for one behind. An opener past `MAX_DEPTH` is
132
+ * one the algorithm ignores, so no row repeats it, or a hostile line's every row
133
+ * would carry them all. An explicit control is one code unit, so offsets need no
134
+ * care for surrogates.
135
+ */
136
+ export function openersAlong(text) {
137
+ let open = opened();
138
+ let read = 0;
139
+ return (offset) => {
140
+ if (offset < read) {
141
+ open = opened();
142
+ read = 0;
143
+ }
144
+ for (; read < offset; read += 1)
145
+ advance(open, text[read]);
146
+ return open.stack.slice(0, MAX_DEPTH).join("");
147
+ };
148
+ }
149
+ /** What closes `openers`, innermost first. */
150
+ export function closersOf(openers) {
151
+ return Array.from(openers, (opener) => (isIsolateOpener(opener) ? "\u2069" : "\u202c"))
152
+ .reverse()
153
+ .join("");
154
+ }
155
+ /** What closes the direction state `content` leaves open. */
156
+ export function closing(content) {
157
+ return isBidi(content) ? closersOf(openersOf(content)) : "";
158
+ }
159
+ /** `content` with the direction state it leaves open closed: separate content starts clean after it. */
160
+ export function closed(content) {
161
+ return content + closing(content);
162
+ }
163
+ /** Whether `text` needs the algorithm at all. */
164
+ export function isBidi(text) {
165
+ return text.match(ACTIVE) !== null;
166
+ }
167
+ const isIsolateInitiator = (type) => type === "LRI" || type === "RLI" || type === "FSI";
168
+ const isRemoved = (type) => type === "LRE" || type === "RLE" || type === "LRO" || type === "RLO" || type === "PDF" || type === "BN";
169
+ const isNeutral = (type) => type === "B" || type === "S" || type === "WS" || type === "ON" || type === "FSI" || type === "LRI" || type === "RLI" || type === "PDI";
170
+ /** BD9: each isolate initiator's matching PDI, and each matched PDI's initiator. */
171
+ function matchIsolates(types) {
172
+ const pdiOf = new Map();
173
+ const initiatorOf = new Map();
174
+ const open = [];
175
+ types.forEach((type, index) => {
176
+ if (isIsolateInitiator(type))
177
+ open.push(index);
178
+ else if (type === "PDI" && open.length > 0) {
179
+ const initiator = open.pop();
180
+ pdiOf.set(initiator, index);
181
+ initiatorOf.set(index, initiator);
182
+ }
183
+ });
184
+ return { pdiOf, initiatorOf };
185
+ }
186
+ /** P2 and P3: the level of the first strong character outside any isolate in `from`..`to`, 0 if none. */
187
+ function firstStrongLevel(types, pdiOf, from, to) {
188
+ for (let index = from; index < to; index += 1) {
189
+ const type = types[index];
190
+ if (type === "L")
191
+ return 0;
192
+ if (type === "R" || type === "AL")
193
+ return 1;
194
+ if (isIsolateInitiator(type)) {
195
+ const pdi = pdiOf.get(index);
196
+ if (pdi === undefined)
197
+ return 0;
198
+ index = pdi;
199
+ }
200
+ }
201
+ return 0;
202
+ }
203
+ /** X1 to X8: the explicit level of each character, with overrides applied to `types`. */
204
+ function resolveExplicit(types, paragraphLevel, pdiOf, initiatorOf) {
205
+ const levels = new Array(types.length).fill(paragraphLevel);
206
+ const stack = [{ level: paragraphLevel, override: undefined, isolate: false }];
207
+ let overflowIsolates = 0;
208
+ let overflowEmbeddings = 0;
209
+ let validIsolates = 0;
210
+ const top = () => stack[stack.length - 1];
211
+ const nextOdd = (level) => (level + 1) | 1;
212
+ const nextEven = (level) => (level + 2) & ~1;
213
+ const stamp = (index) => {
214
+ levels[index] = top().level;
215
+ if (top().override !== undefined)
216
+ types[index] = top().override;
217
+ };
218
+ types.forEach((type, index) => {
219
+ switch (type) {
220
+ case "RLE":
221
+ case "LRE":
222
+ case "RLO":
223
+ case "LRO": {
224
+ levels[index] = top().level;
225
+ const level = type === "RLE" || type === "RLO" ? nextOdd(top().level) : nextEven(top().level);
226
+ if (level <= MAX_DEPTH && overflowIsolates === 0 && overflowEmbeddings === 0) {
227
+ const override = type === "RLO" ? "R" : type === "LRO" ? "L" : undefined;
228
+ stack.push({ level, override, isolate: false });
229
+ }
230
+ else if (overflowIsolates === 0) {
231
+ overflowEmbeddings += 1;
232
+ }
233
+ break;
234
+ }
235
+ case "RLI":
236
+ case "LRI":
237
+ case "FSI": {
238
+ stamp(index);
239
+ const pdi = pdiOf.get(index) ?? types.length;
240
+ const rtl = type === "RLI" || (type === "FSI" && firstStrongLevel(types, pdiOf, index + 1, pdi) === 1);
241
+ const level = rtl ? nextOdd(top().level) : nextEven(top().level);
242
+ if (level <= MAX_DEPTH && overflowIsolates === 0 && overflowEmbeddings === 0) {
243
+ validIsolates += 1;
244
+ stack.push({ level, override: undefined, isolate: true });
245
+ }
246
+ else {
247
+ overflowIsolates += 1;
248
+ }
249
+ break;
250
+ }
251
+ case "PDI": {
252
+ if (overflowIsolates > 0)
253
+ overflowIsolates -= 1;
254
+ else if (validIsolates > 0 && initiatorOf.has(index)) {
255
+ overflowEmbeddings = 0;
256
+ while (!top().isolate)
257
+ stack.pop();
258
+ stack.pop();
259
+ validIsolates -= 1;
260
+ }
261
+ stamp(index);
262
+ break;
263
+ }
264
+ case "PDF": {
265
+ levels[index] = top().level;
266
+ if (overflowIsolates > 0)
267
+ break;
268
+ if (overflowEmbeddings > 0)
269
+ overflowEmbeddings -= 1;
270
+ else if (!top().isolate && stack.length >= 2)
271
+ stack.pop();
272
+ break;
273
+ }
274
+ case "B":
275
+ levels[index] = paragraphLevel;
276
+ break;
277
+ case "BN":
278
+ levels[index] = top().level;
279
+ break;
280
+ default:
281
+ stamp(index);
282
+ }
283
+ });
284
+ return levels;
285
+ }
286
+ /** X10: the indexes of each isolating run sequence, in order, with X9's removals left out. */
287
+ function isolatingRunSequences(original, levels, pdiOf, initiatorOf) {
288
+ const runs = [];
289
+ let previousLevel = -1;
290
+ original.forEach((type, index) => {
291
+ if (isRemoved(type))
292
+ return;
293
+ if (levels[index] !== previousLevel)
294
+ runs.push([]);
295
+ runs[runs.length - 1].push(index);
296
+ previousLevel = levels[index];
297
+ });
298
+ const runStartingAt = new Map(runs.map((run) => [run[0], run]));
299
+ const sequences = [];
300
+ for (const run of runs) {
301
+ const first = run[0];
302
+ if (original[first] === "PDI" && initiatorOf.has(first))
303
+ continue;
304
+ const sequence = [...run];
305
+ for (;;) {
306
+ const last = sequence[sequence.length - 1];
307
+ const pdi = isIsolateInitiator(original[last]) ? pdiOf.get(last) : undefined;
308
+ const next = pdi === undefined ? undefined : runStartingAt.get(pdi);
309
+ if (next === undefined)
310
+ break;
311
+ // One push at a time: a run can be longer than a call's arguments may be.
312
+ for (const index of next)
313
+ sequence.push(index);
314
+ }
315
+ sequences.push(sequence);
316
+ }
317
+ return sequences;
318
+ }
319
+ const directionOf = (level) => (level & 1 ? "R" : "L");
320
+ /** W1 to W7, over one sequence's classes, in place. */
321
+ function resolveWeak(seq, sos) {
322
+ // W1
323
+ seq.forEach((type, at) => {
324
+ if (type !== "NSM")
325
+ return;
326
+ const before = at === 0 ? sos : seq[at - 1];
327
+ seq[at] = isIsolateInitiator(before) || before === "PDI" ? "ON" : before;
328
+ });
329
+ // W2
330
+ let strong = sos;
331
+ seq.forEach((type, at) => {
332
+ if (type === "EN" && strong === "AL")
333
+ seq[at] = "AN";
334
+ if (type === "L" || type === "R" || type === "AL")
335
+ strong = type;
336
+ });
337
+ // W3
338
+ seq.forEach((type, at) => {
339
+ if (type === "AL")
340
+ seq[at] = "R";
341
+ });
342
+ // W4
343
+ for (let at = 1; at < seq.length - 1; at += 1) {
344
+ const before = seq[at - 1];
345
+ const after = seq[at + 1];
346
+ if (seq[at] === "ES" && before === "EN" && after === "EN")
347
+ seq[at] = "EN";
348
+ if (seq[at] === "CS" && before === after && (before === "EN" || before === "AN"))
349
+ seq[at] = before;
350
+ }
351
+ // W5
352
+ for (let at = 0; at < seq.length; at += 1) {
353
+ if (seq[at] !== "ET")
354
+ continue;
355
+ let end = at;
356
+ while (end < seq.length && seq[end] === "ET")
357
+ end += 1;
358
+ const touches = (at > 0 && seq[at - 1] === "EN") || (end < seq.length && seq[end] === "EN");
359
+ for (let et = at; et < end; et += 1)
360
+ if (touches)
361
+ seq[et] = "EN";
362
+ at = end - 1;
363
+ }
364
+ // W6
365
+ seq.forEach((type, at) => {
366
+ if (type === "ES" || type === "ET" || type === "CS")
367
+ seq[at] = "ON";
368
+ });
369
+ // W7
370
+ strong = sos;
371
+ seq.forEach((type, at) => {
372
+ if (type === "EN" && strong === "L")
373
+ seq[at] = "L";
374
+ if (type === "L" || type === "R")
375
+ strong = type;
376
+ });
377
+ }
378
+ /** BD16: the bracket pairs of one sequence, ascending by opening position. */
379
+ function bracketPairs(seq, codePoints) {
380
+ const open = [];
381
+ const pairs = [];
382
+ for (let at = 0; at < seq.length; at += 1) {
383
+ if (seq[at] !== "ON")
384
+ continue;
385
+ const codePoint = codePoints[at];
386
+ const closing = CLOSING_OF.get(codePoint);
387
+ if (closing !== undefined) {
388
+ if (open.length === MAX_OPEN_BRACKETS)
389
+ return [];
390
+ open.push({ closing: CANONICAL_BRACKET.get(closing) ?? closing, at });
391
+ continue;
392
+ }
393
+ if (!OPENING_OF.has(codePoint))
394
+ continue;
395
+ const canonical = CANONICAL_BRACKET.get(codePoint) ?? codePoint;
396
+ for (let entry = open.length - 1; entry >= 0; entry -= 1) {
397
+ if (open[entry].closing !== canonical)
398
+ continue;
399
+ pairs.push([open[entry].at, at]);
400
+ open.length = entry;
401
+ break;
402
+ }
403
+ }
404
+ return pairs.sort(([a], [b]) => a - b);
405
+ }
406
+ /** N0: paired brackets take the direction of what they enclose. */
407
+ function resolveBrackets(seq, originals, codePoints, embedding, sos) {
408
+ // Under N0, EN and AN count as R.
409
+ const strongOf = (type) => type === "L" ? "L" : type === "R" || type === "EN" || type === "AN" ? "R" : undefined;
410
+ for (const [open, close] of bracketPairs(seq, codePoints)) {
411
+ let inside;
412
+ let matchesEmbedding = false;
413
+ for (let at = open + 1; at < close; at += 1) {
414
+ const strong = strongOf(seq[at]);
415
+ if (strong === undefined)
416
+ continue;
417
+ inside = strong;
418
+ if (strong === embedding)
419
+ matchesEmbedding = true;
420
+ }
421
+ if (inside === undefined)
422
+ continue;
423
+ let direction = embedding;
424
+ if (!matchesEmbedding) {
425
+ let before = strongOf(sos);
426
+ for (let at = open - 1; at >= 0; at -= 1) {
427
+ const strong = strongOf(seq[at]);
428
+ if (strong !== undefined) {
429
+ before = strong;
430
+ break;
431
+ }
432
+ }
433
+ direction = before === embedding ? embedding : inside;
434
+ }
435
+ for (const bracket of [open, close]) {
436
+ seq[bracket] = direction;
437
+ for (let mark = bracket + 1; mark < seq.length && originals[mark] === "NSM"; mark += 1)
438
+ seq[mark] = direction;
439
+ }
440
+ }
441
+ }
442
+ /** N1 and N2: each run of neutrals takes the direction around it, or the embedding's. */
443
+ function resolveNeutrals(seq, sos, eos, embedding) {
444
+ const sideOf = (type) => (type === "L" ? "L" : "R");
445
+ for (let at = 0; at < seq.length; at += 1) {
446
+ if (!isNeutral(seq[at]))
447
+ continue;
448
+ let end = at;
449
+ while (end < seq.length && isNeutral(seq[end]))
450
+ end += 1;
451
+ const before = sideOf(at === 0 ? sos : seq[at - 1]);
452
+ const after = sideOf(end === seq.length ? eos : seq[end]);
453
+ seq.fill(before === after ? before : embedding, at, end);
454
+ at = end - 1;
455
+ }
456
+ }
457
+ /** The classes, levels, and paragraph level of one paragraph, its code points indexed from 0. */
458
+ function resolveParagraph(codePoints, direction) {
459
+ const original = codePoints.map(classOf);
460
+ const { pdiOf, initiatorOf } = matchIsolates(original);
461
+ const level = direction ?? firstStrongLevel(original, pdiOf, 0, original.length);
462
+ const types = [...original];
463
+ const levels = resolveExplicit(types, level, pdiOf, initiatorOf);
464
+ // Apart from `levels`: X10 reads a neighbouring run's explicit level, which I1 and I2 must not have moved.
465
+ const resolved = [...levels];
466
+ for (const sequence of isolatingRunSequences(original, levels, pdiOf, initiatorOf)) {
467
+ const first = sequence[0];
468
+ const last = sequence[sequence.length - 1];
469
+ const sequenceLevel = levels[first];
470
+ // X10: sos and eos compare this level with the one on the far side of the sequence.
471
+ let before = first - 1;
472
+ while (before >= 0 && isRemoved(original[before]))
473
+ before -= 1;
474
+ const sos = directionOf(Math.max(sequenceLevel, before < 0 ? level : levels[before]));
475
+ let after = last + 1;
476
+ while (after < original.length && isRemoved(original[after]))
477
+ after += 1;
478
+ const unmatched = isIsolateInitiator(original[last]) && !pdiOf.has(last);
479
+ const eos = directionOf(Math.max(sequenceLevel, unmatched || after >= original.length ? level : levels[after]));
480
+ const seq = sequence.map((index) => types[index]);
481
+ const originals = sequence.map((index) => original[index]);
482
+ resolveWeak(seq, sos);
483
+ const embedding = directionOf(sequenceLevel);
484
+ resolveBrackets(seq, originals, sequence.map((index) => codePoints[index]), embedding, sos);
485
+ resolveNeutrals(seq, sos, eos, embedding);
486
+ // I1 and I2.
487
+ sequence.forEach((index, at) => {
488
+ const type = seq[at];
489
+ if (sequenceLevel & 1)
490
+ resolved[index] += type === "R" ? 0 : 1;
491
+ else
492
+ resolved[index] += type === "L" ? 0 : type === "R" ? 1 : 2;
493
+ });
494
+ }
495
+ return { level, levels: resolved.map((value, index) => (isRemoved(original[index]) ? REMOVED : value)) };
496
+ }
497
+ /**
498
+ * `text`, one paragraph, resolved to rule I2. `direction` is rule HL1: a
499
+ * paragraph direction stated rather than found. Absent, P2 and P3 find it.
500
+ */
501
+ /** P2 and P3 alone: `text`'s paragraph level, for a caller that resolves nothing else. */
502
+ export function paragraphLevel(text) {
503
+ const types = Array.from(text, (character) => classOf(character.codePointAt(0)));
504
+ return firstStrongLevel(types, matchIsolates(types).pdiOf, 0, types.length);
505
+ }
506
+ export function paragraph(text, direction) {
507
+ const codePoints = Array.from(text, (character) => character.codePointAt(0));
508
+ const { level, levels } = resolveParagraph(codePoints, direction);
509
+ const units = [];
510
+ codePoints.forEach((codePoint, index) => {
511
+ units.push(levels[index]);
512
+ if (codePoint > 0xffff)
513
+ units.push(levels[index]);
514
+ });
515
+ return { level, levels: units };
516
+ }
517
+ /** L1: the levels of one line, its separators and trailing whitespace back at the paragraph level. */
518
+ export function lineLevels(line, levels, paragraphLevel) {
519
+ const out = [...levels];
520
+ const types = Array.from(line, (character) => classOf(character.codePointAt(0)));
521
+ const units = Array.from(line, (character) => character.length);
522
+ let unit = line.length;
523
+ let trailing = true;
524
+ for (let index = types.length - 1; index >= 0; index -= 1) {
525
+ const type = types[index];
526
+ unit -= units[index];
527
+ const whitespace = type === "WS" || isIsolateInitiator(type) || type === "PDI" || isRemoved(type);
528
+ if (type === "B" || type === "S") {
529
+ trailing = true;
530
+ }
531
+ else if (!whitespace) {
532
+ trailing = false;
533
+ }
534
+ if (trailing && !isRemoved(type))
535
+ out.fill(paragraphLevel, unit, unit + units[index]);
536
+ }
537
+ return out;
538
+ }
539
+ /** L2: the indexes of `levels` in visual order, what X9 removed left out. */
540
+ export function visualOrder(levels) {
541
+ const order = levels.flatMap((level, index) => (level === REMOVED ? [] : [index]));
542
+ const kept = order.map((index) => levels[index]);
543
+ const highest = kept.reduce((max, level) => Math.max(max, level), 0);
544
+ const lowestOdd = kept.reduce((min, level) => (level & 1 ? Math.min(min, level) : min), Infinity);
545
+ for (let level = highest; level >= lowestOdd; level -= 1) {
546
+ for (let start = 0; start < kept.length; start += 1) {
547
+ if (kept[start] < level)
548
+ continue;
549
+ let end = start;
550
+ while (end < kept.length && kept[end] >= level)
551
+ end += 1;
552
+ reverse(order, start, end);
553
+ reverse(kept, start, end);
554
+ start = end;
555
+ }
556
+ }
557
+ return order;
558
+ }
559
+ function reverse(items, start, end) {
560
+ for (let left = start, right = end - 1; left < right; left += 1, right -= 1) {
561
+ [items[left], items[right]] = [items[right], items[left]];
562
+ }
563
+ }
564
+ function joiningTypeOf(codePoint) {
565
+ const index = inRanges(JOINING_RANGES, codePoint);
566
+ return index === undefined ? "U" : JOINING_TYPES[index];
567
+ }
568
+ const FORMS_OF = new Map(PRESENTATION_FORMS.map(([letter, ...forms]) => [letter, forms]));
569
+ const ISOLATED = 0;
570
+ const FINAL = 1;
571
+ const INITIAL = 2;
572
+ const MEDIAL = 3;
573
+ const ZWNJ = "\u200c";
574
+ const ZWJ = "\u200d";
575
+ const UNJOINED = { letter: false, toPrevious: false, toNext: false, separator: false };
576
+ function joiningOf(cluster) {
577
+ const base = cluster.codePointAt(0);
578
+ const type = joiningTypeOf(base);
579
+ const toNext = (type === "D" || type === "L" || type === "C") && !cluster.includes(ZWNJ);
580
+ return { letter: FORMS_OF.has(base), toPrevious: type === "D" || type === "R" || type === "C", toNext, separator: cluster.includes(ZWJ) };
581
+ }
582
+ /**
583
+ * Arabic joining on one row's grapheme clusters, in logical order and in place:
584
+ * each letter takes the presentation form its neighbours call for, because a
585
+ * terminal that does not reorder does not join either. A presentation form is
586
+ * one cell, as the letter was, so no width moves. Lam-alef stays two forms rather
587
+ * than the one-cell ligature, as VTE leaves it, since the ligature would narrow
588
+ * a line after it was laid out. A letter Unicode encodes no such form for stays a
589
+ * base letter. A joiner beside a shaped letter goes, its effect carried by the
590
+ * form; any other joiner, an emoji sequence's among them, stays or `cellLen`
591
+ * would change after the cut.
592
+ */
593
+ export function shaped(clusters) {
594
+ const joinings = clusters.map(joiningOf);
595
+ // Marks and lone joiners are transparent: joining reads past them.
596
+ const isTransparent = (at) => joiningTypeOf(clusters[at].codePointAt(0)) === "T";
597
+ const neighbour = (at, step) => {
598
+ let next = at + step;
599
+ while (next >= 0 && next < clusters.length && isTransparent(next))
600
+ next += step;
601
+ return joinings[next] ?? UNJOINED;
602
+ };
603
+ return clusters.map((text, at) => {
604
+ const own = joinings[at];
605
+ if (!own.letter)
606
+ return text;
607
+ const before = neighbour(at, -1);
608
+ const joinsBefore = own.toPrevious && (before.toNext || before.separator);
609
+ const joinsAfter = own.toNext && (own.separator || neighbour(at, 1).toPrevious);
610
+ const forms = FORMS_OF.get(text.codePointAt(0));
611
+ const wanted = joinsBefore ? (joinsAfter ? MEDIAL : FINAL) : joinsAfter ? INITIAL : ISOLATED;
612
+ const form = forms[wanted] || text.codePointAt(0);
613
+ return String.fromCodePoint(form) + text.slice(1).replaceAll(ZWNJ, "").replaceAll(ZWJ, "");
614
+ });
615
+ }
@@ -25,7 +25,7 @@
25
25
  * mismatched to a line.
26
26
  */
27
27
  import { Segment } from "./segment.js";
28
- import type { Style } from "./style.js";
28
+ import { Style } from "./style.js";
29
29
  /** A rule spanning the table: the top and bottom borders, and every separator. */
30
30
  export interface EdgeChars {
31
31
  readonly left: string;
@@ -63,6 +63,14 @@ export declare class Box {
63
63
  * consumer below ever re-checks the shape of the data it reads.
64
64
  */
65
65
  constructor(grid: string);
66
+ /**
67
+ * The style one drawing of a box draws its frame in: `style`, its cells
68
+ * told as that drawing's (`Style.boxFrame`). Whatever draws a box calls this
69
+ * once a drawing and hands the result to `getTop`, `getRow`, `getBottom` and
70
+ * the `getContentChars` it writes, so an effect can find the frame. Its look
71
+ * and its bytes are `style`'s.
72
+ */
73
+ frame(style?: Style): Style;
66
74
  /**
67
75
  * Renders the top border row for given column widths.
68
76
  */
package/dist/core/box.js CHANGED
@@ -26,6 +26,7 @@
26
26
  */
27
27
  import { cellLen } from "./cells.js";
28
28
  import { Segment } from "./segment.js";
29
+ import { Style } from "./style.js";
29
30
  import { drawable } from "./protocol.js";
30
31
  const GRID_ROWS = 8;
31
32
  const GRID_COLUMNS = 4;
@@ -89,6 +90,16 @@ export class Box {
89
90
  this.footContent = contentOf(rows[6]);
90
91
  this.bottom = edgeOf(rows[7]);
91
92
  }
93
+ /**
94
+ * The style one drawing of a box draws its frame in: `style`, its cells
95
+ * told as that drawing's (`Style.boxFrame`). Whatever draws a box calls this
96
+ * once a drawing and hands the result to `getTop`, `getRow`, `getBottom` and
97
+ * the `getContentChars` it writes, so an effect can find the frame. Its look
98
+ * and its bytes are `style`'s.
99
+ */
100
+ frame(style) {
101
+ return (style ?? Style.null()).withBoxFrame({});
102
+ }
92
103
  /**
93
104
  * Renders the top border row for given column widths.
94
105
  */
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * The cells Rich's `cell_len` gives each code point, and the code points VS16
3
3
  * widens. Generated by scripts/cell-widths.py from Rich 15.0.0's Unicode 17.0.0
4
- * table, which Rich builds from wcwidth's data; regenerate rather than edit.
4
+ * table, which Rich builds from wcwidth's data, and the clause for C0 and C1
5
+ * controls that Rich answers ahead of it; regenerate rather than edit.
5
6
  */
6
- /** The cells Rich gives `codePoint` on its own: none for a C0 or C1 control. */
7
+ /** The cells Rich gives `codePoint` on its own. */
7
8
  export declare function codePointWidth(codePoint: number): 0 | 1 | 2;
8
9
  /** Whether VS16 after `codePoint` widens it from one cell to two. */
9
10
  export declare function widensToEmoji(codePoint: number): boolean;