@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.
Files changed (110) hide show
  1. package/README.md +1 -0
  2. package/THIRD-PARTY-NOTICES +42 -0
  3. package/dist/core/animation.d.ts +36 -17
  4. package/dist/core/animation.js +61 -37
  5. package/dist/core/ansi.d.ts +15 -8
  6. package/dist/core/ansi.js +106 -28
  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 +93 -5
  17. package/dist/core/color.d.ts +6 -7
  18. package/dist/core/color.js +11 -13
  19. package/dist/core/console.d.ts +18 -1
  20. package/dist/core/console.js +24 -5
  21. package/dist/core/control-codes.d.ts +61 -0
  22. package/dist/core/control-codes.js +157 -0
  23. package/dist/core/env.d.ts +6 -0
  24. package/dist/core/env.js +9 -1
  25. package/dist/core/export-html.js +4 -3
  26. package/dist/core/export-lines.d.ts +2 -0
  27. package/dist/core/export-lines.js +7 -3
  28. package/dist/core/export-svg.js +6 -5
  29. package/dist/core/json.js +9 -2
  30. package/dist/core/markup.js +4 -3
  31. package/dist/core/orient.d.ts +68 -0
  32. package/dist/core/orient.js +224 -0
  33. package/dist/core/osc8.d.ts +12 -5
  34. package/dist/core/osc8.js +14 -7
  35. package/dist/core/pretty.js +22 -18
  36. package/dist/core/protocol.d.ts +27 -6
  37. package/dist/core/protocol.js +14 -6
  38. package/dist/core/render.d.ts +8 -3
  39. package/dist/core/render.js +18 -5
  40. package/dist/core/seal.d.ts +26 -0
  41. package/dist/core/seal.js +75 -0
  42. package/dist/core/segment.d.ts +9 -1
  43. package/dist/core/segment.js +11 -1
  44. package/dist/core/style.d.ts +29 -1
  45. package/dist/core/style.js +41 -5
  46. package/dist/core/text-order.d.ts +51 -0
  47. package/dist/core/text-order.js +50 -0
  48. package/dist/core/text.d.ts +10 -0
  49. package/dist/core/text.js +133 -87
  50. package/dist/core/timeline.js +6 -6
  51. package/dist/core/view.d.ts +1 -1
  52. package/dist/core/view.js +5 -4
  53. package/dist/core/wrap.d.ts +24 -6
  54. package/dist/core/wrap.js +82 -16
  55. package/dist/host/app.d.ts +44 -3
  56. package/dist/host/app.js +111 -6
  57. package/dist/host/index.d.ts +3 -2
  58. package/dist/host/index.js +1 -0
  59. package/dist/host/input.d.ts +35 -2
  60. package/dist/host/input.js +88 -4
  61. package/dist/host/terminal-colors.d.ts +18 -0
  62. package/dist/host/terminal-colors.js +46 -0
  63. package/dist/host/terminal-host.d.ts +2 -1
  64. package/dist/index.d.ts +5 -4
  65. package/dist/index.js +2 -2
  66. package/dist/renderables/change.d.ts +1 -3
  67. package/dist/renderables/change.js +4 -6
  68. package/dist/renderables/effect.d.ts +60 -59
  69. package/dist/renderables/effect.js +91 -81
  70. package/dist/renderables/effects.d.ts +37 -8
  71. package/dist/renderables/effects.js +185 -52
  72. package/dist/renderables/frames.d.ts +1 -10
  73. package/dist/renderables/frames.js +1 -16
  74. package/dist/renderables/glyph-effect.d.ts +12 -13
  75. package/dist/renderables/glyph-effect.js +34 -32
  76. package/dist/renderables/glyph-effects.d.ts +27 -8
  77. package/dist/renderables/glyph-effects.js +75 -7
  78. package/dist/renderables/lifecycle.d.ts +5 -7
  79. package/dist/renderables/lifecycle.js +6 -7
  80. package/dist/renderables/markdown.js +4 -2
  81. package/dist/renderables/panel.js +6 -4
  82. package/dist/renderables/progressBar.d.ts +3 -3
  83. package/dist/renderables/progressBar.js +16 -13
  84. package/dist/renderables/syntax.js +7 -2
  85. package/dist/renderables/table.js +2 -1
  86. package/dist/template-bindings/animation-funcs.d.ts +49 -18
  87. package/dist/template-bindings/animation-funcs.js +115 -32
  88. package/dist/template-bindings/index.d.ts +7 -5
  89. package/dist/template-bindings/index.js +8 -5
  90. package/dist/template-bindings/palette-funcs.d.ts +1 -1
  91. package/dist/template-bindings/palette-funcs.js +1 -1
  92. package/dist/themes/buildPalette.d.ts +3 -2
  93. package/dist/themes/buildPalette.js +2 -2
  94. package/dist/themes/data/index.js +1 -4
  95. package/dist/themes/data/textual-ansi.js +0 -143
  96. package/dist/themes/ramp.d.ts +1 -1
  97. package/dist/themes/ramp.js +1 -1
  98. package/dist/themes/registry.d.ts +6 -8
  99. package/dist/themes/registry.js +2 -4
  100. package/dist/themes/terminalThemes.d.ts +4 -11
  101. package/dist/themes/terminalThemes.js +31 -47
  102. package/dist/widgets/button.d.ts +1 -1
  103. package/dist/widgets/button.js +6 -6
  104. package/dist/widgets/checkbox.js +4 -4
  105. package/dist/widgets/dropdown.d.ts +1 -1
  106. package/dist/widgets/dropdown.js +7 -5
  107. package/dist/widgets/themed-widget.js +3 -3
  108. package/dist/widgets/toggle.d.ts +1 -1
  109. package/dist/widgets/toggle.js +5 -5
  110. package/package.json +5 -4
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `sealed` — the one total enforcer of what the wire holds.
3
+ *
4
+ * [LAW:single-enforcer] An explicit bidi control reaches the bytes only inside
5
+ * an isolate that accounts for it. The rule has no list of who wrote what: in a
6
+ * segment with no logical stamp every explicit control is deleted, in every
7
+ * order, so a raw `new Segment(text)` is contained and merely shows no effect.
8
+ * What the census withholds goes here too (`wireText`), a segment at a time, on
9
+ * text nothing cuts again, which is why a rule that reads neighbours can live here
10
+ * and not in the sanitizer. A span edge is therefore where that reading stops. A
11
+ * flag cut by one loses its tags and stays a flag; a selector after one is kept;
12
+ * a ZWJ before one is not taken with a deletion past it, so a join can form that
13
+ * `cellLen`, which reads the whole string, did not measure. Segments have always
14
+ * been measured apart and written together, so the same edge in the same text with
15
+ * nothing deleted draws the same row.
16
+ *
17
+ * This runs on the way out, below every cut, because a row is cut after it is
18
+ * drawn: `scroll` divides it, `mergeHorizontal` and a viewport crop it, an
19
+ * overlay splices into it. Controls written at drawing time would be cut with
20
+ * it. `Style.bidi` is a fact on the cells instead, so what survives still names
21
+ * its stamp, and the controls are written around the survivors.
22
+ */
23
+ import { advance, closersOf, opened, withoutExplicit } from "./bidi.js";
24
+ import { wireText } from "./cells.js";
25
+ import { Segment } from "./segment.js";
26
+ const LRI = "\u2066";
27
+ const RLI = "\u2067";
28
+ const PDI = "\u2069";
29
+ /** `text` with closers that match nothing in `open` dropped, so content cannot end the isolate around it. */
30
+ function balanced(text, open) {
31
+ return Array.from(text, (character) => (advance(open, character) ? character : "")).join("");
32
+ }
33
+ /**
34
+ * One row's run of one stamp in its isolate. The initiator is stated from the
35
+ * whole line's direction rather than FSI, so a wrapped row does not re-detect
36
+ * it. Embeddings close by PDF ahead of the PDI because VTE discards a zero-width
37
+ * character at column 0, initiator included, and a PDI matching nothing there
38
+ * closes nothing.
39
+ */
40
+ function* isolated(run, { level, opens }) {
41
+ const open = opened(opens);
42
+ yield new Segment((level === 1 ? RLI : LRI) + opens);
43
+ for (const segment of run) {
44
+ const text = balanced(segment.text, open);
45
+ yield text === segment.text ? segment : new Segment(text, segment.style, segment.control);
46
+ }
47
+ yield new Segment(closersOf(open.stack.join("")) + PDI);
48
+ }
49
+ /** `segments` as the wire may hold them for `order`. */
50
+ export function* sealed(segments, order) {
51
+ let run = [];
52
+ let stamp;
53
+ const flush = function* () {
54
+ if (stamp !== undefined)
55
+ yield* isolated(run, stamp);
56
+ run = [];
57
+ stamp = undefined;
58
+ };
59
+ for (const raw of segments) {
60
+ const drawn = wireText(raw.text);
61
+ const segment = drawn === raw.text ? raw : new Segment(drawn, raw.style, raw.control);
62
+ const own = order === "logical" && !segment.isControl ? segment.style?.bidi : undefined;
63
+ if (own?.order === "logical") {
64
+ if (own !== stamp)
65
+ yield* flush();
66
+ stamp = own;
67
+ run.push(segment);
68
+ continue;
69
+ }
70
+ yield* flush();
71
+ const text = withoutExplicit(segment.text);
72
+ yield text === segment.text ? segment : new Segment(text, segment.style, segment.control);
73
+ }
74
+ yield* flush();
75
+ }
@@ -130,6 +130,14 @@ export declare class Segment {
130
130
  * short rows padded with spaces, long ones cut.
131
131
  */
132
132
  static splitAndCropLines(segments: Iterable<Segment>, width: number): Segment[][];
133
+ /**
134
+ * The plain text of one line of segments, in the coordinate system
135
+ * `divideLine` and `Segment.divide` share: cell offsets into the styled line
136
+ * are cell offsets into this string. [LAW:one-source-of-truth] for that
137
+ * correspondence — the offsets are found in this text and applied to the
138
+ * segments it came from.
139
+ */
140
+ static plainOf(line: readonly Segment[]): string;
133
141
  /**
134
142
  * Returns total cell width of a line. Ignores control segments.
135
143
  */
@@ -177,7 +185,7 @@ export declare class Segment {
177
185
  * rest every time, so a long line wrapped into many rows took time quadratic
178
186
  * in its length.
179
187
  */
180
- static divide(segments: Segment[], cuts: number[]): Segment[][];
188
+ static divide(segments: readonly Segment[], cuts: number[]): Segment[][];
181
189
  /**
182
190
  * Pads below to fill height.
183
191
  */
@@ -307,6 +307,16 @@ export class Segment {
307
307
  static splitAndCropLines(segments, width) {
308
308
  return Segment.splitLines(segments).map((line) => Segment.adjustLineLength(line, width));
309
309
  }
310
+ /**
311
+ * The plain text of one line of segments, in the coordinate system
312
+ * `divideLine` and `Segment.divide` share: cell offsets into the styled line
313
+ * are cell offsets into this string. [LAW:one-source-of-truth] for that
314
+ * correspondence — the offsets are found in this text and applied to the
315
+ * segments it came from.
316
+ */
317
+ static plainOf(line) {
318
+ return line.map((segment) => segment.text).join("");
319
+ }
310
320
  /**
311
321
  * Returns total cell width of a line. Ignores control segments.
312
322
  */
@@ -426,7 +436,7 @@ export class Segment {
426
436
  */
427
437
  static divide(segments, cuts) {
428
438
  if (cuts.length === 0)
429
- return [segments];
439
+ return [[...segments]];
430
440
  const sections = [[]];
431
441
  let offset = 0;
432
442
  let next = 0;
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { ColorSpec, ColorDepth } from "./color.js";
5
5
  import { type Anchor } from "./anchor.js";
6
+ import type { BidiStamp } from "./text-order.js";
6
7
  /**
7
8
  * Canonical text-attribute inventory. Single source of truth consumed by
8
9
  * `Style.parse` / `Style.toString` and the template bindings — adding an
@@ -45,6 +46,8 @@ export interface StyleOptions {
45
46
  link?: string;
46
47
  meta?: Record<string, unknown>;
47
48
  anchor?: Anchor;
49
+ boxFrame?: object;
50
+ bidi?: BidiStamp;
48
51
  /**
49
52
  * The hold the style's cells are in, both ends or neither: a cell saying
50
53
  * only when it next changes would claim, by default, that it always was,
@@ -58,6 +61,10 @@ export interface Hold {
58
61
  readonly lastChange: number;
59
62
  readonly nextChange: number;
60
63
  }
64
+ /** The hold of a cell that never changes: always was, and always will be. */
65
+ export declare const NEVER_CHANGES: Hold;
66
+ /** The moments both `a` and `b` hold through: the later `lastChange` and the sooner `nextChange`. */
67
+ export declare function intersection(a: Hold, b: Hold): Hold;
61
68
  export declare class StyleSyntaxError extends Error {
62
69
  constructor(message: string, options?: ErrorOptions);
63
70
  }
@@ -85,6 +92,23 @@ export declare class Style {
85
92
  * unequal, because they came from different places.
86
93
  */
87
94
  readonly anchor: Anchor | undefined;
95
+ /**
96
+ * The drawing of a box these cells are the frame of (`Box.frame`), compared
97
+ * by identity. A frame is told by what drew it, never by its glyphs, as a
98
+ * strip's seam is (`JOIN`, `./strip.ts`): a box may draw its frame in blanks,
99
+ * and what it draws in ink an element's own content may draw too. It is a
100
+ * drawing and not a box, so two panels of one `ROUNDED` are two frames, and
101
+ * it is no `Anchor`: a frame's cells say whose they are and not where, since
102
+ * a table draws its top rule before it knows its height. It draws nothing.
103
+ */
104
+ readonly boxFrame: object | undefined;
105
+ /**
106
+ * What drawing said of these cells' direction, compared by identity, as a
107
+ * link is a fact the encoder turns into bytes: `Segment` never holds a control
108
+ * the library wrote, so no cut of a row can unbalance one, and what survives a
109
+ * cut still names its stamp (`./seal.ts`). It draws nothing.
110
+ */
111
+ readonly bidi: BidiStamp | undefined;
88
112
  /**
89
113
  * The hold this style's cells are in, on the timeline of whoever drew the
90
114
  * frame: since when what they show has been what it is (`lastChange`), and
@@ -117,6 +141,10 @@ export declare class Style {
117
141
  * The same look, drawn from the place `anchor` names, or from nowhere.
118
142
  */
119
143
  withAnchor(anchor: Anchor | undefined): Style;
144
+ /** The same look, as the frame of the drawing `boxFrame` names, or of none. */
145
+ withBoxFrame(boxFrame: object | undefined): Style;
146
+ /** The same look, its cells' direction as `bidi` says. */
147
+ withBidi(bidi: BidiStamp | undefined): Style;
120
148
  /** The style's text attributes, each as it sets it. */
121
149
  get attributes(): Attributes;
122
150
  /**
@@ -163,7 +191,7 @@ export declare class Style {
163
191
  /**
164
192
  * The look as a canonical string that round-trips through `Style.parse()`:
165
193
  * colours, attributes and the link, which is all a style string can spell.
166
- * What a style carries beyond its look — `meta`, `anchor`, `hold` —
194
+ * What a style carries beyond its look — `meta`, `anchor`, `boxFrame`, `bidi`, `hold` —
167
195
  * has no spelling and is not here, so two styles that differ only there
168
196
  * read alike; `equals` tells them apart.
169
197
  */
@@ -63,7 +63,11 @@ export const ATTRIBUTE_SHORT_ALIASES = {
63
63
  uu: "underline2",
64
64
  };
65
65
  /** The hold of a cell that never changes: always was, and always will be. */
66
- const NEVER_CHANGES = { lastChange: Number.NEGATIVE_INFINITY, nextChange: Number.POSITIVE_INFINITY };
66
+ export const NEVER_CHANGES = { lastChange: Number.NEGATIVE_INFINITY, nextChange: Number.POSITIVE_INFINITY };
67
+ /** The moments both `a` and `b` hold through: the later `lastChange` and the sooner `nextChange`. */
68
+ export function intersection(a, b) {
69
+ return { lastChange: Math.max(a.lastChange, b.lastChange), nextChange: Math.min(a.nextChange, b.nextChange) };
70
+ }
67
71
  export class StyleSyntaxError extends Error {
68
72
  constructor(message, options) {
69
73
  super(message, options);
@@ -97,6 +101,23 @@ export class Style {
97
101
  * unequal, because they came from different places.
98
102
  */
99
103
  anchor;
104
+ /**
105
+ * The drawing of a box these cells are the frame of (`Box.frame`), compared
106
+ * by identity. A frame is told by what drew it, never by its glyphs, as a
107
+ * strip's seam is (`JOIN`, `./strip.ts`): a box may draw its frame in blanks,
108
+ * and what it draws in ink an element's own content may draw too. It is a
109
+ * drawing and not a box, so two panels of one `ROUNDED` are two frames, and
110
+ * it is no `Anchor`: a frame's cells say whose they are and not where, since
111
+ * a table draws its top rule before it knows its height. It draws nothing.
112
+ */
113
+ boxFrame;
114
+ /**
115
+ * What drawing said of these cells' direction, compared by identity, as a
116
+ * link is a fact the encoder turns into bytes: `Segment` never holds a control
117
+ * the library wrote, so no cut of a row can unbalance one, and what survives a
118
+ * cut still names its stamp (`./seal.ts`). It draws nothing.
119
+ */
120
+ bidi;
100
121
  /**
101
122
  * The hold this style's cells are in, on the timeline of whoever drew the
102
123
  * frame: since when what they show has been what it is (`lastChange`), and
@@ -142,6 +163,8 @@ export class Style {
142
163
  this.link = options.link || undefined;
143
164
  this.meta = options.meta;
144
165
  this.anchor = options.anchor;
166
+ this.boxFrame = options.boxFrame;
167
+ this.bidi = options.bidi;
145
168
  }
146
169
  get isNull() {
147
170
  return (this.color === undefined &&
@@ -162,6 +185,8 @@ export class Style {
162
185
  this.link === undefined &&
163
186
  this.meta === undefined &&
164
187
  this.anchor === undefined &&
188
+ this.boxFrame === undefined &&
189
+ this.bidi === undefined &&
165
190
  this.hold.nextChange === Number.POSITIVE_INFINITY &&
166
191
  this.hold.lastChange === Number.NEGATIVE_INFINITY);
167
192
  }
@@ -199,6 +224,14 @@ export class Style {
199
224
  withAnchor(anchor) {
200
225
  return this.with({ anchor });
201
226
  }
227
+ /** The same look, as the frame of the drawing `boxFrame` names, or of none. */
228
+ withBoxFrame(boxFrame) {
229
+ return this.with({ boxFrame });
230
+ }
231
+ /** The same look, its cells' direction as `bidi` says. */
232
+ withBidi(bidi) {
233
+ return this.with({ bidi });
234
+ }
202
235
  /** The style's text attributes, each as it sets it. */
203
236
  get attributes() {
204
237
  // [LAW:one-source-of-truth] Keyed by `ATTRIBUTE_NAMES`, whose every name is a key here: the cast is that list's own claim.
@@ -257,9 +290,10 @@ export class Style {
257
290
  ? { ...this.meta, ...other.meta }
258
291
  : other.meta ?? this.meta,
259
292
  anchor: other.anchor ?? this.anchor,
260
- // Cells under both change when either does: the sooner, and have been
261
- // what they are since the later — the intersection of the two holds.
262
- hold: { lastChange: Math.max(this.hold.lastChange, other.hold.lastChange), nextChange: Math.min(this.hold.nextChange, other.hold.nextChange) },
293
+ boxFrame: other.boxFrame ?? this.boxFrame,
294
+ bidi: other.bidi ?? this.bidi,
295
+ // Cells under both change when either does.
296
+ hold: intersection(this.hold, other.hold),
263
297
  });
264
298
  }
265
299
  equals(other) {
@@ -282,6 +316,8 @@ export class Style {
282
316
  this.overline === other.overline &&
283
317
  this.link === other.link &&
284
318
  anchorsEqual(this.anchor, other.anchor) &&
319
+ this.boxFrame === other.boxFrame &&
320
+ this.bidi === other.bidi &&
285
321
  this.hold.nextChange === other.hold.nextChange &&
286
322
  this.hold.lastChange === other.hold.lastChange);
287
323
  }
@@ -333,7 +369,7 @@ export class Style {
333
369
  /**
334
370
  * The look as a canonical string that round-trips through `Style.parse()`:
335
371
  * colours, attributes and the link, which is all a style string can spell.
336
- * What a style carries beyond its look — `meta`, `anchor`, `hold` —
372
+ * What a style carries beyond its look — `meta`, `anchor`, `boxFrame`, `bidi`, `hold` —
337
373
  * has no spelling and is not here, so two styles that differ only there
338
374
  * read alike; `equals` tells them apart.
339
375
  */
@@ -0,0 +1,51 @@
1
+ /**
2
+ * What a destination does with text direction.
3
+ *
4
+ * Every order above `plain` is a claim about the terminal, and a wrong claim
5
+ * costs more than none. Visual order on a terminal that also reorders reverses
6
+ * the text twice, and no query answers for every such terminal. An isolate on a
7
+ * terminal whose width table predates it advances the cursor (xterm.js 6 does),
8
+ * which moves a table's border and the rows a painter goes back over. So what
9
+ * nothing is known about gets bare text, and `detectTextOrder` grants more only
10
+ * on evidence.
11
+ */
12
+ import { type Env } from "./env.js";
13
+ /**
14
+ * - `plain`: nothing is known of the destination, or it is not a display: a
15
+ * pipe, a file, an unknown terminal. Characters only, in logical order, with
16
+ * no explicit bidi control.
17
+ * - `logical`: a display known to reorder and to hold the controls at zero
18
+ * width. Rows are sealed in isolates.
19
+ * - `visual`: a display known not to reorder. Rows are written reordered and
20
+ * shaped, with no controls.
21
+ */
22
+ export type TextOrder = "plain" | "logical" | "visual";
23
+ /** A row's paragraph direction: 0 left to right, 1 right to left. */
24
+ export type ParagraphLevel = 0 | 1;
25
+ /** Cells written for a terminal that reorders them: their row's direction, and what is still open where they begin. */
26
+ export interface LogicalStamp {
27
+ readonly order: "logical";
28
+ readonly level: ParagraphLevel;
29
+ readonly opens: string;
30
+ }
31
+ /** Cells already in visual order, for an export not to reverse again. */
32
+ export interface VisualStamp {
33
+ readonly order: "visual";
34
+ }
35
+ export type BidiStamp = LogicalStamp | VisualStamp;
36
+ /**
37
+ * The order `env` gives evidence for.
38
+ *
39
+ * An environment is inherited: `code .` from Windows Terminal carries
40
+ * `WT_SESSION` into VS Code's xterm.js terminal, and Alacritty clears nothing
41
+ * from GNOME Terminal. `TERM` is the one variable every terminal writes for its
42
+ * own child, so it is read first, and a variable a parent could have left counts
43
+ * only beside the `TERM` its own terminal sets. The rows are in the order that
44
+ * makes that true; a terminal naming itself off this list is `plain`.
45
+ *
46
+ * Not listed, each for a reason: xterm.js lays a row out through the browser and
47
+ * advances for an isolate, and VS Code is xterm.js. WezTerm's bidi is a setting
48
+ * the environment cannot show. iTerm2, Konsole, Terminal.app, mintty and mlterm
49
+ * are unmeasured. Any program may state its order.
50
+ */
51
+ export declare function detectTextOrder(env: Env, isTTY: boolean): TextOrder;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * What a destination does with text direction.
3
+ *
4
+ * Every order above `plain` is a claim about the terminal, and a wrong claim
5
+ * costs more than none. Visual order on a terminal that also reorders reverses
6
+ * the text twice, and no query answers for every such terminal. An isolate on a
7
+ * terminal whose width table predates it advances the cursor (xterm.js 6 does),
8
+ * which moves a table's border and the rows a painter goes back over. So what
9
+ * nothing is known about gets bare text, and `detectTextOrder` grants more only
10
+ * on evidence.
11
+ */
12
+ import { isDumbTerminal } from "./env.js";
13
+ import { pattern } from "./pattern.js";
14
+ // Terminals that paint their own cells and do not reorder, by the `TERM` each
15
+ // writes for its child. Ghostty forces the embedding level to 0
16
+ // (font/shaper/coretext.zig); Alacritty (#663) and foot (#756) run no bidi.
17
+ const VISUAL_TERM = pattern(/^(alacritty|foot|xterm-ghostty)/);
18
+ /**
19
+ * The order `env` gives evidence for.
20
+ *
21
+ * An environment is inherited: `code .` from Windows Terminal carries
22
+ * `WT_SESSION` into VS Code's xterm.js terminal, and Alacritty clears nothing
23
+ * from GNOME Terminal. `TERM` is the one variable every terminal writes for its
24
+ * own child, so it is read first, and a variable a parent could have left counts
25
+ * only beside the `TERM` its own terminal sets. The rows are in the order that
26
+ * makes that true; a terminal naming itself off this list is `plain`.
27
+ *
28
+ * Not listed, each for a reason: xterm.js lays a row out through the browser and
29
+ * advances for an isolate, and VS Code is xterm.js. WezTerm's bidi is a setting
30
+ * the environment cannot show. iTerm2, Konsole, Terminal.app, mintty and mlterm
31
+ * are unmeasured. Any program may state its order.
32
+ */
33
+ export function detectTextOrder(env, isTTY) {
34
+ if (!isTTY || isDumbTerminal(env))
35
+ return "plain";
36
+ if (env["TMUX"] || env["STY"] || env["ZELLIJ"] || env["INSIDE_EMACS"] || env["NVIM"] || env["VIM_TERMINAL"])
37
+ return "plain";
38
+ if ((env["TERM"] ?? "").match(VISUAL_TERM) !== null)
39
+ return "visual";
40
+ if (env["TERM_PROGRAM"] || env["TERMINAL_EMULATOR"] || env["KONSOLE_VERSION"])
41
+ return "plain";
42
+ // VTE reorders, and takes the controls at zero width past column 0
43
+ // (`src/unicode-width.hh`, `Terminal::insert_char`).
44
+ if (env["VTE_VERSION"] && env["TERM"] === "xterm-256color")
45
+ return "logical";
46
+ // Windows Terminal (microsoft/terminal#538): a console there has no `TERM`, WSL has this one.
47
+ if (env["WT_SESSION"] && (env["TERM"] === undefined || env["TERM"] === "xterm-256color"))
48
+ return "visual";
49
+ return "plain";
50
+ }
@@ -5,6 +5,13 @@ import { Segment } from "./segment.js";
5
5
  import { Style, type TextStyle } from "./style.js";
6
6
  import type { Renderable, Measurable, OverflowMethod, RenderOptions } from "./protocol.js";
7
7
  export declare function stripControlChars(text: string): string;
8
+ /**
9
+ * `text` as a Text's characters hold it: stripped, every line ending an LF, and
10
+ * every control its picture. One code unit for one, so an offset into stripped
11
+ * text holds, and every reader of a Text finds one line break without having to
12
+ * know the others.
13
+ */
14
+ export declare function sanitized(text: string): string;
8
15
  /**
9
16
  * The style a stored `TextStyle` stands for in this render.
10
17
  *
@@ -35,9 +42,12 @@ export interface RichTextOptions {
35
42
  export declare class RichText implements Renderable, Measurable {
36
43
  private readonly _text;
37
44
  private readonly _spans;
45
+ private readonly _closure;
38
46
  private readonly _format;
39
47
  private readonly _tabSize;
40
48
  constructor(text?: string, options?: RichTextOptions);
49
+ /** The part of the text whose direction state nothing has closed. */
50
+ private _unclosed;
41
51
  private _reformat;
42
52
  get plain(): string;
43
53
  set plain(value: string);