@promptctl/rich-js 0.18.1 → 0.20.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 (169) hide show
  1. package/README.md +11 -8
  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 +99 -18
  9. package/dist/core/color.js +334 -146
  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/noise.d.ts +31 -0
  33. package/dist/core/noise.js +94 -0
  34. package/dist/core/osc8.d.ts +6 -6
  35. package/dist/core/osc8.js +9 -21
  36. package/dist/core/paint.d.ts +107 -0
  37. package/dist/core/paint.js +161 -0
  38. package/dist/core/place.d.ts +1 -1
  39. package/dist/core/place.js +4 -6
  40. package/dist/core/pretty.d.ts +126 -2
  41. package/dist/core/pretty.js +571 -105
  42. package/dist/core/protocol.d.ts +43 -3
  43. package/dist/core/protocol.js +23 -2
  44. package/dist/core/render.d.ts +33 -30
  45. package/dist/core/render.js +43 -83
  46. package/dist/core/segment.d.ts +6 -5
  47. package/dist/core/segment.js +11 -15
  48. package/dist/core/selection.d.ts +12 -0
  49. package/dist/core/selection.js +14 -0
  50. package/dist/core/strip.d.ts +116 -74
  51. package/dist/core/strip.js +266 -217
  52. package/dist/core/style.d.ts +27 -7
  53. package/dist/core/style.js +106 -41
  54. package/dist/core/text.d.ts +43 -16
  55. package/dist/core/text.js +180 -59
  56. package/dist/core/wrap.d.ts +5 -0
  57. package/dist/core/wrap.js +16 -3
  58. package/dist/host/app.d.ts +95 -17
  59. package/dist/host/app.js +201 -61
  60. package/dist/host/index.d.ts +4 -1
  61. package/dist/host/index.js +1 -0
  62. package/dist/host/input.d.ts +84 -0
  63. package/dist/host/input.js +498 -0
  64. package/dist/host/terminal-host.d.ts +15 -2
  65. package/dist/host/terminal-host.js +2 -0
  66. package/dist/index.d.ts +26 -14
  67. package/dist/index.js +17 -7
  68. package/dist/node/prompt.d.ts +13 -0
  69. package/dist/node/prompt.js +30 -9
  70. package/dist/node/save.d.ts +4 -3
  71. package/dist/node/save.js +6 -3
  72. package/dist/node/terminal-host.d.ts +10 -2
  73. package/dist/node/terminal-host.js +7 -0
  74. package/dist/node/traceback.d.ts +2 -1
  75. package/dist/node/traceback.js +3 -39
  76. package/dist/renderables/align.js +2 -9
  77. package/dist/renderables/columns.d.ts +45 -29
  78. package/dist/renderables/columns.js +142 -103
  79. package/dist/renderables/constrain.js +6 -12
  80. package/dist/renderables/effect.d.ts +113 -0
  81. package/dist/renderables/effect.js +182 -0
  82. package/dist/renderables/effects.d.ts +252 -0
  83. package/dist/renderables/effects.js +565 -0
  84. package/dist/renderables/embed.d.ts +94 -31
  85. package/dist/renderables/embed.js +141 -45
  86. package/dist/renderables/group.d.ts +6 -3
  87. package/dist/renderables/group.js +5 -0
  88. package/dist/renderables/layout.d.ts +6 -23
  89. package/dist/renderables/layout.js +24 -113
  90. package/dist/renderables/list-view.d.ts +42 -0
  91. package/dist/renderables/list-view.js +78 -0
  92. package/dist/renderables/live.d.ts +40 -16
  93. package/dist/renderables/live.js +96 -83
  94. package/dist/renderables/markdown.d.ts +16 -2
  95. package/dist/renderables/markdown.js +521 -118
  96. package/dist/renderables/padding.d.ts +22 -1
  97. package/dist/renderables/padding.js +30 -24
  98. package/dist/renderables/panel.d.ts +31 -33
  99. package/dist/renderables/panel.js +137 -141
  100. package/dist/renderables/progress.d.ts +110 -16
  101. package/dist/renderables/progress.js +212 -81
  102. package/dist/renderables/progressBar.d.ts +24 -4
  103. package/dist/renderables/progressBar.js +78 -23
  104. package/dist/renderables/prompt.d.ts +37 -15
  105. package/dist/renderables/prompt.js +125 -85
  106. package/dist/renderables/ratio.d.ts +123 -0
  107. package/dist/renderables/ratio.js +213 -0
  108. package/dist/renderables/rule.d.ts +3 -2
  109. package/dist/renderables/rule.js +50 -52
  110. package/dist/renderables/spinner.d.ts +28 -2
  111. package/dist/renderables/spinner.js +52 -19
  112. package/dist/renderables/status.d.ts +13 -7
  113. package/dist/renderables/status.js +15 -29
  114. package/dist/renderables/syntax.d.ts +37 -7
  115. package/dist/renderables/syntax.js +134 -83
  116. package/dist/renderables/table.d.ts +58 -20
  117. package/dist/renderables/table.js +373 -197
  118. package/dist/renderables/traceback.d.ts +5 -4
  119. package/dist/renderables/traceback.js +239 -68
  120. package/dist/renderables/tree.d.ts +10 -2
  121. package/dist/renderables/tree.js +61 -34
  122. package/dist/renderables/viewport.d.ts +30 -0
  123. package/dist/renderables/viewport.js +61 -7
  124. package/dist/template-bindings/color-funcs.d.ts +31 -9
  125. package/dist/template-bindings/color-funcs.js +40 -14
  126. package/dist/template-bindings/helpers.d.ts +4 -2
  127. package/dist/template-bindings/helpers.js +5 -20
  128. package/dist/template-bindings/index.d.ts +29 -17
  129. package/dist/template-bindings/index.js +36 -20
  130. package/dist/template-bindings/palette-funcs.d.ts +3 -1
  131. package/dist/template-bindings/palette-funcs.js +11 -7
  132. package/dist/template-bindings/style-funcs.js +4 -2
  133. package/dist/themes/buildPalette.d.ts +24 -3
  134. package/dist/themes/buildPalette.js +52 -8
  135. package/dist/themes/colorMath.d.ts +29 -17
  136. package/dist/themes/colorMath.js +74 -48
  137. package/dist/themes/colorRef.d.ts +2 -17
  138. package/dist/themes/colorRef.js +13 -31
  139. package/dist/themes/palette.d.ts +24 -0
  140. package/dist/themes/palette.js +46 -3
  141. package/dist/themes/ramp.d.ts +21 -34
  142. package/dist/themes/ramp.js +23 -42
  143. package/dist/themes/registry.js +39 -28
  144. package/dist/widgets/button.d.ts +2 -6
  145. package/dist/widgets/button.js +9 -40
  146. package/dist/widgets/checkbox.d.ts +2 -5
  147. package/dist/widgets/checkbox.js +9 -31
  148. package/dist/widgets/dropdown.d.ts +2 -5
  149. package/dist/widgets/dropdown.js +17 -51
  150. package/dist/widgets/event-router.d.ts +31 -40
  151. package/dist/widgets/event-router.js +52 -483
  152. package/dist/widgets/index.d.ts +2 -1
  153. package/dist/widgets/ink.d.ts +22 -0
  154. package/dist/widgets/ink.js +31 -0
  155. package/dist/widgets/slider.d.ts +2 -5
  156. package/dist/widgets/slider.js +11 -34
  157. package/dist/widgets/text-input.d.ts +19 -10
  158. package/dist/widgets/text-input.js +156 -147
  159. package/dist/widgets/themed-widget.d.ts +20 -0
  160. package/dist/widgets/themed-widget.js +89 -0
  161. package/dist/widgets/toggle.d.ts +2 -5
  162. package/dist/widgets/toggle.js +8 -31
  163. package/dist/widgets/types.d.ts +8 -25
  164. package/dist/widgets/types.js +8 -0
  165. package/dist/widgets/widget-app.d.ts +24 -7
  166. package/dist/widgets/widget-app.js +35 -18
  167. package/dist/widgets/widget-base.d.ts +2 -0
  168. package/dist/widgets/widget-base.js +9 -2
  169. package/package.json +31 -17
