@promptctl/rich-js 0.18.1 → 0.19.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 (150) hide show
  1. package/README.md +9 -7
  2. package/dist/core/box.d.ts +3 -1
  3. package/dist/core/box.js +6 -13
  4. package/dist/core/cells.d.ts +61 -28
  5. package/dist/core/cells.js +171 -124
  6. package/dist/core/clock.d.ts +48 -0
  7. package/dist/core/clock.js +42 -0
  8. package/dist/core/color.d.ts +98 -18
  9. package/dist/core/color.js +331 -144
  10. package/dist/core/console.d.ts +38 -7
  11. package/dist/core/console.js +159 -90
  12. package/dist/core/easing.d.ts +86 -0
  13. package/dist/core/easing.js +171 -0
  14. package/dist/core/env.d.ts +16 -0
  15. package/dist/core/env.js +16 -0
  16. package/dist/core/export-html.js +1 -4
  17. package/dist/core/export-lines.d.ts +22 -13
  18. package/dist/core/export-lines.js +37 -19
  19. package/dist/core/export-svg.d.ts +49 -0
  20. package/dist/core/export-svg.js +213 -0
  21. package/dist/core/fnv1a.d.ts +10 -0
  22. package/dist/core/fnv1a.js +17 -0
  23. package/dist/core/highlighter.d.ts +2 -2
  24. package/dist/core/highlighter.js +113 -47
  25. package/dist/core/json.js +1 -1
  26. package/dist/core/markup.d.ts +28 -7
  27. package/dist/core/markup.js +235 -312
  28. package/dist/core/measure.d.ts +3 -3
  29. package/dist/core/measure.js +12 -7
  30. package/dist/core/memo.d.ts +19 -0
  31. package/dist/core/memo.js +34 -0
  32. package/dist/core/osc8.d.ts +6 -6
  33. package/dist/core/osc8.js +9 -21
  34. package/dist/core/paint.d.ts +107 -0
  35. package/dist/core/paint.js +150 -0
  36. package/dist/core/place.d.ts +1 -1
  37. package/dist/core/place.js +4 -6
  38. package/dist/core/pretty.d.ts +105 -2
  39. package/dist/core/pretty.js +527 -97
  40. package/dist/core/protocol.d.ts +41 -3
  41. package/dist/core/protocol.js +20 -0
  42. package/dist/core/render.d.ts +33 -30
  43. package/dist/core/render.js +43 -83
  44. package/dist/core/segment.d.ts +6 -5
  45. package/dist/core/segment.js +11 -15
  46. package/dist/core/strip.d.ts +13 -2
  47. package/dist/core/strip.js +44 -23
  48. package/dist/core/style.d.ts +27 -7
  49. package/dist/core/style.js +97 -34
  50. package/dist/core/text.d.ts +43 -16
  51. package/dist/core/text.js +180 -59
  52. package/dist/core/wrap.d.ts +5 -0
  53. package/dist/core/wrap.js +16 -3
  54. package/dist/host/app.d.ts +31 -19
  55. package/dist/host/app.js +90 -60
  56. package/dist/host/index.d.ts +2 -1
  57. package/dist/host/terminal-host.d.ts +3 -2
  58. package/dist/index.d.ts +17 -10
  59. package/dist/index.js +11 -4
  60. package/dist/node/prompt.d.ts +13 -0
  61. package/dist/node/prompt.js +30 -9
  62. package/dist/node/save.d.ts +4 -3
  63. package/dist/node/save.js +6 -3
  64. package/dist/node/terminal-host.d.ts +3 -2
  65. package/dist/node/traceback.d.ts +2 -1
  66. package/dist/node/traceback.js +3 -39
  67. package/dist/renderables/align.js +2 -9
  68. package/dist/renderables/columns.d.ts +45 -29
  69. package/dist/renderables/columns.js +142 -103
  70. package/dist/renderables/constrain.js +6 -12
  71. package/dist/renderables/effect.d.ts +98 -0
  72. package/dist/renderables/effect.js +160 -0
  73. package/dist/renderables/embed.d.ts +87 -30
  74. package/dist/renderables/embed.js +134 -45
  75. package/dist/renderables/group.d.ts +6 -3
  76. package/dist/renderables/group.js +5 -0
  77. package/dist/renderables/layout.d.ts +6 -23
  78. package/dist/renderables/layout.js +24 -113
  79. package/dist/renderables/live.d.ts +40 -16
  80. package/dist/renderables/live.js +96 -83
  81. package/dist/renderables/markdown.d.ts +16 -2
  82. package/dist/renderables/markdown.js +521 -118
  83. package/dist/renderables/padding.d.ts +22 -1
  84. package/dist/renderables/padding.js +30 -24
  85. package/dist/renderables/panel.d.ts +31 -33
  86. package/dist/renderables/panel.js +137 -141
  87. package/dist/renderables/progress.d.ts +104 -16
  88. package/dist/renderables/progress.js +205 -81
  89. package/dist/renderables/progressBar.d.ts +2 -2
  90. package/dist/renderables/progressBar.js +30 -19
  91. package/dist/renderables/prompt.d.ts +37 -15
  92. package/dist/renderables/prompt.js +125 -85
  93. package/dist/renderables/ratio.d.ts +123 -0
  94. package/dist/renderables/ratio.js +213 -0
  95. package/dist/renderables/rule.d.ts +1 -0
  96. package/dist/renderables/rule.js +44 -48
  97. package/dist/renderables/spinner.d.ts +28 -2
  98. package/dist/renderables/spinner.js +52 -19
  99. package/dist/renderables/status.d.ts +13 -7
  100. package/dist/renderables/status.js +15 -29
  101. package/dist/renderables/syntax.d.ts +37 -7
  102. package/dist/renderables/syntax.js +134 -83
  103. package/dist/renderables/table.d.ts +58 -20
  104. package/dist/renderables/table.js +370 -192
  105. package/dist/renderables/traceback.d.ts +5 -4
  106. package/dist/renderables/traceback.js +239 -68
  107. package/dist/renderables/tree.d.ts +10 -2
  108. package/dist/renderables/tree.js +61 -34
  109. package/dist/renderables/viewport.js +2 -4
  110. package/dist/template-bindings/color-funcs.d.ts +31 -9
  111. package/dist/template-bindings/color-funcs.js +40 -14
  112. package/dist/template-bindings/helpers.d.ts +4 -2
  113. package/dist/template-bindings/helpers.js +5 -20
  114. package/dist/template-bindings/index.d.ts +29 -17
  115. package/dist/template-bindings/index.js +36 -20
  116. package/dist/template-bindings/palette-funcs.d.ts +3 -1
  117. package/dist/template-bindings/palette-funcs.js +11 -7
  118. package/dist/template-bindings/style-funcs.js +4 -2
  119. package/dist/themes/buildPalette.d.ts +24 -3
  120. package/dist/themes/buildPalette.js +52 -8
  121. package/dist/themes/colorMath.d.ts +29 -17
  122. package/dist/themes/colorMath.js +74 -48
  123. package/dist/themes/colorRef.d.ts +2 -17
  124. package/dist/themes/colorRef.js +13 -31
  125. package/dist/themes/palette.d.ts +24 -0
  126. package/dist/themes/palette.js +46 -3
  127. package/dist/themes/ramp.d.ts +21 -34
  128. package/dist/themes/ramp.js +23 -42
  129. package/dist/themes/registry.js +39 -28
  130. package/dist/widgets/button.d.ts +2 -6
  131. package/dist/widgets/button.js +9 -40
  132. package/dist/widgets/checkbox.d.ts +2 -5
  133. package/dist/widgets/checkbox.js +9 -31
  134. package/dist/widgets/dropdown.d.ts +2 -5
  135. package/dist/widgets/dropdown.js +14 -48
  136. package/dist/widgets/ink.d.ts +22 -0
  137. package/dist/widgets/ink.js +31 -0
  138. package/dist/widgets/slider.d.ts +2 -5
  139. package/dist/widgets/slider.js +11 -34
  140. package/dist/widgets/text-input.d.ts +19 -10
  141. package/dist/widgets/text-input.js +156 -147
  142. package/dist/widgets/themed-widget.d.ts +20 -0
  143. package/dist/widgets/themed-widget.js +89 -0
  144. package/dist/widgets/toggle.d.ts +2 -5
  145. package/dist/widgets/toggle.js +8 -31
  146. package/dist/widgets/widget-app.d.ts +2 -0
  147. package/dist/widgets/widget-app.js +13 -6
  148. package/dist/widgets/widget-base.d.ts +2 -0
  149. package/dist/widgets/widget-base.js +9 -2
  150. package/package.json +11 -1
