@promptctl/rich-js 0.17.0 → 0.18.1

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 (147) hide show
  1. package/README.md +13 -26
  2. package/dist/core/anchor.d.ts +45 -0
  3. package/dist/core/anchor.js +39 -0
  4. package/dist/core/ansi.d.ts +65 -0
  5. package/dist/core/ansi.js +231 -0
  6. package/dist/core/box.d.ts +0 -1
  7. package/dist/core/box.js +6 -18
  8. package/dist/core/cells.d.ts +16 -5
  9. package/dist/core/cells.js +31 -10
  10. package/dist/core/color.d.ts +4 -8
  11. package/dist/core/color.js +28 -19
  12. package/dist/core/console.d.ts +13 -4
  13. package/dist/core/console.js +91 -36
  14. package/dist/core/export-html.d.ts +40 -5
  15. package/dist/core/export-html.js +111 -17
  16. package/dist/core/markup.d.ts +27 -5
  17. package/dist/core/markup.js +282 -170
  18. package/dist/core/measure.d.ts +1 -1
  19. package/dist/core/measure.js +2 -1
  20. package/dist/core/place.d.ts +30 -0
  21. package/dist/core/place.js +51 -0
  22. package/dist/core/pretty.d.ts +3 -0
  23. package/dist/core/pretty.js +5 -1
  24. package/dist/core/protocol.d.ts +136 -6
  25. package/dist/core/protocol.js +64 -2
  26. package/dist/core/render.d.ts +2 -2
  27. package/dist/core/render.js +2 -2
  28. package/dist/core/segment.d.ts +23 -4
  29. package/dist/core/segment.js +75 -16
  30. package/dist/core/spinnerData.d.ts +2 -0
  31. package/dist/core/spinnerData.js +3 -1
  32. package/dist/core/strip.d.ts +3 -7
  33. package/dist/core/strip.js +32 -35
  34. package/dist/core/style.d.ts +25 -0
  35. package/dist/core/style.js +61 -89
  36. package/dist/core/text.d.ts +16 -15
  37. package/dist/core/text.js +62 -76
  38. package/dist/host/app.d.ts +121 -0
  39. package/dist/host/app.js +255 -0
  40. package/dist/host/host-environment.d.ts +26 -0
  41. package/dist/host/host-environment.js +44 -0
  42. package/dist/host/index.d.ts +4 -2
  43. package/dist/host/index.js +2 -1
  44. package/dist/host/terminal-host.d.ts +43 -7
  45. package/dist/host/terminal-host.js +57 -3
  46. package/dist/index.d.ts +7 -3
  47. package/dist/index.js +4 -1
  48. package/dist/node/terminal-host.d.ts +31 -2
  49. package/dist/node/terminal-host.js +69 -2
  50. package/dist/node/traceback.d.ts +16 -6
  51. package/dist/node/traceback.js +30 -8
  52. package/dist/renderables/align.d.ts +2 -2
  53. package/dist/renderables/align.js +6 -19
  54. package/dist/renderables/columns.js +4 -20
  55. package/dist/renderables/embed.d.ts +50 -0
  56. package/dist/renderables/embed.js +71 -0
  57. package/dist/renderables/group.js +3 -1
  58. package/dist/renderables/layout.js +31 -34
  59. package/dist/renderables/live.d.ts +6 -3
  60. package/dist/renderables/live.js +89 -45
  61. package/dist/renderables/markdown.d.ts +1 -1
  62. package/dist/renderables/markdown.js +6 -4
  63. package/dist/renderables/padding.js +3 -2
  64. package/dist/renderables/panel.js +26 -41
  65. package/dist/renderables/progress.js +5 -8
  66. package/dist/renderables/progressBar.js +6 -5
  67. package/dist/renderables/rule.d.ts +3 -2
  68. package/dist/renderables/rule.js +9 -9
  69. package/dist/renderables/spinner.d.ts +5 -3
  70. package/dist/renderables/spinner.js +14 -8
  71. package/dist/renderables/syntax.d.ts +1 -1
  72. package/dist/renderables/syntax.js +2 -1
  73. package/dist/renderables/table.d.ts +26 -14
  74. package/dist/renderables/table.js +239 -141
  75. package/dist/renderables/traceback.js +8 -9
  76. package/dist/renderables/tree.d.ts +17 -3
  77. package/dist/renderables/tree.js +62 -71
  78. package/dist/renderables/viewport.d.ts +114 -0
  79. package/dist/renderables/viewport.js +198 -0
  80. package/dist/template-bindings/color-funcs.js +11 -13
  81. package/dist/template-bindings/index.js +4 -2
  82. package/dist/template-bindings/palette-funcs.js +6 -16
  83. package/dist/template-bindings/style-funcs.js +4 -0
  84. package/dist/themes/data/ansi-tables.d.ts +14 -0
  85. package/dist/themes/data/ansi-tables.js +49 -0
  86. package/dist/themes/data/atom-one-dark.js +19 -0
  87. package/dist/themes/data/atom-one-light.js +19 -0
  88. package/dist/themes/data/catppuccin-frappe.js +19 -0
  89. package/dist/themes/data/catppuccin-latte.js +19 -0
  90. package/dist/themes/data/catppuccin-macchiato.js +19 -0
  91. package/dist/themes/data/catppuccin-mocha.js +19 -0
  92. package/dist/themes/data/cyberpunk.js +2 -0
  93. package/dist/themes/data/default.js +2 -0
  94. package/dist/themes/data/dracula.js +19 -0
  95. package/dist/themes/data/flexoki.js +19 -0
  96. package/dist/themes/data/gruvbox.js +19 -0
  97. package/dist/themes/data/monokai.js +19 -0
  98. package/dist/themes/data/nord.js +19 -0
  99. package/dist/themes/data/rose-pine-dawn.js +19 -0
  100. package/dist/themes/data/rose-pine-moon.js +19 -0
  101. package/dist/themes/data/rose-pine.js +19 -0
  102. package/dist/themes/data/solarized-dark.js +19 -0
  103. package/dist/themes/data/solarized-light.js +19 -0
  104. package/dist/themes/data/svg-export.js +19 -0
  105. package/dist/themes/data/textual-ansi.js +2 -0
  106. package/dist/themes/data/textual-dark.js +2 -0
  107. package/dist/themes/data/textual-light.js +2 -0
  108. package/dist/themes/data/tokyo-night.js +19 -0
  109. package/dist/themes/data/types.d.ts +16 -0
  110. package/dist/themes/data/types.js +10 -1
  111. package/dist/themes/registry.d.ts +3 -2
  112. package/dist/themes/registry.js +13 -1
  113. package/dist/themes/terminalThemes.d.ts +2 -7
  114. package/dist/themes/terminalThemes.js +4 -9
  115. package/dist/widgets/button.d.ts +1 -1
  116. package/dist/widgets/button.js +4 -2
  117. package/dist/widgets/checkbox.d.ts +2 -2
  118. package/dist/widgets/checkbox.js +5 -3
  119. package/dist/widgets/dropdown.d.ts +2 -2
  120. package/dist/widgets/dropdown.js +13 -16
  121. package/dist/widgets/event-router.d.ts +21 -28
  122. package/dist/widgets/event-router.js +80 -73
  123. package/dist/widgets/focus-manager.d.ts +25 -6
  124. package/dist/widgets/focus-manager.js +63 -65
  125. package/dist/widgets/hit.d.ts +64 -0
  126. package/dist/widgets/hit.js +113 -0
  127. package/dist/widgets/index.d.ts +5 -7
  128. package/dist/widgets/index.js +3 -4
  129. package/dist/widgets/slider.d.ts +2 -2
  130. package/dist/widgets/slider.js +7 -7
  131. package/dist/widgets/static-item.d.ts +9 -15
  132. package/dist/widgets/static-item.js +9 -15
  133. package/dist/widgets/text-input.d.ts +2 -2
  134. package/dist/widgets/text-input.js +17 -14
  135. package/dist/widgets/toggle.d.ts +1 -1
  136. package/dist/widgets/toggle.js +3 -2
  137. package/dist/widgets/types.d.ts +14 -35
  138. package/dist/widgets/types.js +0 -1
  139. package/dist/widgets/widget-app.d.ts +63 -0
  140. package/dist/widgets/widget-app.js +199 -0
  141. package/dist/widgets/widget-base.d.ts +4 -6
  142. package/dist/widgets/widget-base.js +22 -30
  143. package/package.json +12 -4
  144. package/dist/host/host-stream.d.ts +0 -30
  145. package/dist/host/host-stream.js +0 -35
  146. package/dist/widgets/screen.d.ts +0 -98
  147. package/dist/widgets/screen.js +0 -530