@@ -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 {};
@@ -1,34 +1,64 @@
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
- import { cellLen } from "../core/cells.js";
10
+ import { cellLen, expandTabs, parseTabSize } from "../core/cells.js";
6
11
  import { Segment } from "../core/segment.js";
7
- import { Style } from "../core/style.js";
8
12
  import { RichText } from "../core/text.js";
9
- import { drawable } from "../core/protocol.js";
10
- // Built-in token patterns for common languages
11
- const TOKEN_PATTERNS = {
12
- _default: [
13
- [/\/\/.*$/gm, "comment"],
14
- [/\/\*[\s\S]*?\*\//g, "comment"],
15
- [/#.*$/gm, "comment"],
16
- [/"(?:[^"\\]|\\.)*"/g, "string"],
17
- [/'(?:[^'\\]|\\.)*'/g, "string"],
18
- [/`(?:[^`\\]|\\.)*`/g, "string"],
19
- [/\b(?:true|false|null|undefined|NaN|Infinity)\b/g, "keyword.constant"],
20
- [/\b(?:function|class|const|let|var|return|if|else|for|while|do|switch|case|break|continue|new|throw|try|catch|finally|import|export|from|default|async|await|yield|of|in|typeof|instanceof|void|delete)\b/g, "keyword"],
21
- [/\b(?:def|lambda|with|as|pass|raise|elif|except|print|self|cls|None|True|False)\b/g, "keyword"],
22
- [/\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/g, "number"],
23
- [/\b0x[0-9a-fA-F]+\b/g, "number"],
24
- ],
25
- };
26
- const TOKEN_STYLES = {
27
- "keyword": Style.parse("bold magenta"),
28
- "keyword.constant": Style.parse("italic bright_magenta"),
29
- "string": Style.parse("green"),
30
- "number": Style.parse("cyan"),
31
- "comment": Style.parse("dim italic"),
13
+ import { drawable, groundedStyle, getStyle, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
14
+ /**
15
+ * One regex per grammar, each token a named alternative. A single left-to-right
16
+ * scan is what keeps a `//` inside a string from starting a comment, and a
17
+ * keyword inside a comment from being drawn as one: at any position the
18
+ * leftmost match wins, and among matches starting there the earliest rule.
19
+ * `(?!)` never matches, so a grammar with no rules is still a regex.
20
+ */
21
+ function grammar(rules) {
22
+ return new RegExp([...rules.map(([token, source]) => `(?<${token}>${source})`), "(?!)"].join("|"), "g");
23
+ }
24
+ // Not after a `.`: `map.get` and `node.type` are properties, not keywords.
25
+ const words = (list) => `(?<!\\.)\\b(?:${list.split(" ").join("|")})\\b`;
26
+ const NUMBER = String.raw `\b(?:0x[0-9a-fA-F]+|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b`;
27
+ const QUOTED = String.raw `"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'`;
28
+ const JS_KEYWORDS = "function class const let var return if else for while do switch case break continue new throw try catch " +
29
+ "finally import export from default async await yield of in typeof instanceof void delete this super extends static";
30
+ const TS_KEYWORDS = "interface type enum implements private public protected readonly as declare namespace keyof satisfies abstract is infer";
31
+ const JAVASCRIPT = [
32
+ ["comment", String.raw `\/\/[^\n]*|\/\*[\s\S]*?\*\/`],
33
+ ["string", String.raw `${QUOTED}|` + "`(?:[^`\\\\]|\\\\.)*`"],
34
+ ["constant", words("true false null undefined NaN Infinity")],
35
+ ["keyword", words(JS_KEYWORDS)],
36
+ ["number", NUMBER],
37
+ ];
38
+ /**
39
+ * [LAW:one-type-per-behavior] A language is a value here — a grammar — not a
40
+ * code path. The union is the set of languages this module can actually
41
+ * tokenize, so a name it cannot is a compile error rather than a silent plain
42
+ * render; `"text"` is the grammar with no rules.
43
+ */
44
+ const GRAMMARS = {
45
+ javascript: grammar(JAVASCRIPT),
46
+ typescript: grammar(JAVASCRIPT.map(([token, source]) => token === "keyword" ? [token, words(`${JS_KEYWORDS} ${TS_KEYWORDS}`)] : [token, source])),
47
+ python: grammar([
48
+ ["comment", "#[^\\n]*"],
49
+ ["string", String.raw `"""[\s\S]*?"""|'''[\s\S]*?'''|${QUOTED}`],
50
+ ["constant", words("True False None")],
51
+ ["keyword", words("def class return if elif else for while in not and or is import from as with try except finally raise " +
52
+ "pass break continue lambda yield global nonlocal assert del async await self cls")],
53
+ ["number", NUMBER],
54
+ ]),
55
+ bash: grammar([
56
+ ["comment", "(?<![^\\s;])#[^\\n]*"],
57
+ ["string", QUOTED],
58
+ ["keyword", words("if then else elif fi for while until do done case esac function in return local export select")],
59
+ ["number", NUMBER],
60
+ ]),
61
+ text: grammar([]),
32
62
  };
33
63
  export class Syntax {
34
64
  code;
@@ -39,77 +69,98 @@ export class Syntax {
39
69
  highlightLines;
40
70
  wordWrap;
41
71
  tabSize;
42
- constructor(code, language, options) {
72
+ /** The code with its tabs expanded, as lines — what both render and measure read. */
73
+ _lines;
74
+ constructor(code, language = "text", options) {
75
+ // A plain-JS caller, or one passing a language read at runtime, gets past
76
+ // the type; left here, the name surfaces at render as a TypeError about
77
+ // `undefined`. [LAW:no-silent-failure] Refused where it is given, by name.
78
+ if (!Object.hasOwn(GRAMMARS, language)) {
79
+ throw new TypeError(`Syntax has no grammar for ${JSON.stringify(language)}: use one of ${Object.keys(GRAMMARS).join(", ")}`);
80
+ }
43
81
  this.code = code;
44
- this.language = language ?? "text";
82
+ this.language = language;
45
83
  this.lineNumbers = options?.lineNumbers ?? false;
46
84
  this.startLine = options?.startLine ?? 1;
47
85
  this.lineRange = options?.lineRange;
48
86
  this.highlightLines = options?.highlightLines ?? new Set();
49
87
  this.wordWrap = options?.wordWrap ?? false;
50
- this.tabSize = options?.tabSize ?? 4;
88
+ const tabSize = parseTabSize(options?.tabSize ?? 4);
89
+ this.tabSize = tabSize;
90
+ this._lines = expandTabs(code, tabSize).text.split("\n");
91
+ }
92
+ /**
93
+ * The lines shown, as a 0-based half-open slice of `_lines`, and the number
94
+ * the first is given — render and measure both read them here, so a range
95
+ * counts the same in each. `lineRange` is 1-based and inclusive, and a line
96
+ * keeps the number it has in the whole code: `[3, 4]` shows lines 3 and 4
97
+ * numbered 3 and 4 (with a `startLine` of 1), as Rich does. A range reaching
98
+ * outside the code shows the part inside it, which may be none.
99
+ */
100
+ _shown() {
101
+ const [start, end] = this.lineRange ?? [1, this._lines.length];
102
+ const from = Math.max(0, start - 1);
103
+ return { first: this.startLine + from, from, to: Math.max(from, end) };
104
+ }
105
+ /** Digits of the last number, a space, and the `│ ` rule; nothing without numbers. */
106
+ _gutterWidth(first, count) {
107
+ return this.lineNumbers ? String(first + count - 1).length + 3 : 0;
51
108
  }
52
109
  *render(options) {
53
- const expandedCode = this.code.replace(/\t/g, " ".repeat(this.tabSize));
54
- let lines = expandedCode.split("\n");
55
- // Apply line range
56
- if (this.lineRange) {
57
- const [start, end] = this.lineRange;
58
- lines = lines.slice(start - 1, end);
59
- }
60
- const lineNumWidth = this.lineNumbers
61
- ? String(this.startLine + lines.length - 1).length + 1
62
- : 0;
63
- // Tokenize the full text for highlighting
64
- const text = new RichText(lines.join("\n"), { end: "" });
65
- this._highlight(text);
66
- const textLines = text.split("\n");
67
- for (let i = 0; i < textLines.length; i++) {
68
- const lineNo = this.startLine + i;
69
- const isHighlighted = this.highlightLines.has(lineNo);
70
- if (this.lineNumbers) {
71
- const numStr = String(lineNo).padStart(lineNumWidth - 1) + " ";
72
- const numStyle = isHighlighted
73
- ? Style.parse("bold on grey27")
74
- : Style.parse("dim");
75
- yield new Segment(numStr, numStyle);
76
- yield new Segment(drawable(options, "│ ", "| "), Style.parse("dim"));
77
- }
78
- const line = textLines[i];
79
- const segs = [...line.render({ ...options, maxWidth: options.maxWidth - lineNumWidth - 2 })];
80
- // Filter out trailing newline from render
81
- for (const seg of segs) {
82
- if (seg.text !== "\n")
83
- yield seg;
84
- }
85
- yield Segment.line();
86
- }
110
+ const bounded = withBoundedWidth(options, this);
111
+ // A gutter wider than the offer is cut at its edge like any other row.
112
+ yield* Segment.cropLines(this._rows(bounded), bounded.maxWidth);
87
113
  }
88
- measure(options) {
89
- const lines = this.code.split("\n");
90
- let maxLine = 0;
91
- for (const line of lines) {
92
- maxLine = Math.max(maxLine, cellLen(line));
114
+ *_rows(options) {
115
+ const { first, from, to } = this._shown();
116
+ const numberStyle = getStyle(options, "syntax.line_number");
117
+ const highlightStyle = groundedStyle(options, getStyle(options, "syntax.line_number.highlight"));
118
+ const rule = drawable(options, "│ ", "| ");
119
+ // Tokenized whole and then sliced, so a block comment or a triple-quoted
120
+ // string that spans lines is one token even when the range starts inside it.
121
+ const text = new RichText(this._lines.join("\n"), { end: "" });
122
+ for (const match of text.plain.matchAll(GRAMMARS[this.language])) {
123
+ const [token] = Object.entries(match.groups).find(([, value]) => value !== undefined);
124
+ text.stylize(`syntax.${token}`, match.index, match.index + match[0].length);
93
125
  }
94
- const lineNumWidth = this.lineNumbers
95
- ? String(this.startLine + lines.length - 1).length + 3
96
- : 0;
97
- return {
98
- minimum: 10,
99
- maximum: Math.min(maxLine + lineNumWidth, options.maxWidth),
126
+ const textLines = text.split("\n").slice(from, to);
127
+ const gutterWidth = this._gutterWidth(first, textLines.length);
128
+ // [LAW:dataflow-not-control-flow] `wordWrap` reaches the line as the
129
+ // overflow it is drawn with, not as a second path: wrapped, a line may
130
+ // become several rows; cropped, it is always one. Never justified: a
131
+ // caller's `justify` would align each line alone and lose its indent.
132
+ const lineOptions = {
133
+ ...options,
134
+ maxWidth: Math.max(0, options.maxWidth - gutterWidth),
135
+ justify: undefined,
136
+ noWrap: !this.wordWrap,
137
+ overflow: this.wordWrap ? "fold" : "crop",
100
138
  };
101
- }
102
- _highlight(text) {
103
- const patterns = TOKEN_PATTERNS["_default"] ?? [];
104
- for (const [pattern, tokenType] of patterns) {
105
- const style = TOKEN_STYLES[tokenType];
106
- if (style) {
107
- text.highlightRegex(pattern, style);
139
+ for (let i = 0; i < textLines.length; i++) {
140
+ const lineNo = first + i;
141
+ const style = this.highlightLines.has(lineNo) ? highlightStyle : numberStyle;
142
+ // Terminated, so an empty line is one empty row rather than none.
143
+ const rows = Segment.splitLines([...textLines[i].render(lineOptions), Segment.line()]);
144
+ for (let row = 0; row < rows.length; row++) {
145
+ if (this.lineNumbers) {
146
+ // A wrapped line's continuation rows keep the gutter, unnumbered.
147
+ const label = row === 0 ? String(lineNo) : "";
148
+ yield new Segment(label.padStart(gutterWidth - 3) + " ", style);
149
+ yield new Segment(rule, numberStyle);
150
+ }
151
+ yield* rows[row];
152
+ yield Segment.line();
108
153
  }
109
154
  }
110
155
  }
111
- static fromPath(_filePath, _options) {
112
- // In a real implementation, this would read the file and detect language
113
- throw new Error("Syntax.fromPath requires file system access — use the constructor with code string instead");
156
+ /** The gutter is the narrowest it can be drawn; the widest shown line plus the gutter, the widest. */
157
+ measure(options) {
158
+ const { maxWidth } = withCellWidth(options);
159
+ const { first, from, to } = this._shown();
160
+ const lines = this._lines.slice(from, to);
161
+ const gutterWidth = this._gutterWidth(first, lines.length);
162
+ const widest = lines.reduce((width, line) => Math.max(width, cellLen(line)), 0);
163
+ const maximum = Math.min(gutterWidth + widest, maxWidth);
164
+ return { minimum: Math.min(gutterWidth, maximum), maximum };
114
165
  }
115
166
  }
@@ -8,8 +8,9 @@ import { RichText } from "../core/text.js";
8
8
  import type { PaddingDimensions } from "./padding.js";
9
9
  import type { Renderable, Measurable, OverflowMethod, RenderOptions } from "../core/protocol.js";
10
10
  export interface ColumnOptions {
11
- header?: string | RichText;
12
- footer?: string | RichText;
11
+ /** Anything a cell can hold, as Rich's `RenderableType` header is. */
12
+ header?: string | RichText | Renderable;
13
+ footer?: string | RichText | Renderable;
13
14
  headerStyle?: string | Style;
14
15
  footerStyle?: string | Style;
15
16
  style?: string | Style;
@@ -20,6 +21,12 @@ export interface ColumnOptions {
20
21
  ratio?: number;
21
22
  noWrap?: boolean;
22
23
  overflow?: OverflowMethod;
24
+ /**
25
+ * Whether the column's string cells — its header and footer among them — are
26
+ * drawn with the console's highlighter, as Rich's `Column(highlight=)`.
27
+ * `false` for a `Column` built alone; `addColumn` takes the table's.
28
+ */
29
+ highlight?: boolean;
23
30
  }
24
31
  export declare class Column {
25
32
  private _header;
@@ -34,15 +41,15 @@ export declare class Column {
34
41
  ratio: number | undefined;
35
42
  noWrap: boolean;
36
43
  overflow: OverflowMethod;
44
+ highlight: boolean;
37
45
  private _cells;
38
46
  constructor(options?: ColumnOptions);
39
47
  /**
40
- * The two stamped cells, parsed on assignment rather than at the constructor.
48
+ * The two stamped cells, stamped on assignment rather than at the constructor.
41
49
  * `Table.columns` hands out the live column and both fields are public, so a
42
50
  * constructor-only stamp held only until the first `columns[0].footer = mine`
43
- * — which installed content that had parsed no markup, still carried its
44
- * `end`, and was still owned by the caller, into a slot every reader below
45
- * assumes `embeddedText` has been through. [LAW:parse-dont-validate] The setter
51
+ * — which installed content still owned by the caller into a slot every
52
+ * reader below assumes `embed` has been through. [LAW:parse-dont-validate] The setter
46
53
  * is the border, so the guarantee holds for the object's whole lifetime and
47
54
  * the constructor is one caller of it rather than the one place it is true.
48
55
  *
@@ -51,13 +58,13 @@ export declare class Column {
51
58
  * a column always has one and `show_footer` alone decides whether it is
52
59
  * drawn. Modelling the absence as `undefined` instead made "no column has a
53
60
  * footer" a state the render path could ask about — and it did, skipping the
54
- * row a caller had asked for. `embeddedText` already maps nothing onto empty,
61
+ * row a caller had asked for. `embed` already maps nothing onto empty,
55
62
  * which is why the setters take `undefined` rather than defaulting around it.
56
63
  */
57
- get header(): RichText;
58
- set header(content: string | RichText | undefined);
59
- get footer(): RichText;
60
- set footer(content: string | RichText | undefined);
64
+ get header(): Renderable & Partial<Measurable>;
65
+ set header(content: ColumnOptions["header"]);
66
+ get footer(): Renderable & Partial<Measurable>;
67
+ set footer(content: ColumnOptions["footer"]);
61
68
  get flexible(): boolean;
62
69
  /** @internal */
63
70
  addCell(cell: Renderable): void;
@@ -75,6 +82,8 @@ export interface TableOptions {
75
82
  showLines?: boolean;
76
83
  showEdge?: boolean;
77
84
  padding?: PaddingDimensions;
85
+ collapsePadding?: boolean;
86
+ padEdge?: boolean;
78
87
  style?: string | Style;
79
88
  headerStyle?: string | Style;
80
89
  footerStyle?: string | Style;
@@ -86,19 +95,23 @@ export interface TableOptions {
86
95
  width?: number;
87
96
  minWidth?: number;
88
97
  rowStyles?: string[];
98
+ /** The `highlight` every column `addColumn` makes takes unless told otherwise (default: `false`). */
99
+ highlight?: boolean;
89
100
  }
90
101
  export declare class Table implements Renderable, Measurable {
91
102
  private _columns;
92
103
  private _rows;
93
104
  readonly box: Box | null;
94
- readonly title: RichText | undefined;
95
- readonly caption: RichText | undefined;
105
+ private readonly _title;
106
+ private readonly _caption;
96
107
  readonly expand: boolean;
97
108
  readonly showHeader: boolean;
98
109
  readonly showFooter: boolean;
99
110
  readonly showLines: boolean;
100
111
  readonly showEdge: boolean;
101
112
  readonly padding: [number, number, number, number];
113
+ readonly collapsePadding: boolean;
114
+ readonly padEdge: boolean;
102
115
  readonly style: string | Style;
103
116
  readonly headerStyle: string | Style;
104
117
  readonly footerStyle: string | Style;
@@ -110,17 +123,20 @@ export declare class Table implements Renderable, Measurable {
110
123
  readonly tableWidth: number | undefined;
111
124
  readonly minWidth: number | undefined;
112
125
  readonly rowStyles: string[];
126
+ readonly highlight: boolean;
127
+ get title(): (Renderable & Measurable) | undefined;
128
+ get caption(): (Renderable & Measurable) | undefined;
113
129
  constructor(options?: TableOptions);
114
130
  get columns(): Column[];
115
131
  get rowCount(): number;
116
- addColumn(header?: string | RichText, options?: ColumnOptions): this;
132
+ addColumn(header?: ColumnOptions["header"], options?: ColumnOptions): this;
117
133
  addRow(...cells: unknown[]): this;
118
134
  addSection(): this;
119
135
  render(rawOptions: RenderOptions): Iterable<Segment>;
120
136
  /**
121
137
  * [LAW:one-source-of-truth] Both ends of the range are widths the geometry
122
138
  * actually produced — the maximum from the demands as they stand, the
123
- * minimum from the same layout with every column asking for a single cell.
139
+ * minimum from the same layout with every column asking for its minimum.
124
140
  * Neither end stretches or fills into an offer: both only spend cells an
125
141
  * offer happens to leave over, and a renderable reports the width its content
126
142
  * wants rather than the width it was offered. A declared `width` is not an
@@ -137,6 +153,12 @@ export declare class Table implements Renderable, Measurable {
137
153
  minimum: number;
138
154
  maximum: number;
139
155
  };
156
+ /**
157
+ * A table with no frame and no header, padded between its columns and never
158
+ * at its edges, so an expanded grid reaches both sides of its width. The
159
+ * reference's `grid` defaults, but for `padding`: Rich's is 0, which runs
160
+ * adjacent columns together, and this port's is one cell to the right.
161
+ */
140
162
  static grid(options?: Omit<TableOptions, "box" | "showHeader" | "showEdge">): Table;
141
163
  private _frame;
142
164
  /**
@@ -159,8 +181,23 @@ export declare class Table implements Renderable, Measurable {
159
181
  * produce, and `expand` only in its `stretch`. They are resolved once, here, into uniform data, so
160
182
  * `layoutTable` runs the same apportionment for every table and no sizing
161
183
  * mode gets its own path through the width division.
184
+ *
185
+ * Cells are measured against the width left once the frame is paid, as the
186
+ * reference's `_measure_column` measures them. Padding and the floor are laid
187
+ * over whichever sizing the column has: they are the same for all three.
162
188
  */
163
189
  private _columnDemands;
190
+ private _columnSizing;
191
+ /**
192
+ * The padding either side of column `index`'s content, and above and below
193
+ * one row of cells: the reference's `get_padding`. `collapsePadding` lets a
194
+ * cell's left padding and its row's bottom padding merge into the padding
195
+ * beside and below them; `padEdge: false` drops every side that meets the
196
+ * table's edge. Each side is the configured padding unless one of those two
197
+ * reduces it.
198
+ */
199
+ private _columnPadding;
200
+ private _rowPadding;
164
201
  /**
165
202
  * Every cell column `index` draws, in draw order.
166
203
  *
@@ -174,12 +211,13 @@ export declare class Table implements Renderable, Measurable {
174
211
  */
175
212
  private _columnCells;
176
213
  /**
177
- * The widest cell in a column, or `undefined` when the column has no cell to
178
- * measure. Zero is a column whose cells draw nothing — a gutter asks for its
179
- * padding and nothing else — and is not the same answer.
180
- * [LAW:types-are-the-program]
214
+ * The widest any cell in a column draws, and the narrowest the column can be
215
+ * without cutting one — the reference's `_measure_column` — or `undefined`
216
+ * when the column has no cell to measure. Zero is a column whose cells draw
217
+ * nothing — a gutter asks for its padding and nothing else — and is not the
218
+ * same answer. [LAW:types-are-the-program]
181
219
  */
182
- private _widestCell;
220
+ private _cellRange;
183
221
  /**
184
222
  * A natural width held to the column's own `minWidth`/`maxWidth`.
185
223
  *