@@ -16,6 +16,7 @@ export declare class Rule implements Renderable, Measurable {
16
16
  readonly characters: string;
17
17
  readonly align: RuleAlign;
18
18
  readonly style: string | Style;
19
+ private readonly _label;
19
20
  constructor(title?: string | RichText, options?: RuleOptions);
20
21
  render(options: RenderOptions): Iterable<Segment>;
21
22
  measure(_options: RenderOptions): {
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Rule — a horizontal divider line, optionally with a centered title.
3
3
  */
4
- import { cellLen } from "../core/cells.js";
4
+ import { asCellCol, cellLen, setCellSize } from "../core/cells.js";
5
5
  import { Segment } from "../core/segment.js";
6
6
  import { NULL_STYLE } from "../core/style.js";
7
7
  import { drawable, getStyle } from "../core/protocol.js";
8
- import { inlineLabel } from "./embed.js";
8
+ import { cutLabel, drawLabel, inlineLabel } from "./embed.js";
9
9
  const ASCII_RULE_CHAR = "-";
10
10
  const DEFAULT_RULE_CHAR = "\u2500"; // ─
11
11
  export class Rule {
@@ -13,16 +13,20 @@ export class Rule {
13
13
  characters;
14
14
  align;
15
15
  style;
16
+ _label;
16
17
  constructor(title, options) {
17
18
  const chars = options?.characters ?? DEFAULT_RULE_CHAR;
18
- if (chars.length === 0) {
19
- throw new Error("Rule characters must not be empty");
19
+ // [LAW:parse-dont-validate] Rich's rule: a rule drawn from zero-width
20
+ // characters would advance no cells, so `repeatToWidth` never meets one.
21
+ if (cellLen(chars) < 1) {
22
+ throw new Error("Rule characters must have a cell width of at least 1");
20
23
  }
21
24
  const align = options?.align;
22
25
  if (align !== undefined && align !== "left" && align !== "center" && align !== "right") {
23
26
  throw new Error(`Invalid align value: "${align}"`);
24
27
  }
25
28
  this.title = title;
29
+ this._label = inlineLabel(title);
26
30
  this.characters = chars;
27
31
  this.align = align ?? "center";
28
32
  this.style = options?.style ?? NULL_STYLE;
@@ -32,34 +36,42 @@ export class Rule {
32
36
  const ruleChar = drawable(options, this.characters, ASCII_RULE_CHAR);
33
37
  const style = getStyle(options, this.style);
34
38
  const ruleStyle = style.isNull ? undefined : style;
35
- const title = inlineLabel(this.title, options, ruleStyle);
36
- const titleWidth = Segment.getLineLength(title);
37
- if (titleWidth === 0) {
38
- // No title — just a line of repeated characters
39
- yield new Segment(repeatToWidth(ruleChar, maxWidth), ruleStyle);
40
- yield Segment.line();
41
- return;
42
- }
43
- if (titleWidth >= maxWidth) {
44
- // Title fills the whole width; adjustLineLength cuts by cells, not code units.
45
- yield* Segment.adjustLineLength(title, maxWidth, ruleStyle);
39
+ const line = (width) => new Segment(repeatToWidth(ruleChar, width), ruleStyle);
40
+ // Rich's `required_space`: a centred title keeps a rule cell and a space
41
+ // either side of it, an aligned one a space and a rule cell on its open
42
+ // side. A rule with no room past that draws no title, as one without a title.
43
+ const room = Math.max(0, maxWidth - (this.align === "center" ? 4 : 2));
44
+ if (this._label === undefined || room === 0) {
45
+ yield line(maxWidth);
46
46
  yield Segment.line();
47
47
  return;
48
48
  }
49
- const remaining = maxWidth - titleWidth;
50
- // [LAW:dataflow-not-control-flow] Always compute both sides; alignment determines distribution
51
- const leftWidth = this.align === "right"
52
- ? remaining
53
- : this.align === "center"
54
- ? Math.floor(remaining / 2)
55
- : 0;
56
- const rightWidth = remaining - leftWidth;
57
- if (leftWidth > 0) {
58
- yield new Segment(repeatToWidth(ruleChar, leftWidth), ruleStyle);
59
- }
60
- yield* title;
61
- if (rightWidth > 0) {
62
- yield new Segment(repeatToWidth(ruleChar, rightWidth), ruleStyle);
49
+ const text = this._label.text(options);
50
+ cutLabel(text, room, drawable(options, "\u2026", "."));
51
+ const title = drawLabel(text, options, ruleStyle);
52
+ const titleWidth = Segment.getLineLength(title);
53
+ // As Rich lays the line out: the gaps of a centred title are the rule's,
54
+ // styled with it; an aligned title's one gap is unstyled.
55
+ switch (this.align) {
56
+ case "center": {
57
+ const left = Math.floor((maxWidth - titleWidth) / 2) - 1;
58
+ yield line(left);
59
+ yield new Segment(" ", ruleStyle);
60
+ yield* title;
61
+ yield new Segment(" ", ruleStyle);
62
+ yield line(maxWidth - left - titleWidth - 2);
63
+ break;
64
+ }
65
+ case "left":
66
+ yield* title;
67
+ yield new Segment(" ");
68
+ yield line(maxWidth - titleWidth - 1);
69
+ break;
70
+ case "right":
71
+ yield line(maxWidth - titleWidth - 1);
72
+ yield new Segment(" ");
73
+ yield* title;
74
+ break;
63
75
  }
64
76
  yield Segment.line();
65
77
  }
@@ -67,24 +79,8 @@ export class Rule {
67
79
  return { minimum: 1, maximum: _options.maxWidth };
68
80
  }
69
81
  }
82
+ // [LAW:one-source-of-truth] `setCellSize` crops between the grapheme clusters
83
+ // `cellLen` measures, so `❤️` or `👨‍👩‍👧` is never cut in half or over-counted.
70
84
  function repeatToWidth(char, width) {
71
- const charWidth = cellLen(char);
72
- if (charWidth === 0)
73
- return " ".repeat(width);
74
- const repeats = Math.ceil(width / charWidth);
75
- const full = char.repeat(repeats);
76
- // Trim to exact width
77
- let w = 0;
78
- let i = 0;
79
- for (const c of full) {
80
- const cw = cellLen(c);
81
- if (w + cw > width)
82
- break;
83
- w += cw;
84
- i += c.length;
85
- }
86
- const result = full.slice(0, i);
87
- // Pad if needed (wide char couldn't fill exact width)
88
- const gap = width - w;
89
- return gap > 0 ? result + " ".repeat(gap) : result;
85
+ return setCellSize(char.repeat(Math.ceil(width / cellLen(char))), asCellCol(width));
90
86
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { Segment } from "../core/segment.js";
5
5
  import { Style } from "../core/style.js";
6
+ import { RichText } from "../core/text.js";
6
7
  import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
7
8
  export interface SpinnerOptions {
8
9
  speed?: number;
@@ -10,20 +11,45 @@ export interface SpinnerOptions {
10
11
  }
11
12
  export declare class Spinner implements Renderable, Measurable {
12
13
  readonly name: string;
13
- readonly text: string | undefined;
14
14
  readonly speed: number;
15
15
  readonly style: string | Style;
16
+ /**
17
+ * The label drawn after the frame. It is read through `embed`'s one crossing
18
+ * each time it is drawn, so a string is markup under the console that draws
19
+ * it, as a string handed to `print` is — where Rich's `Spinner` parses it
20
+ * whatever the console says — and a `RichText` changed in place draws as
21
+ * changed, as the `Text` Rich keeps does. [LAW:single-enforcer]
22
+ */
23
+ text: string | RichText;
16
24
  private readonly _data;
17
25
  private _frameIndex;
18
26
  private _lastUpdate;
19
- constructor(name?: string, text?: string, options?: SpinnerOptions);
27
+ constructor(name?: string, text?: string | RichText, options?: SpinnerOptions);
20
28
  get frames(): readonly string[];
21
29
  get interval(): number;
22
30
  /** Advance the frame count by the time elapsed, and return it. */
23
31
  private _advance;
24
32
  /** The frames this output can draw: the `line` spinner stands in for any other on an ASCII-only one. */
25
33
  private _drawnFrames;
34
+ /**
35
+ * The current frame and its label as one line wrapped to the width and
36
+ * ended, as the reference's `Text` is — printed, stacked in a `Group`, or
37
+ * laid out in a `Progress` cell alike.
38
+ */
26
39
  render(options: RenderOptions): Iterable<Segment>;
40
+ private _currentLine;
41
+ /**
42
+ * `frame` and the label as one text, as Rich's `Text.assemble(frame, " ",
43
+ * text)`: a long label wraps as one line with its frame. A label that draws
44
+ * nothing — the default `""`, or markup that styles nothing — is no label,
45
+ * not a trailing space, as an empty `Text` is falsy there.
46
+ */
47
+ private _line;
48
+ /**
49
+ * Measured at its widest frame, so a column holding it never narrows as it
50
+ * turns, and never offered less than that frame: a frame is one picture,
51
+ * which the reference's `Text` measure would let wrap apart at its spaces.
52
+ */
27
53
  measure(options: RenderOptions): {
28
54
  minimum: number;
29
55
  maximum: number;
@@ -4,18 +4,27 @@
4
4
  import { cellLen } from "../core/cells.js";
5
5
  import { Segment } from "../core/segment.js";
6
6
  import { NULL_STYLE } from "../core/style.js";
7
+ import { RichText } from "../core/text.js";
7
8
  import { SPINNERS, DEFAULT_SPINNER, LINE_SPINNER } from "../core/spinnerData.js";
8
- import { drawable, getStyle } from "../core/protocol.js";
9
+ import { drawable } from "../core/protocol.js";
10
+ import { EmbeddedText } from "./embed.js";
9
11
  const spinnerGlyphs = ({ frames }) => frames.join("");
10
12
  export class Spinner {
11
13
  name;
12
- text;
13
14
  speed;
14
15
  style;
16
+ /**
17
+ * The label drawn after the frame. It is read through `embed`'s one crossing
18
+ * each time it is drawn, so a string is markup under the console that draws
19
+ * it, as a string handed to `print` is — where Rich's `Spinner` parses it
20
+ * whatever the console says — and a `RichText` changed in place draws as
21
+ * changed, as the `Text` Rich keeps does. [LAW:single-enforcer]
22
+ */
23
+ text;
15
24
  _data;
16
25
  _frameIndex;
17
26
  _lastUpdate;
18
- constructor(name, text, options) {
27
+ constructor(name, text = "", options) {
19
28
  const spinnerName = name ?? DEFAULT_SPINNER;
20
29
  const data = SPINNERS[spinnerName];
21
30
  if (!data) {
@@ -38,11 +47,12 @@ export class Spinner {
38
47
  /** Advance the frame count by the time elapsed, and return it. */
39
48
  _advance() {
40
49
  const now = Date.now();
41
- const elapsed = now - this._lastUpdate;
42
- const effectiveInterval = this.interval / this.speed;
43
- if (elapsed >= effectiveInterval) {
44
- const steps = Math.floor(elapsed / effectiveInterval);
45
- this._frameIndex = (this._frameIndex + steps) % this._data.frames.length;
50
+ // Whole frames passed at this speed; a negative speed steps backwards, as
51
+ // Rich's does, so the index wraps with a modulo that is never negative.
52
+ const steps = Math.trunc(((now - this._lastUpdate) * this.speed) / this.interval);
53
+ if (steps !== 0) {
54
+ const count = this._data.frames.length;
55
+ this._frameIndex = (((this._frameIndex + steps) % count) + count) % count;
46
56
  this._lastUpdate = now;
47
57
  }
48
58
  return this._frameIndex;
@@ -51,20 +61,43 @@ export class Spinner {
51
61
  _drawnFrames(options) {
52
62
  return drawable(options, this._data, LINE_SPINNER, spinnerGlyphs).frames;
53
63
  }
64
+ /**
65
+ * The current frame and its label as one line wrapped to the width and
66
+ * ended, as the reference's `Text` is — printed, stacked in a `Group`, or
67
+ * laid out in a `Progress` cell alike.
68
+ */
54
69
  *render(options) {
70
+ yield* this._currentLine(options).render(options);
71
+ yield Segment.line();
72
+ }
73
+ _currentLine(options) {
55
74
  const frames = this._drawnFrames(options);
56
- const frame = frames[this._advance() % frames.length];
57
- const style = getStyle(options, this.style);
58
- const spinStyle = style.isNull ? undefined : style;
59
- yield new Segment(frame, spinStyle);
60
- if (this.text) {
61
- yield new Segment(` ${this.text}`);
62
- }
75
+ return this._line(frames[this._advance() % frames.length], options);
76
+ }
77
+ /**
78
+ * `frame` and the label as one text, as Rich's `Text.assemble(frame, " ",
79
+ * text)`: a long label wraps as one line with its frame. A label that draws
80
+ * nothing — the default `""`, or markup that styles nothing — is no label,
81
+ * not a trailing space, as an empty `Text` is falsy there.
82
+ */
83
+ _line(frame, options) {
84
+ const line = new RichText("", { end: "" }).append(frame, this.style);
85
+ // Unhighlighted: Rich's label is a `Text` from the moment it is handed over, and a `Text` is never highlighted.
86
+ const label = new EmbeddedText(this.text).text({ ...options, highlight: false });
87
+ if (label.plain !== "")
88
+ line.append(" ").append(label);
89
+ return line;
63
90
  }
91
+ /**
92
+ * Measured at its widest frame, so a column holding it never narrows as it
93
+ * turns, and never offered less than that frame: a frame is one picture,
94
+ * which the reference's `Text` measure would let wrap apart at its spaces.
95
+ */
64
96
  measure(options) {
65
- const frameWidth = Math.max(...this._drawnFrames(options).map((f) => cellLen(f)));
66
- const textWidth = this.text ? cellLen(this.text) + 1 : 0;
67
- const total = frameWidth + textWidth;
68
- return { minimum: frameWidth, maximum: total };
97
+ const frames = this._drawnFrames(options);
98
+ const widths = frames.map((frame) => cellLen(frame));
99
+ const frameWidth = Math.max(...widths);
100
+ const { minimum, maximum } = this._line(frames[widths.indexOf(frameWidth)], options).measure(options);
101
+ return { minimum: Math.max(minimum, Math.min(frameWidth, maximum)), maximum };
69
102
  }
70
103
  }
@@ -1,23 +1,29 @@
1
1
  /**
2
2
  * Status — displays a spinner animation with a status message.
3
+ *
4
+ * Its renderable is a `Spinner` whose label is the message, as Rich's is, so
5
+ * the message is drawn the way the spinner draws its label: a string is markup
6
+ * under the console that draws it.
3
7
  */
4
- import { Style } from "../core/style.js";
8
+ import type { Style } from "../core/style.js";
9
+ import type { RichText } from "../core/text.js";
5
10
  import { Console } from "../core/console.js";
6
11
  export interface StatusOptions {
7
12
  spinner?: string;
8
13
  speed?: number;
9
- style?: string | Style;
14
+ /** The spinner's frame style; the message styles itself with its markup. */
15
+ spinnerStyle?: string | Style;
10
16
  console?: Console;
11
17
  }
12
18
  export declare class Status {
13
19
  private _live;
14
- private _renderable;
20
+ private _spinner;
15
21
  private _console;
16
- constructor(message: string, options?: StatusOptions);
22
+ constructor(message: string | RichText, options?: StatusOptions);
17
23
  get console(): Console;
18
- get message(): string;
19
- set message(value: string);
24
+ get message(): string | RichText;
25
+ set message(value: string | RichText);
20
26
  start(): void;
21
27
  stop(): void;
22
- update(message: string): void;
28
+ update(message: string | RichText): void;
23
29
  }
@@ -1,38 +1,24 @@
1
1
  /**
2
2
  * Status — displays a spinner animation with a status message.
3
+ *
4
+ * Its renderable is a `Spinner` whose label is the message, as Rich's is, so
5
+ * the message is drawn the way the spinner draws its label: a string is markup
6
+ * under the console that draws it.
3
7
  */
4
- import { Segment } from "../core/segment.js";
5
- import { NULL_STYLE } from "../core/style.js";
6
8
  import { Console } from "../core/console.js";
7
9
  import { Spinner } from "./spinner.js";
8
10
  import { Live } from "./live.js";
9
- import { getStyle } from "../core/protocol.js";
10
- /** A renderable that shows spinner + message */
11
- class StatusRenderable {
12
- message;
13
- _spinner;
14
- _style;
15
- constructor(message, spinner, style) {
16
- this.message = message;
17
- this._spinner = spinner;
18
- this._style = style;
19
- }
20
- *render(options) {
21
- yield* this._spinner.render(options);
22
- const style = getStyle(options, this._style);
23
- const msgStyle = style.isNull ? undefined : style;
24
- yield new Segment(` ${this.message}`, msgStyle);
25
- }
26
- }
27
11
  export class Status {
28
12
  _live;
29
- _renderable;
13
+ _spinner;
30
14
  _console;
31
15
  constructor(message, options) {
32
- this._console = options?.console ?? new Console({ forceTerminal: true });
33
- const spinner = new Spinner(options?.spinner ?? "dots", undefined, { speed: options?.speed });
34
- this._renderable = new StatusRenderable(message, spinner, options?.style ?? NULL_STYLE);
35
- this._live = new Live(this._renderable, {
16
+ this._console = options?.console ?? new Console();
17
+ this._spinner = new Spinner(options?.spinner ?? "dots", message, {
18
+ speed: options?.speed,
19
+ style: options?.spinnerStyle ?? "status.spinner",
20
+ });
21
+ this._live = new Live(this._spinner, {
36
22
  console: this._console,
37
23
  transient: true,
38
24
  refreshPerSecond: 12.5,
@@ -42,10 +28,10 @@ export class Status {
42
28
  return this._console;
43
29
  }
44
30
  get message() {
45
- return this._renderable.message;
31
+ return this._spinner.text;
46
32
  }
47
33
  set message(value) {
48
- this._renderable.message = value;
34
+ this._spinner.text = value;
49
35
  }
50
36
  start() {
51
37
  this._live.start();
@@ -54,7 +40,7 @@ export class Status {
54
40
  this._live.stop();
55
41
  }
56
42
  update(message) {
57
- this._renderable.message = message;
58
- this._live.update(this._renderable, { refresh: true });
43
+ this._spinner.text = message;
44
+ this._live.update(this._spinner, { refresh: true });
59
45
  }
60
46
  }
@@ -1,35 +1,65 @@
1
1
  /**
2
2
  * Syntax — renders source code with basic syntax highlighting.
3
3
  * Uses built-in tokenization (no external dependency).
4
+ *
5
+ * Colours are names in the console's theme (`syntax.keyword`,
6
+ * `syntax.line_number`, …), not a per-instance palette: a theme is how every
7
+ * other renderable here is restyled, and a second styling vocabulary on this
8
+ * one class would be a second answer to "what colour is a keyword".
4
9
  */
5
10
  import { Segment } from "../core/segment.js";
6
- import type { PaddingDimensions } from "./padding.js";
7
11
  import { type Measurable, type Renderable, type RenderOptions } from "../core/protocol.js";
8
12
  export interface SyntaxOptions {
9
13
  lineNumbers?: boolean;
10
- theme?: string;
11
14
  startLine?: number;
12
15
  lineRange?: [number, number];
13
16
  highlightLines?: Set<number>;
14
17
  wordWrap?: boolean;
15
18
  tabSize?: number;
16
- padding?: PaddingDimensions;
17
19
  }
20
+ /**
21
+ * [LAW:one-type-per-behavior] A language is a value here — a grammar — not a
22
+ * code path. The union is the set of languages this module can actually
23
+ * tokenize, so a name it cannot is a compile error rather than a silent plain
24
+ * render; `"text"` is the grammar with no rules.
25
+ */
26
+ declare const GRAMMARS: {
27
+ javascript: RegExp;
28
+ typescript: RegExp;
29
+ python: RegExp;
30
+ bash: RegExp;
31
+ text: RegExp;
32
+ };
33
+ export type SyntaxLanguage = keyof typeof GRAMMARS;
18
34
  export declare class Syntax implements Renderable, Measurable {
19
35
  readonly code: string;
20
- readonly language: string;
36
+ readonly language: SyntaxLanguage;
21
37
  readonly lineNumbers: boolean;
22
38
  readonly startLine: number;
23
39
  readonly lineRange: [number, number] | undefined;
24
40
  readonly highlightLines: Set<number>;
25
41
  readonly wordWrap: boolean;
26
42
  readonly tabSize: number;
27
- constructor(code: string, language?: string, options?: SyntaxOptions);
43
+ /** The code with its tabs expanded, as lines — what both render and measure read. */
44
+ private readonly _lines;
45
+ constructor(code: string, language?: SyntaxLanguage, options?: SyntaxOptions);
46
+ /**
47
+ * The lines shown, as a 0-based half-open slice of `_lines`, and the number
48
+ * the first is given — render and measure both read them here, so a range
49
+ * counts the same in each. `lineRange` is 1-based and inclusive, and a line
50
+ * keeps the number it has in the whole code: `[3, 4]` shows lines 3 and 4
51
+ * numbered 3 and 4 (with a `startLine` of 1), as Rich does. A range reaching
52
+ * outside the code shows the part inside it, which may be none.
53
+ */
54
+ private _shown;
55
+ /** Digits of the last number, a space, and the `│ ` rule; nothing without numbers. */
56
+ private _gutterWidth;
28
57
  render(options: RenderOptions): Iterable<Segment>;
58
+ private _rows;
59
+ /** The gutter is the narrowest it can be drawn; the widest shown line plus the gutter, the widest. */
29
60
  measure(options: RenderOptions): {
30
61
  minimum: number;
31
62
  maximum: number;
32
63
  };
33
- private _highlight;
34
- static fromPath(_filePath: string, _options?: SyntaxOptions): Syntax;
35
64
  }
65
+ export {};