@@ -29,6 +29,7 @@ import { Segment } from "./segment.js";
29
29
  import { Style } from "./style.js";
30
30
  import { ColorDepth, ColorSpec, blendRgb } from "./color.js";
31
31
  import { Oklch } from "./oklch.js";
32
+ import { drawable } from "./protocol.js";
32
33
  // --- Strip ---
33
34
  export class Strip {
34
35
  items;
@@ -72,17 +73,6 @@ const EMPTY = {
72
73
  // intentionally empty
73
74
  },
74
75
  };
75
- class FixedSegment {
76
- _text;
77
- _style;
78
- constructor(text, style) {
79
- this._text = text;
80
- this._style = style;
81
- }
82
- *render(_options) {
83
- yield new Segment(this._text, this._style);
84
- }
85
- }
86
76
  /** A renderable whose segments are computed from the options it is rendered with. */
87
77
  function deferred(emit) {
88
78
  return { render: emit };
@@ -159,16 +149,18 @@ export const POWERLINE_JOINER_GLYPHS = Object.freeze({
159
149
  lead: "\ue0d7",
160
150
  tail: "\ue0b0",
161
151
  });
152
+ /** The powerline set in ASCII: an arrow for every join, runs begun flat. */
153
+ const ASCII_POWERLINE_GLYPHS = Object.freeze({
154
+ glyph: ">",
155
+ divider: ">",
156
+ lead: "",
157
+ tail: ">",
158
+ });
159
+ const powerlineGlyphs = ({ glyph, divider, lead, tail }) => glyph + divider + lead + tail;
162
160
  export class PowerlineJoiner {
163
- _glyph;
164
- _divider;
165
- _lead;
166
- _tail;
161
+ _glyphs;
167
162
  constructor(options = POWERLINE_JOINER_GLYPHS) {
168
- this._glyph = options.glyph;
169
- this._divider = options.divider;
170
- this._lead = options.lead;
171
- this._tail = options.tail;
163
+ this._glyphs = options;
172
164
  }
173
165
  join(left, right) {
174
166
  // [LAW:dataflow-not-control-flow] One table for all three positions (start
@@ -196,11 +188,9 @@ export class PowerlineJoiner {
196
188
  // is the only thing that elides the separator, and that is paint logic.
197
189
  // "Absent" = no bg OR the terminal default (transparent) — paintableBg folds
198
190
  // both to undefined so an explicit `… on default` cannot smuggle a separator.
199
- const glyph = this._glyph;
200
- const divider = this._divider;
201
- const lead = this._lead;
202
- const tail = this._tail;
191
+ const glyphs = this._glyphs;
203
192
  return deferred(function* (options) {
193
+ const { glyph, divider, lead, tail } = drawable(options, glyphs, ASCII_POWERLINE_GLYPHS, powerlineGlyphs);
204
194
  const leftEdge = left?.edgeStyle("right", options);
205
195
  const leftBg = paintableBg(leftEdge?.bgcolor);
206
196
  const rightBg = paintableBg(right?.edgeStyle("left", options).bgcolor);
@@ -222,31 +212,33 @@ export class PowerlineJoiner {
222
212
  });
223
213
  }
224
214
  }
215
+ const ASCII_CAPS = { left: "(", right: ")" };
216
+ const capGlyphs = ({ left, right }) => left + right;
225
217
  export class CapsuleJoiner {
226
- _left;
227
- _right;
218
+ _caps;
228
219
  _separator;
229
220
  constructor(options) {
230
- this._left = options?.left ?? "";
231
- this._right = options?.right ?? "";
221
+ this._caps = { left: options?.left ?? "", right: options?.right ?? "" };
232
222
  this._separator = options?.separator ?? " ";
233
223
  }
234
224
  *_emit(left, right, options) {
225
+ const caps = drawable(options, this._caps, ASCII_CAPS, capGlyphs);
226
+ const separator = drawable(options, this._separator, " ");
235
227
  if (left === null && right === null)
236
228
  return;
237
229
  if (left === null) {
238
- yield new Segment(this._left, bgAsFg(right.edgeStyle("left", options)));
230
+ yield new Segment(caps.left, bgAsFg(right.edgeStyle("left", options)));
239
231
  return;
240
232
  }
241
233
  if (right === null) {
242
- yield new Segment(this._right, bgAsFg(left.edgeStyle("right", options)));
234
+ yield new Segment(caps.right, bgAsFg(left.edgeStyle("right", options)));
243
235
  return;
244
236
  }
245
237
  // Middle: close the left capsule, separator (unstyled), open the right.
246
- yield new Segment(this._right, bgAsFg(left.edgeStyle("right", options)));
247
- if (this._separator.length > 0)
248
- yield new Segment(this._separator);
249
- yield new Segment(this._left, bgAsFg(right.edgeStyle("left", options)));
238
+ yield new Segment(caps.right, bgAsFg(left.edgeStyle("right", options)));
239
+ if (separator.length > 0)
240
+ yield new Segment(separator);
241
+ yield new Segment(caps.left, bgAsFg(right.edgeStyle("left", options)));
250
242
  }
251
243
  join(left, right) {
252
244
  return deferred((options) => this._emit(left, right, options));
@@ -263,7 +255,9 @@ export class PlainJoiner {
263
255
  // Endpoints are empty — a fixed separator has no natural cap.
264
256
  if (left === null || right === null)
265
257
  return EMPTY;
266
- return new FixedSegment(this._separator, this._style);
258
+ const separator = this._separator;
259
+ const style = this._style;
260
+ return deferred((options) => [new Segment(drawable(options, separator, " | "), style)]);
267
261
  }
268
262
  }
269
263
  /**
@@ -292,6 +286,9 @@ export class GradientJoiner {
292
286
  return;
293
287
  const lTrip = lbg.getTruecolor();
294
288
  const rTrip = rbg.getTruecolor();
289
+ // An ASCII cell carries one sample, the background: the gradient keeps
290
+ // its colours at half the resolution.
291
+ const half = drawable(options, HALF_BLOCK, " ");
295
292
  const samples = 2 * steps;
296
293
  // Midpoint sampling across `2 * steps` half-cell positions: sample j has
297
294
  // t = (j + 0.5) / samples. Cell i takes samples 2i (left half) and 2i+1
@@ -301,7 +298,7 @@ export class GradientJoiner {
301
298
  const tRight = (2 * i + 1.5) / samples;
302
299
  const fg = ColorSpec.fromRgba(blendRgb(lTrip, rTrip, tLeft));
303
300
  const bg = ColorSpec.fromRgba(blendRgb(lTrip, rTrip, tRight));
304
- yield new Segment(HALF_BLOCK, new Style({ color: fg, bgcolor: bg }));
301
+ yield new Segment(half, new Style({ color: fg, bgcolor: bg }));
305
302
  }
306
303
  });
307
304
  }
@@ -2,6 +2,7 @@
2
2
  * Immutable style descriptors — colors, text attributes, links, metadata.
3
3
  */
4
4
  import { ColorSpec, ColorDepth } from "./color.js";
5
+ import { type Anchor } from "./anchor.js";
5
6
  /**
6
7
  * Canonical text-attribute inventory. Single source of truth consumed by
7
8
  * `Style.parse` / `Style.toString` and the template bindings — adding an
@@ -10,6 +11,7 @@ import { ColorSpec, ColorDepth } from "./color.js";
10
11
  */
11
12
  export declare const ATTRIBUTE_NAMES: readonly ["bold", "dim", "italic", "underline", "blink", "blink2", "reverse", "conceal", "strike", "underline2", "frame", "encircle", "overline"];
12
13
  export type AttributeName = (typeof ATTRIBUTE_NAMES)[number];
14
+ export declare const ATTRIBUTE_SGR: Record<AttributeName, number>;
13
15
  /**
14
16
  * Canonical short-attribute aliases (`b` → `bold`, `i` → `italic`, …).
15
17
  * Single source of truth consumed by `Style.parse` and the template
@@ -35,6 +37,7 @@ export interface StyleOptions {
35
37
  overline?: boolean;
36
38
  link?: string;
37
39
  meta?: Record<string, unknown>;
40
+ anchor?: Anchor;
38
41
  }
39
42
  export declare class StyleSyntaxError extends Error {
40
43
  constructor(message: string, options?: ErrorOptions);
@@ -57,13 +60,35 @@ export declare class Style {
57
60
  readonly overline: boolean | undefined;
58
61
  readonly link: string | undefined;
59
62
  readonly meta: Record<string, unknown> | undefined;
63
+ /**
64
+ * Where this style's cells sat in the output of whatever drew them; see
65
+ * `./anchor.ts`. It draws nothing, and it makes two styles that draw alike
66
+ * unequal, because they came from different places.
67
+ */
68
+ readonly anchor: Anchor | undefined;
60
69
  constructor(options?: StyleOptions);
61
70
  get isNull(): boolean;
62
71
  get transparentBackground(): boolean;
63
72
  get backgroundStyle(): Style;
73
+ /**
74
+ * This style with `overrides` in place of its own fields.
75
+ *
76
+ * [LAW:one-source-of-truth] The field list is the class's own fields, so a
77
+ * field added to `Style` is carried by every copy without being named here.
78
+ */
79
+ private with;
64
80
  get withoutColor(): Style;
65
81
  withLink(link: string | undefined): Style;
66
82
  clearMetaAndLinks(): Style;
83
+ /**
84
+ * The same look, drawn from the place `anchor` names, or from nowhere.
85
+ */
86
+ withAnchor(anchor: Anchor | undefined): Style;
87
+ /**
88
+ * This style on the cell `cells` to the right: its anchor shifted along its
89
+ * row, or the style itself when it has no anchor.
90
+ */
91
+ shiftedBy(cells: number): Style;
67
92
  /**
68
93
  * Merges two styles. `other`'s values take priority.
69
94
  */
@@ -2,6 +2,7 @@
2
2
  * Immutable style descriptors — colors, text attributes, links, metadata.
3
3
  */
4
4
  import { COLOR_NAMES, ColorSpec, SURFACE_BLACK, } from "./color.js";
5
+ import { shiftAnchor } from "./anchor.js";
5
6
  // [LAW:one-way-deps] `core/style` depends only on `core/color`. The substrate
6
7
  // a translucent colour is flattened over is `SURFACE_BLACK`, defined beside
7
8
  // `compositeOver`.
@@ -27,8 +28,8 @@ export const ATTRIBUTE_NAMES = [
27
28
  "encircle",
28
29
  "overline",
29
30
  ];
30
- // The SGR parameter that turns each attribute on
31
- const ATTRIBUTE_SGR = {
31
+ // The SGR parameter that turns each attribute on. `ansi.ts` reads it back.
32
+ export const ATTRIBUTE_SGR = {
32
33
  bold: 1,
33
34
  dim: 2,
34
35
  italic: 3,
@@ -86,27 +87,13 @@ export class Style {
86
87
  overline;
87
88
  link;
88
89
  meta;
89
- constructor(options) {
90
- if (!options) {
91
- this.color = undefined;
92
- this.bgcolor = undefined;
93
- this.bold = undefined;
94
- this.dim = undefined;
95
- this.italic = undefined;
96
- this.underline = undefined;
97
- this.blink = undefined;
98
- this.blink2 = undefined;
99
- this.reverse = undefined;
100
- this.conceal = undefined;
101
- this.strike = undefined;
102
- this.underline2 = undefined;
103
- this.frame = undefined;
104
- this.encircle = undefined;
105
- this.overline = undefined;
106
- this.link = undefined;
107
- this.meta = undefined;
108
- return;
109
- }
90
+ /**
91
+ * Where this style's cells sat in the output of whatever drew them; see
92
+ * `./anchor.ts`. It draws nothing, and it makes two styles that draw alike
93
+ * unequal, because they came from different places.
94
+ */
95
+ anchor;
96
+ constructor(options = {}) {
110
97
  this.color = resolveColor(options.color);
111
98
  this.bgcolor = resolveColor(options.bgcolor);
112
99
  this.bold = options.bold;
@@ -124,6 +111,7 @@ export class Style {
124
111
  this.overline = options.overline;
125
112
  this.link = options.link;
126
113
  this.meta = options.meta;
114
+ this.anchor = options.anchor;
127
115
  }
128
116
  get isNull() {
129
117
  return (this.color === undefined &&
@@ -142,7 +130,8 @@ export class Style {
142
130
  this.encircle === undefined &&
143
131
  this.overline === undefined &&
144
132
  this.link === undefined &&
145
- this.meta === undefined);
133
+ this.meta === undefined &&
134
+ this.anchor === undefined);
146
135
  }
147
136
  get transparentBackground() {
148
137
  return this.bgcolor === undefined || this.bgcolor.isDefault;
@@ -150,68 +139,40 @@ export class Style {
150
139
  get backgroundStyle() {
151
140
  return new Style({ bgcolor: this.bgcolor });
152
141
  }
142
+ /**
143
+ * This style with `overrides` in place of its own fields.
144
+ *
145
+ * [LAW:one-source-of-truth] The field list is the class's own fields, so a
146
+ * field added to `Style` is carried by every copy without being named here.
147
+ */
148
+ with(overrides) {
149
+ return new Style({ ...this, ...overrides });
150
+ }
153
151
  get withoutColor() {
154
- return new Style({
155
- bold: this.bold,
156
- dim: this.dim,
157
- italic: this.italic,
158
- underline: this.underline,
159
- blink: this.blink,
160
- blink2: this.blink2,
161
- reverse: this.reverse,
162
- conceal: this.conceal,
163
- strike: this.strike,
164
- underline2: this.underline2,
165
- frame: this.frame,
166
- encircle: this.encircle,
167
- overline: this.overline,
168
- link: this.link,
169
- meta: this.meta,
170
- });
152
+ return this.with({ color: undefined, bgcolor: undefined });
171
153
  }
172
154
  // [LAW:types-are-the-program] Structural clone helper — pure rearrangement
173
155
  // of fields, no policy. Sanitization of the URL is the trust boundary's
174
156
  // job (see RichText in text.ts); Style stays a faithful container so
175
157
  // callers that have already sanitized are not silently re-mutated.
176
158
  withLink(link) {
177
- return new Style({
178
- color: this.color,
179
- bgcolor: this.bgcolor,
180
- bold: this.bold,
181
- dim: this.dim,
182
- italic: this.italic,
183
- underline: this.underline,
184
- blink: this.blink,
185
- blink2: this.blink2,
186
- reverse: this.reverse,
187
- conceal: this.conceal,
188
- strike: this.strike,
189
- underline2: this.underline2,
190
- frame: this.frame,
191
- encircle: this.encircle,
192
- overline: this.overline,
193
- link,
194
- meta: this.meta,
195
- });
159
+ return this.with({ link });
196
160
  }
197
161
  clearMetaAndLinks() {
198
- return new Style({
199
- color: this.color,
200
- bgcolor: this.bgcolor,
201
- bold: this.bold,
202
- dim: this.dim,
203
- italic: this.italic,
204
- underline: this.underline,
205
- blink: this.blink,
206
- blink2: this.blink2,
207
- reverse: this.reverse,
208
- conceal: this.conceal,
209
- strike: this.strike,
210
- underline2: this.underline2,
211
- frame: this.frame,
212
- encircle: this.encircle,
213
- overline: this.overline,
214
- });
162
+ return this.with({ link: undefined, meta: undefined });
163
+ }
164
+ /**
165
+ * The same look, drawn from the place `anchor` names, or from nowhere.
166
+ */
167
+ withAnchor(anchor) {
168
+ return this.with({ anchor });
169
+ }
170
+ /**
171
+ * This style on the cell `cells` to the right: its anchor shifted along its
172
+ * row, or the style itself when it has no anchor.
173
+ */
174
+ shiftedBy(cells) {
175
+ return this.anchor ? this.withAnchor(shiftAnchor(this.anchor, cells)) : this;
215
176
  }
216
177
  /**
217
178
  * Merges two styles. `other`'s values take priority.
@@ -241,6 +202,7 @@ export class Style {
241
202
  meta: other.meta && this.meta
242
203
  ? { ...this.meta, ...other.meta }
243
204
  : other.meta ?? this.meta,
205
+ anchor: other.anchor ?? this.anchor,
244
206
  });
245
207
  }
246
208
  equals(other) {
@@ -261,7 +223,8 @@ export class Style {
261
223
  this.frame === other.frame &&
262
224
  this.encircle === other.encircle &&
263
225
  this.overline === other.overline &&
264
- this.link === other.link);
226
+ this.link === other.link &&
227
+ anchorsEqual(this.anchor, other.anchor));
265
228
  }
266
229
  /**
267
230
  * The colours this style puts on screen at `colorSystem`: each flattened
@@ -297,18 +260,17 @@ export class Style {
297
260
  toSgrCodes(colorSystem) {
298
261
  if (this.isNull)
299
262
  return "";
300
- const attrs = [];
263
+ // Attributes, then foreground, then background: Python Rich 9d8f9a3's
264
+ // `_make_ansi_codes` order, so a byte pinned against the reference holds.
265
+ // An attribute set false writes nothing, as there. It overrides an
266
+ // inherited attribute when styles combine, and every styled run is written
267
+ // after a reset, so no off code is ever needed.
301
268
  const drawn = this.drawnColors(colorSystem);
302
- if (drawn.color)
303
- attrs.push(...drawn.color.getAnsiCodes(true));
304
- if (drawn.bgcolor)
305
- attrs.push(...drawn.bgcolor.getAnsiCodes(false));
306
- // An attribute set false writes nothing, as in Python Rich 9d8f9a3's
307
- // `_make_ansi_codes`. It overrides an inherited attribute when styles
308
- // combine, and every styled run is written after a reset, so no off
309
- // code is ever needed.
310
- attrs.push(...ATTRIBUTE_NAMES.filter((name) => this[name] === true).map((name) => `${ATTRIBUTE_SGR[name]}`));
311
- return attrs.join(";");
269
+ return [
270
+ ...ATTRIBUTE_NAMES.filter((name) => this[name] === true).map((name) => `${ATTRIBUTE_SGR[name]}`),
271
+ ...(drawn.color?.getAnsiCodes(true) ?? []),
272
+ ...(drawn.bgcolor?.getAnsiCodes(false) ?? []),
273
+ ].join(";");
312
274
  }
313
275
  /**
314
276
  * Serializes to a canonical string that round-trips through `Style.parse()`.
@@ -680,6 +642,9 @@ export const DEFAULT_STYLES = {
680
642
  "bar.complete": Style.parse("magenta"),
681
643
  "bar.finished": Style.parse("green"),
682
644
  "bar.pulse": Style.parse("magenta"),
645
+ // Scrollbar
646
+ "scrollbar.thumb": NULL_STYLE,
647
+ "scrollbar.track": Style.parse("grey37"),
683
648
  // Tree
684
649
  "tree": NULL_STYLE,
685
650
  "tree.guide": NULL_STYLE,
@@ -721,3 +686,10 @@ export const DEFAULT_STYLES = {
721
686
  * a renderable sees outside a `Console`, as in `renderToString`.
722
687
  */
723
688
  export const DEFAULT_THEME = new Theme();
689
+ function anchorsEqual(a, b) {
690
+ if (a === b)
691
+ return true;
692
+ if (!a || !b)
693
+ return false;
694
+ return a.owner === b.owner && a.row === b.row && a.col === b.col && anchorsEqual(a.inner, b.inner);
695
+ }
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { Segment } from "./segment.js";
5
5
  import { Style } from "./style.js";
6
- import type { Renderable, Measurable, RenderOptions } from "./protocol.js";
6
+ import type { Renderable, Measurable, OverflowMethod, RenderOptions } from "./protocol.js";
7
+ export declare function stripControlChars(text: string): string;
7
8
  export declare class Span {
8
9
  readonly start: number;
9
10
  readonly end: number;
@@ -19,7 +20,7 @@ export declare class Span {
19
20
  export interface RichTextOptions {
20
21
  style?: string | Style;
21
22
  justify?: "left" | "center" | "right" | "full";
22
- overflow?: "fold" | "crop" | "ellipsis";
23
+ overflow?: OverflowMethod;
23
24
  end?: string;
24
25
  tabSize?: number;
25
26
  noWrap?: boolean;
@@ -44,8 +45,8 @@ export declare class RichText implements Renderable, Measurable {
44
45
  set style(value: string | Style);
45
46
  get justify(): "left" | "center" | "right" | "full" | undefined;
46
47
  set justify(value: "left" | "center" | "right" | "full" | undefined);
47
- get overflow(): "fold" | "crop" | "ellipsis" | undefined;
48
- set overflow(value: "fold" | "crop" | "ellipsis" | undefined);
48
+ get overflow(): OverflowMethod | undefined;
49
+ set overflow(value: OverflowMethod | undefined);
49
50
  get end(): string;
50
51
  set end(value: string);
51
52
  get noWrap(): boolean;
@@ -114,7 +115,10 @@ export declare class RichText implements Renderable, Measurable {
114
115
  * in the middle.
115
116
  *
116
117
  * Marker default is `"\u2026"`. Pass `marker: ""` for raw cropping without an
117
- * indicator glyph.
118
+ * indicator glyph. A marker wider than `width` is itself cut to the glyphs
119
+ * that fit, and the text takes whatever cells that leaves — all of them, when
120
+ * not one glyph of the marker fits — so the result is never wider than asked.
121
+ * Every cut, marker and text alike, falls between grapheme clusters.
118
122
  *
119
123
  * Spans are preserved through the cut: characters that survive keep their
120
124
  * styling; the marker (if any) is inserted as plain text with no span.
@@ -130,10 +134,6 @@ export declare class RichText implements Renderable, Measurable {
130
134
  }): this;
131
135
  private _cropRightTo;
132
136
  private _cropLeftTo;
133
- /** Number of char-index code units that fit within `targetWidth` cell columns from the left. */
134
- private _cellPrefixCharLength;
135
- /** Char-index at which the suffix of `targetWidth` cell columns starts. */
136
- private _cellSuffixStartCharIndex;
137
137
  align(justify: "left" | "center" | "right", width: number): this;
138
138
  removeSuffix(suffix: string): this;
139
139
  appendTokens(tokens: Array<[string, (string | Style)?]>): this;
@@ -150,9 +150,8 @@ export declare class RichText implements Renderable, Measurable {
150
150
  * `{{ red "x" }}{{ blue "y" }}` evaluates to two fragments — one with
151
151
  * wrapping `style = red`, one with `blue` — and consumers that want a
152
152
  * single styled string for downstream rendering need both styles
153
- * preserved as spans on the concatenated result. The plain `append()`
154
- * propagates spans only, so this static does the additional work of
155
- * lifting `frag.style` into a span before appending.
153
+ * preserved as spans on the concatenated result, which is what `append()`
154
+ * does with each fragment.
156
155
  *
157
156
  * Empty input → empty `RichText` with `end: ""`. Caller can override
158
157
  * `end` (defaults to `""` — the engine-output case rarely wants a
@@ -247,9 +246,11 @@ export declare class RichText implements Renderable, Measurable {
247
246
  *
248
247
  * Everything reaching here already survived wrapping, so the only text still
249
248
  * too wide is text no break could help: a word longer than the canvas under
250
- * a non-folding overflow method, a glyph wider than the budget, or a canvas
251
- * with no cells at all. That is what makes the overflow method a last
252
- * resort rather than the first thing a long cell meets.
249
+ * a non-folding overflow method, a glyph wider than the budget, a canvas
250
+ * with no cells at all, or a `noWrap` line, which no break was allowed to
251
+ * help. That is what makes the overflow method a last resort rather than the
252
+ * first thing a long cell meets. `"fold"` has nothing left to fold here and
253
+ * cuts as `"crop"` does, as the reference's `truncate` does.
253
254
  */
254
255
  private _fitLine;
255
256
  }