@localix-ai/flowdown 0.0.0-stage → 0.5.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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -2
  3. package/dist/browser/file-actions.js +80 -0
  4. package/dist/browser/file-actions.js.map +1 -0
  5. package/dist/core/block-facts.js +72 -0
  6. package/dist/core/block-facts.js.map +1 -0
  7. package/dist/core/blocks.d.ts +25 -0
  8. package/dist/core/blocks.js +464 -0
  9. package/dist/core/blocks.js.map +1 -0
  10. package/dist/core/bounded-cache.js +42 -0
  11. package/dist/core/bounded-cache.js.map +1 -0
  12. package/dist/core/continues.js +15 -0
  13. package/dist/core/continues.js.map +1 -0
  14. package/dist/core/dollars.d.ts +12 -0
  15. package/dist/core/dollars.js +88 -0
  16. package/dist/core/dollars.js.map +1 -0
  17. package/dist/core/fence-scan.js +62 -0
  18. package/dist/core/fence-scan.js.map +1 -0
  19. package/dist/core/file-tree-parse.d.ts +26 -0
  20. package/dist/core/file-tree-parse.js +51 -0
  21. package/dist/core/file-tree-parse.js.map +1 -0
  22. package/dist/core/forms-html.js +63 -0
  23. package/dist/core/forms-html.js.map +1 -0
  24. package/dist/core/forms.d.ts +28 -0
  25. package/dist/core/forms.js +23 -0
  26. package/dist/core/forms.js.map +1 -0
  27. package/dist/core/headings.d.ts +11 -0
  28. package/dist/core/headings.js +74 -0
  29. package/dist/core/headings.js.map +1 -0
  30. package/dist/core/heal.d.ts +11 -0
  31. package/dist/core/heal.js +220 -0
  32. package/dist/core/heal.js.map +1 -0
  33. package/dist/core/load-once.js +23 -0
  34. package/dist/core/load-once.js.map +1 -0
  35. package/dist/core/math-delimiters.js +33 -0
  36. package/dist/core/math-delimiters.js.map +1 -0
  37. package/dist/core/math-syntax.js +56 -0
  38. package/dist/core/math-syntax.js.map +1 -0
  39. package/dist/core/prose.d.ts +10 -0
  40. package/dist/core/prose.js +77 -0
  41. package/dist/core/prose.js.map +1 -0
  42. package/dist/core/remark-answer-blocks.d.ts +5 -0
  43. package/dist/core/remark-answer-blocks.js +172 -0
  44. package/dist/core/remark-answer-blocks.js.map +1 -0
  45. package/dist/core/remark-answer-containers.js +299 -0
  46. package/dist/core/remark-answer-containers.js.map +1 -0
  47. package/dist/core/sanitize-schema.d.ts +18 -0
  48. package/dist/core/sanitize-schema.js +62 -0
  49. package/dist/core/sanitize-schema.js.map +1 -0
  50. package/dist/core/syntax.d.ts +8 -0
  51. package/dist/core/syntax.js +32 -0
  52. package/dist/core/syntax.js.map +1 -0
  53. package/dist/dialect.d.ts +92 -0
  54. package/dist/dialect.js +176 -0
  55. package/dist/dialect.js.map +1 -0
  56. package/dist/document/fence.d.ts +25 -0
  57. package/dist/document/fence.js +102 -0
  58. package/dist/document/fence.js.map +1 -0
  59. package/dist/document/index.d.ts +46 -0
  60. package/dist/document/index.js +66 -0
  61. package/dist/document/index.js.map +1 -0
  62. package/dist/document/table.d.ts +11 -0
  63. package/dist/document/table.js +35 -0
  64. package/dist/document/table.js.map +1 -0
  65. package/dist/dom/index.d.ts +21 -0
  66. package/dist/dom/index.js +141 -0
  67. package/dist/dom/index.js.map +1 -0
  68. package/dist/dom/patch.js +104 -0
  69. package/dist/dom/patch.js.map +1 -0
  70. package/dist/element/index.d.ts +35 -0
  71. package/dist/element/index.js +64 -0
  72. package/dist/element/index.js.map +1 -0
  73. package/dist/engine/blocks.js +785 -0
  74. package/dist/engine/blocks.js.map +1 -0
  75. package/dist/engine/entities.js +83 -0
  76. package/dist/engine/entities.js.map +1 -0
  77. package/dist/engine/entity-core.js +9 -0
  78. package/dist/engine/entity-core.js.map +1 -0
  79. package/dist/engine/entity-table.js +9 -0
  80. package/dist/engine/entity-table.js.map +1 -0
  81. package/dist/engine/html.d.ts +7 -0
  82. package/dist/engine/html.js +207 -0
  83. package/dist/engine/html.js.map +1 -0
  84. package/dist/engine/index.d.ts +7 -0
  85. package/dist/engine/index.js +74 -0
  86. package/dist/engine/index.js.map +1 -0
  87. package/dist/engine/inlines.js +887 -0
  88. package/dist/engine/inlines.js.map +1 -0
  89. package/dist/engine/types.d.ts +109 -0
  90. package/dist/engine/url.d.ts +11 -0
  91. package/dist/engine/url.js +16 -0
  92. package/dist/engine/url.js.map +1 -0
  93. package/dist/engine/visit.d.ts +10 -0
  94. package/dist/engine/visit.js +29 -0
  95. package/dist/engine/visit.js.map +1 -0
  96. package/dist/highlight/highlight.d.ts +4 -0
  97. package/dist/highlight/highlight.js +144 -0
  98. package/dist/highlight/highlight.js.map +1 -0
  99. package/dist/highlight/index.d.ts +4 -0
  100. package/dist/highlight/index.js +18 -0
  101. package/dist/highlight/index.js.map +1 -0
  102. package/dist/html/index.d.ts +4 -0
  103. package/dist/html/index.js +544 -0
  104. package/dist/html/index.js.map +1 -0
  105. package/dist/index.d.ts +27 -0
  106. package/dist/index.js +13 -0
  107. package/dist/math/index.d.ts +17 -0
  108. package/dist/math/index.js +20 -0
  109. package/dist/math/index.js.map +1 -0
  110. package/dist/math/typeset.d.ts +12 -0
  111. package/dist/math/typeset.js +74 -0
  112. package/dist/math/typeset.js.map +1 -0
  113. package/dist/mermaid/diagram.d.ts +8 -0
  114. package/dist/mermaid/diagram.js +114 -0
  115. package/dist/mermaid/diagram.js.map +1 -0
  116. package/dist/mermaid/drawing.js +139 -0
  117. package/dist/mermaid/drawing.js.map +1 -0
  118. package/dist/mermaid/icons.js +16 -0
  119. package/dist/mermaid/icons.js.map +1 -0
  120. package/dist/mermaid/index.d.ts +13 -0
  121. package/dist/mermaid/index.js +3 -0
  122. package/dist/mermaid/loader.d.ts +5 -0
  123. package/dist/mermaid/loader.js +18 -0
  124. package/dist/mermaid/loader.js.map +1 -0
  125. package/dist/mermaid/look.d.ts +16 -0
  126. package/dist/mermaid/look.js +167 -0
  127. package/dist/mermaid/look.js.map +1 -0
  128. package/dist/mermaid/normalize-svg.js +44 -0
  129. package/dist/mermaid/normalize-svg.js.map +1 -0
  130. package/dist/mermaid/renderer.d.ts +22 -0
  131. package/dist/mermaid/renderer.js +132 -0
  132. package/dist/mermaid/renderer.js.map +1 -0
  133. package/dist/mermaid/viewer.js +309 -0
  134. package/dist/mermaid/viewer.js.map +1 -0
  135. package/dist/prompt/index.d.ts +19 -0
  136. package/dist/prompt/index.js +54 -0
  137. package/dist/prompt/index.js.map +1 -0
  138. package/dist/react/callout.d.ts +9 -0
  139. package/dist/react/callout.js +12 -0
  140. package/dist/react/callout.js.map +1 -0
  141. package/dist/react/code-block.d.ts +18 -0
  142. package/dist/react/code-block.js +94 -0
  143. package/dist/react/code-block.js.map +1 -0
  144. package/dist/react/context.d.ts +18 -0
  145. package/dist/react/context.js +17 -0
  146. package/dist/react/context.js.map +1 -0
  147. package/dist/react/draw.d.ts +2 -0
  148. package/dist/react/draw.js +17 -0
  149. package/dist/react/draw.js.map +1 -0
  150. package/dist/react/file-tree.d.ts +16 -0
  151. package/dist/react/file-tree.js +69 -0
  152. package/dist/react/file-tree.js.map +1 -0
  153. package/dist/react/growing-code.js +36 -0
  154. package/dist/react/growing-code.js.map +1 -0
  155. package/dist/react/helpers.d.ts +6 -0
  156. package/dist/react/helpers.js +17 -0
  157. package/dist/react/helpers.js.map +1 -0
  158. package/dist/react/icons.js +23 -0
  159. package/dist/react/icons.js.map +1 -0
  160. package/dist/react/labels.js +9 -0
  161. package/dist/react/labels.js.map +1 -0
  162. package/dist/react/markdown.d.ts +78 -0
  163. package/dist/react/markdown.js +206 -0
  164. package/dist/react/markdown.js.map +1 -0
  165. package/dist/react/render.d.ts +8 -0
  166. package/dist/react/render.js +41 -0
  167. package/dist/react/render.js.map +1 -0
  168. package/dist/react/scrolls.js +62 -0
  169. package/dist/react/scrolls.js.map +1 -0
  170. package/dist/react/table-rows.js +66 -0
  171. package/dist/react/table-rows.js.map +1 -0
  172. package/dist/react/table.js +22 -0
  173. package/dist/react/table.js.map +1 -0
  174. package/dist/react/tabs.d.ts +25 -0
  175. package/dist/react/tabs.js +58 -0
  176. package/dist/react/tabs.js.map +1 -0
  177. package/dist/react/use-smooth-text.d.ts +6 -0
  178. package/dist/react/use-smooth-text.js +157 -0
  179. package/dist/react/use-smooth-text.js.map +1 -0
  180. package/dist/react/use-stage.d.ts +9 -0
  181. package/dist/react/use-stage.js +57 -0
  182. package/dist/react/use-stage.js.map +1 -0
  183. package/dist/remark.d.ts +20 -0
  184. package/dist/remark.js +10 -0
  185. package/dist/render/factory.d.ts +15 -0
  186. package/dist/render/labels.d.ts +46 -0
  187. package/dist/render/labels.js +46 -0
  188. package/dist/render/labels.js.map +1 -0
  189. package/dist/render/tree.js +314 -0
  190. package/dist/render/tree.js.map +1 -0
  191. package/dist/render/view.d.ts +58 -0
  192. package/dist/render/view.js +306 -0
  193. package/dist/render/view.js.map +1 -0
  194. package/dist/render/views.d.ts +16 -0
  195. package/dist/render/views.js +261 -0
  196. package/dist/render/views.js.map +1 -0
  197. package/dist/render/vnode.d.ts +21 -0
  198. package/dist/render/vnode.js +194 -0
  199. package/dist/render/vnode.js.map +1 -0
  200. package/dist/static/index.d.ts +28 -0
  201. package/dist/static/index.js +27 -0
  202. package/dist/static/index.js.map +1 -0
  203. package/dist/stream.d.ts +12 -0
  204. package/dist/stream.js +3 -0
  205. package/dist/styles.css +1362 -0
  206. package/dist/tui/code.d.ts +5 -0
  207. package/dist/tui/code.js +240 -0
  208. package/dist/tui/code.js.map +1 -0
  209. package/dist/tui/index.d.ts +54 -0
  210. package/dist/tui/index.js +99 -0
  211. package/dist/tui/index.js.map +1 -0
  212. package/dist/tui/math.js +242 -0
  213. package/dist/tui/math.js.map +1 -0
  214. package/dist/tui/printable.d.ts +12 -0
  215. package/dist/tui/printable.js +31 -0
  216. package/dist/tui/printable.js.map +1 -0
  217. package/dist/tui/render.d.ts +12 -0
  218. package/dist/tui/render.js +524 -0
  219. package/dist/tui/render.js.map +1 -0
  220. package/dist/tui/style.d.ts +35 -0
  221. package/dist/tui/style.js +77 -0
  222. package/dist/tui/style.js.map +1 -0
  223. package/dist/tui/table.js +73 -0
  224. package/dist/tui/table.js.map +1 -0
  225. package/dist/tui/width.d.ts +2 -0
  226. package/dist/tui/width.js +133 -0
  227. package/dist/tui/width.js.map +1 -0
  228. package/dist/tui/wrap.d.ts +8 -0
  229. package/dist/tui/wrap.js +191 -0
  230. package/dist/tui/wrap.js.map +1 -0
  231. package/package.json +128 -4
@@ -0,0 +1,524 @@
1
+ import { parseFileTree } from "../core/file-tree-parse.js";
2
+ import { formOf } from "../core/forms.js";
3
+ import "../dialect.js";
4
+ import { createCodeDrawer } from "./code.js";
5
+ import { mathRows, mathText } from "./math.js";
6
+ import { printable } from "./printable.js";
7
+ import { BOLD, CODE, DIM, DONE, FAILED, MATH, PLAIN, WAITING, merged, patch } from "./style.js";
8
+ import { drawTable } from "./table.js";
9
+ import { fit, lineWidth, span, untabbed, wrap } from "./wrap.js";
10
+ import { width } from "./width.js";
11
+ // src/tui/render.ts
12
+ /**
13
+ * A parsed block as terminal rows. The tree is the one every target reads; what the dialect
14
+ * marked on it (`core/forms.ts`) is drawn here in a terminal's own terms: a callout is a coloured
15
+ * bar, steps a numbered rail, tabs one under another, each under its label, a file tree the
16
+ * branches `tree` draws, figures a row of numbers over a row of what they count.
17
+ *
18
+ * A form this file does not know is drawn as the Markdown it still is: a list, a quote.
19
+ */
20
+ const DEFAULT_LABELS = {
21
+ callouts: {
22
+ note: "Note",
23
+ tip: "Tip",
24
+ important: "Important",
25
+ warning: "Warning",
26
+ caution: "Caution"
27
+ },
28
+ details: "Details",
29
+ diagram: "Diagram"
30
+ };
31
+ const CALLOUT_STYLES = {
32
+ note: CODE,
33
+ tip: DONE,
34
+ important: { color: "magenta" },
35
+ warning: WAITING,
36
+ caution: FAILED
37
+ };
38
+ /** The narrowest a terminal is drawn for: under it, rows are as wide as this. */
39
+ const MIN_WIDTH = 8;
40
+ /**
41
+ * The columns a nested block must leave to write in. A quote in a list in a quote would
42
+ * otherwise leave none, and every row would run past the edge: past this depth the nesting is
43
+ * no longer drawn, and the text still is.
44
+ */
45
+ const MIN_ROOM = 4;
46
+ /** Inline forms set off from what is before them by a space the tree does not hold. */
47
+ const SET_OFF = /* @__PURE__ */ new Set([
48
+ "stat-label",
49
+ "source-note",
50
+ "step-meta"
51
+ ]);
52
+ /** Paragraphs that head what follows them: nothing parts the two. */
53
+ const HEADS = /* @__PURE__ */ new Set([
54
+ "steps-title",
55
+ "stats-title",
56
+ "tabs-title",
57
+ "verdict-title",
58
+ "sources-title"
59
+ ]);
60
+ /** A heading by its depth: the first three stand out by weight, the rest by slant. */
61
+ const HEADINGS = [
62
+ {
63
+ bold: true,
64
+ underline: true
65
+ },
66
+ BOLD,
67
+ {
68
+ bold: true,
69
+ italic: true
70
+ }
71
+ ];
72
+ const DEEP_HEADING = { italic: true };
73
+ /** A list's bullet by how deep the list is: one shape, then the other, and round again. */
74
+ const BULLETS = ["•", "◦"];
75
+ /** The columns between one figure of `[!STATS]` and the next. */
76
+ const FIGURE_GAP = 3;
77
+ const children = (node) => node.children ?? [];
78
+ /** A line ending inside a paragraph is a space; everything else is made safe to print. */
79
+ const flat = (text) => printable(text.replace(/\n/g, " "));
80
+ function push(lines, text, style) {
81
+ lines.at(-1).push(span(flat(text), style));
82
+ }
83
+ function spans(nodes, style, lines) {
84
+ for (const node of nodes) one(node, style, lines);
85
+ }
86
+ /** One inline and what it holds, added to the last of `lines`. */
87
+ function one(node, style, lines) {
88
+ const form = formOf(node)?.form;
89
+ switch (node.type) {
90
+ case "text":
91
+ case "html": return push(lines, node.value ?? "", style);
92
+ case "inlineCode": return push(lines, (node.value ?? "").replaceAll(" ", "\xA0"), patch(style, CODE));
93
+ case "inlineMath": return push(lines, mathText(node.value ?? "").replaceAll(" ", "\xA0"), patch(style, MATH));
94
+ case "break":
95
+ lines.push([]);
96
+ return;
97
+ case "strong": return spans(children(node), patch(style, BOLD), lines);
98
+ case "delete": return spans(children(node), patch(style, { strike: true }), lines);
99
+ case "link": return link(node, patch(style, { underline: true }), lines);
100
+ case "image": {
101
+ const alt = node.alt || node.url || "";
102
+ push(lines, alt, style);
103
+ if (node.url && node.url !== alt) push(lines, ` (${node.url})`, DIM);
104
+ return;
105
+ }
106
+ case "footnoteReference": return push(lines, `[${node.index ?? node.label ?? "?"}]`, DIM);
107
+ case "emphasis":
108
+ if (form === "source-host") return;
109
+ if (form === "section-number") {
110
+ spans(children(node), style, lines);
111
+ return push(lines, " ", style);
112
+ }
113
+ if (form === "caption") {
114
+ lines.push([]);
115
+ return spans(children(node), patch(style, DIM), lines);
116
+ }
117
+ if (form && SET_OFF.has(form)) {
118
+ if (lines.at(-1).length > 0) push(lines, " ", style);
119
+ return spans(children(node), patch(style, DIM), lines);
120
+ }
121
+ return spans(children(node), patch(style, { italic: true }), lines);
122
+ default:
123
+ if (node.children) return spans(node.children, style, lines);
124
+ if (typeof node.value === "string") push(lines, node.value, style);
125
+ }
126
+ }
127
+ /** A link's text, and its address after it unless the text is the address. */
128
+ function link(node, style, lines) {
129
+ const line = lines.length;
130
+ let from = lines.at(-1).length;
131
+ spans(children(node), style, lines);
132
+ if (lines.length !== line) from = 0;
133
+ const text = lines.at(-1).slice(from).map((run) => run.text).join("");
134
+ const url = node.url ?? "";
135
+ if (url && text !== flat(url) && !url.startsWith("#")) push(lines, ` (${url})`, DIM);
136
+ }
137
+ /** `line` without the spaces at its two ends. */
138
+ function trimmed(line) {
139
+ const out = line.map((run) => ({ ...run }));
140
+ while (out.length > 0 && out[0].text.trimStart() === "") out.shift();
141
+ while (out.length > 0 && out.at(-1).text.trimEnd() === "") out.pop();
142
+ if (out.length > 0) {
143
+ out[0].text = out[0].text.trimStart();
144
+ out[out.length - 1].text = out.at(-1).text.trimEnd();
145
+ }
146
+ return out;
147
+ }
148
+ /** A node's inlines as one line: a table cell, a figure. */
149
+ function inline(nodes, style = PLAIN) {
150
+ const lines = [[]];
151
+ spans(nodes, style, lines);
152
+ return lines.flatMap((line, at) => at > 0 && line.length > 0 ? [span(" ", PLAIN), ...line] : line);
153
+ }
154
+ var Renderer = class {
155
+ width;
156
+ labels;
157
+ code;
158
+ open;
159
+ rows = [];
160
+ /** The prefix of the next row alone: a list marker, a step's number. */
161
+ first = null;
162
+ /** The prefix of every row: the bars and indents of what the block is inside. */
163
+ rest = [];
164
+ /** A blank row asked for and not yet drawn: it is, before the next row, if one comes. */
165
+ gap = null;
166
+ /** The plain lists this is inside. */
167
+ depth = 0;
168
+ constructor(width, labels, code, open) {
169
+ this.width = width;
170
+ this.labels = labels;
171
+ this.code = code;
172
+ this.open = open;
173
+ }
174
+ /** A formula on rows of its own: set in from the text, a row for each of its lines. */
175
+ formula(node) {
176
+ const rows = mathRows(node.value ?? "");
177
+ if (rows.length === 0) return;
178
+ const inset = span(" ", PLAIN);
179
+ this.within(inset, inset, () => {
180
+ for (const row of rows) this.put([span(printable(row), MATH)]);
181
+ });
182
+ }
183
+ emit(row) {
184
+ if (this.gap) {
185
+ if (this.rows.length > 0) this.rows.push(this.gap);
186
+ this.gap = null;
187
+ }
188
+ this.rows.push(row);
189
+ }
190
+ /** The columns left to write in. */
191
+ room() {
192
+ return Math.max(1, this.width - lineWidth(this.rest));
193
+ }
194
+ /** A blank row before whatever is drawn next: the prefix with its trailing spaces dropped. */
195
+ blank() {
196
+ const row = [...this.rest];
197
+ while (row.length > 0) {
198
+ const last = row.at(-1);
199
+ const kept = last.text.trimEnd();
200
+ if (kept) {
201
+ row[row.length - 1] = span(kept, last.style);
202
+ break;
203
+ }
204
+ row.pop();
205
+ }
206
+ this.gap = row;
207
+ return row;
208
+ }
209
+ edges() {
210
+ const first = this.first ?? this.rest;
211
+ this.first = null;
212
+ return [first, this.rest];
213
+ }
214
+ /** `line`, wrapped under the prefixes. A line with nothing to read draws nothing. */
215
+ put(line) {
216
+ if (!line.some((run) => run.text.trim() !== "")) return;
217
+ const [first, rest] = this.edges();
218
+ for (const row of wrap(line, this.width, first, rest)) this.emit(row);
219
+ }
220
+ /** `body` drawn with `first` before its first row and `rest` before the others. */
221
+ within(first, rest, body) {
222
+ if (lineWidth(this.rest) + width(rest.text) + MIN_ROOM > this.width) return body();
223
+ const outer = this.rest;
224
+ this.first = [...this.first ?? outer, first];
225
+ this.rest = [...outer, rest];
226
+ body();
227
+ if (this.first) this.emit(this.first);
228
+ this.first = null;
229
+ this.rest = outer;
230
+ }
231
+ blocks(nodes, under = "blocks") {
232
+ let before = null;
233
+ let asked = null;
234
+ for (const node of nodes) {
235
+ if (before) {
236
+ const head = formOf(before)?.form;
237
+ if (!(under === "item" && (node.type === "list" || before.type === "list") || head !== void 0 && HEADS.has(head) || head === "step-head" && formOf(node)?.form === "step-body" || formOf(node)?.form === "quote-source")) asked = this.blank();
238
+ }
239
+ this.block(node);
240
+ before = node;
241
+ }
242
+ if (asked !== null && this.gap === asked) this.gap = null;
243
+ }
244
+ block(node) {
245
+ const form = formOf(node);
246
+ switch (node.type) {
247
+ case "paragraph": {
248
+ const only = children(node);
249
+ if (!form && only.length === 1 && only[0].type === "inlineMath") return this.formula(only[0]);
250
+ return this.paragraph(node, form);
251
+ }
252
+ case "heading": return this.text(children(node), HEADINGS[(node.depth ?? 1) - 1] ?? DEEP_HEADING);
253
+ case "thematicBreak": return this.put([span("─".repeat(Math.min(this.room(), 40)), DIM)]);
254
+ case "blockquote": return this.quote(node, form);
255
+ case "list": return this.list(node, form);
256
+ case "code": return this.fence(node, form);
257
+ case "math": return this.formula(node);
258
+ case "html":
259
+ for (const line of (node.value ?? "").split("\n")) this.put([span(printable(untabbed(line)), PLAIN)]);
260
+ return;
261
+ case "table": return this.table(node);
262
+ case "footnotes": return this.marked(children(node), (index, item) => [`[${item.index ?? index + 1}] `, DIM]);
263
+ default:
264
+ if (node.children) return this.blocks(node.children);
265
+ if (typeof node.value === "string") this.put([span(flat(node.value), PLAIN)]);
266
+ }
267
+ }
268
+ paragraph(node, form) {
269
+ switch (form?.form) {
270
+ case "steps-title":
271
+ case "stats-title":
272
+ case "tabs-title":
273
+ case "verdict-title":
274
+ case "sources-title": return this.text(children(node), BOLD);
275
+ case "quote-source": return this.put([span("— ", DIM), ...inline(children(node), DIM)]);
276
+ default: this.text(children(node), PLAIN);
277
+ }
278
+ }
279
+ text(nodes, style) {
280
+ const lines = [[]];
281
+ spans(nodes, style, lines);
282
+ for (const line of lines) this.put(line);
283
+ }
284
+ quote(node, form) {
285
+ switch (form?.form) {
286
+ case "callout": return this.callout(node, form);
287
+ case "tabs": return this.tabs(node);
288
+ case "verdict": return this.verdict(node, form);
289
+ case "details": return this.details(node);
290
+ case "form":
291
+ case "verdicts":
292
+ case "tab": return this.blocks(children(node));
293
+ default: {
294
+ const bar = span("│ ", DIM);
295
+ this.within(bar, bar, () => this.blocks(children(node)));
296
+ }
297
+ }
298
+ }
299
+ callout(node, form) {
300
+ const kind = form.kind ?? "note";
301
+ const colour = CALLOUT_STYLES[kind] ?? CODE;
302
+ const bar = span("▌ ", colour);
303
+ this.within(bar, bar, () => {
304
+ const head = [span(this.labels.callouts[kind] ?? kind, patch(colour, BOLD))];
305
+ if (form.title) head.push(span(" · ", DIM), span(flat(form.title), BOLD));
306
+ this.put(head);
307
+ this.blocks(children(node));
308
+ });
309
+ }
310
+ tabs(node) {
311
+ children(node).forEach((tab, index) => {
312
+ if (index > 0) this.blank();
313
+ const label = flat(formOf(tab)?.label ?? "");
314
+ const rule = "─".repeat(Math.max(0, Math.min(this.room(), 40) - (width(label) + 4)));
315
+ this.put([
316
+ span("─ ", DIM),
317
+ span(label, BOLD),
318
+ span(` ${rule}`, DIM)
319
+ ]);
320
+ this.blocks(children(tab));
321
+ });
322
+ }
323
+ verdict(node, form) {
324
+ const against = form.kind === "cons";
325
+ for (const part of children(node)) if (part.type === "list") this.marked(children(part), () => against ? ["- ", FAILED] : ["+ ", DONE], part.spread);
326
+ else this.block(part);
327
+ }
328
+ details(node) {
329
+ const parts = children(node);
330
+ const summary = parts.find((part) => formOf(part)?.form === "summary");
331
+ const title = summary ? inline(children(summary), BOLD) : [span(this.labels.details, BOLD)];
332
+ this.put([span("▾ ", BOLD), ...title]);
333
+ const indent = span(" ", PLAIN);
334
+ this.within(indent, indent, () => this.blocks(parts.filter((part) => part !== summary)));
335
+ }
336
+ list(node, form) {
337
+ const items = children(node);
338
+ switch (form?.form) {
339
+ case "steps": return this.steps(node);
340
+ case "stats": return this.stats(node);
341
+ case "sources": return this.marked(items, (index) => [`[${index + 1}] `, DIM], node.spread, (item) => this.source(item));
342
+ default: {
343
+ const start = node.start ?? 1;
344
+ const bullet = `${BULLETS[this.depth % BULLETS.length]} `;
345
+ this.depth++;
346
+ this.marked(items, (index, item) => {
347
+ const number = node.ordered ? `${start + index}. ` : "";
348
+ if (item.checked === true) return [`${number}✓ `, DONE];
349
+ if (item.checked === false) return [`${number}☐ `, DIM];
350
+ return [number || bullet, DIM];
351
+ }, node.spread);
352
+ this.depth--;
353
+ }
354
+ }
355
+ }
356
+ /**
357
+ * One of `[!SOURCES]`: what it is called and what is said of it on a row, and its address on
358
+ * the next, where it is whole whatever the title's length.
359
+ */
360
+ source(item) {
361
+ const parts = children(item);
362
+ const paragraph = parts.length === 1 && parts[0].type === "paragraph" ? parts[0] : null;
363
+ const links = children(paragraph ?? item).filter((part) => part.type === "link" && part.url && !part.url.startsWith("#"));
364
+ if (!paragraph || links.length !== 1) return this.blocks(parts, "item");
365
+ const lines = [[]];
366
+ for (const part of children(paragraph)) if (part === links[0]) spans(children(part), { underline: true }, lines);
367
+ else if (formOf(part)?.form === "source-note") {
368
+ push(lines, " — ", DIM);
369
+ spans(children(part), DIM, lines);
370
+ } else one(part, PLAIN, lines);
371
+ for (const line of lines) this.put(line);
372
+ const url = flat(links[0].url ?? "");
373
+ if (!lines.flat().map((run) => run.text).join("").includes(url)) this.put([span(url, DIM)]);
374
+ }
375
+ /** Items, each behind its marker with its other rows hung under its text. */
376
+ marked(items, marker, loose = false, body = (item) => this.blocks(children(item), "item")) {
377
+ items.forEach((item, index) => {
378
+ if (index > 0 && loose) this.blank();
379
+ const [mark, style] = marker(index, item);
380
+ this.within(span(mark, style), span(" ".repeat(width(mark)), PLAIN), () => body(item));
381
+ });
382
+ }
383
+ /** `[!STEPS]`: each step's number, and a rail down to the next. */
384
+ steps(node) {
385
+ const items = children(node);
386
+ const start = Math.max(1, node.start ?? 1);
387
+ const wide = String(start + items.length - 1).length;
388
+ items.forEach((item, index) => {
389
+ if (index > 0) {
390
+ this.gap = null;
391
+ this.emit([...this.rest, span("│".padStart(wide), DIM)]);
392
+ }
393
+ const number = span(`${String(start + index).padStart(wide)} `, patch(DONE, BOLD));
394
+ const rail = index + 1 === items.length ? span(" ".repeat(wide + 2), PLAIN) : span(`${"│".padStart(wide)} `, DIM);
395
+ this.within(number, rail, () => this.blocks(children(item), "item"));
396
+ });
397
+ }
398
+ /**
399
+ * `[!STATS]`: the figures side by side, each number over what it counts, while a row holds
400
+ * them; and a list when it does not.
401
+ */
402
+ stats(node) {
403
+ const items = children(node);
404
+ const listed = () => this.marked(items, () => ["• ", DIM], node.spread);
405
+ const figures = [];
406
+ for (const item of items) {
407
+ const parts = children(item);
408
+ if (parts.length !== 1 || parts[0].type !== "paragraph") return listed();
409
+ const inlines = children(parts[0]);
410
+ const labels = inlines.filter((part) => formOf(part)?.form === "stat-label");
411
+ figures.push({
412
+ value: trimmed(inline(inlines.filter((part) => !labels.includes(part)))),
413
+ label: trimmed(inline(labels.flatMap(children), DIM))
414
+ });
415
+ }
416
+ const widths = figures.map(({ value, label }) => Math.max(lineWidth(value), lineWidth(label)));
417
+ if (widths.reduce((sum, wide) => sum + wide, 0) + Math.max(0, figures.length - 1) * FIGURE_GAP > this.room()) return listed();
418
+ const row = (pick) => {
419
+ const line = [];
420
+ let owed = 0;
421
+ figures.forEach((figure, index) => {
422
+ const cell = pick(figure);
423
+ if (cell.length > 0 && owed > 0) line.push(span(" ".repeat(owed), PLAIN));
424
+ if (cell.length > 0) owed = 0;
425
+ line.push(...cell);
426
+ owed += widths[index] - lineWidth(cell) + FIGURE_GAP;
427
+ });
428
+ if (line.length > 0) this.exact(line);
429
+ };
430
+ row((figure) => figure.value);
431
+ row((figure) => figure.label);
432
+ }
433
+ fence(node, form) {
434
+ const source = node.value ?? "";
435
+ if (form?.title) this.put([span(flat(form.title), DIM)]);
436
+ else if (node.lang === "mermaid" && source.trim()) this.put([span(this.labels.diagram, DIM)]);
437
+ if (node.lang === "tree") {
438
+ const rows = parseFileTree(source);
439
+ if (rows.length > 0) return this.tree(rows);
440
+ }
441
+ for (const row of this.code.rows(source.replace(/\n+$/, ""), node.lang, node === this.open)) this.exact(row);
442
+ }
443
+ /**
444
+ * A file tree with the branches `tree` draws: each name hangs from its folder, and a folder's
445
+ * line runs down to the last name in it. What is said of the names stands in one column where
446
+ * a row holds it.
447
+ */
448
+ tree(rows) {
449
+ const last = [];
450
+ const after = [];
451
+ for (let at = rows.length - 1; at >= 0; at--) {
452
+ const { depth } = rows[at];
453
+ last[at] = !after[depth];
454
+ after.length = depth;
455
+ after[depth] = true;
456
+ }
457
+ /** For each depth, whether the folder there has names still to come. */
458
+ const open = [];
459
+ const names = rows.map((row, at) => {
460
+ open.length = row.depth;
461
+ open[row.depth] = !last[at];
462
+ let guide = "";
463
+ for (let depth = 1; depth < row.depth; depth++) guide += open[depth] ? "│ " : " ";
464
+ if (row.depth > 0) guide += last[at] ? "└─ " : "├─ ";
465
+ return [span(guide, DIM), span(flat(row.name), row.folder ? BOLD : PLAIN)];
466
+ });
467
+ const said = rows.map((row) => row.comment ? flat(row.comment) : "");
468
+ let column = 0;
469
+ let widest = 0;
470
+ rows.forEach((_, at) => {
471
+ if (!said[at]) return;
472
+ column = Math.max(column, lineWidth(names[at]));
473
+ widest = Math.max(widest, width(said[at]));
474
+ });
475
+ const aligned = column + 2 + widest <= this.room();
476
+ rows.forEach((_, at) => {
477
+ const line = names[at];
478
+ if (said[at]) {
479
+ const gap = aligned ? column - lineWidth(line) + 2 : 2;
480
+ line.push(span(" ".repeat(gap), PLAIN), span(said[at], DIM));
481
+ }
482
+ this.exact(line);
483
+ });
484
+ }
485
+ /** `line` as it is, cut where the row ends: what is aligned by its columns, not by its words. */
486
+ exact(line) {
487
+ for (const row of fit(line, this.room())) {
488
+ const [first] = this.edges();
489
+ this.emit([...first, ...row]);
490
+ }
491
+ }
492
+ table(node) {
493
+ const rows = children(node).map((row) => children(row).map((cell) => inline(children(cell))));
494
+ const [first, rest] = this.edges();
495
+ for (const row of drawTable(rows, node.align ?? [], this.width, first, rest)) this.emit(row);
496
+ }
497
+ };
498
+ /**
499
+ * The last fence of a tree that holds code, in the order it is read: the one a reply still
500
+ * arriving may be writing. (A fence with no code is passed over: closing a fence left open inside
501
+ * a list leaves an empty one after it.)
502
+ */
503
+ function lastFence(node) {
504
+ const parts = children(node);
505
+ for (let at = parts.length - 1; at >= 0; at--) {
506
+ const found = lastFence(parts[at]);
507
+ if (found) return found;
508
+ }
509
+ return node.type === "code" && node.value ? node : null;
510
+ }
511
+ /** Code in its one colour: what a block is drawn with when no view keeps a drawer for it. */
512
+ const PLAIN_CODE = createCodeDrawer(null);
513
+ /**
514
+ * A parsed block (its `root`) as terminal rows no wider than `columns`. `code` colours its fences;
515
+ * `growing` says the block is the end of a reply still arriving: its last fence may gain code.
516
+ */
517
+ function drawBlock(root, columns, labels, code = PLAIN_CODE, growing = false) {
518
+ const renderer = new Renderer(Math.max(8, Math.floor(columns)), labels, code, growing ? lastFence(root) : null);
519
+ renderer.blocks(children(root));
520
+ return renderer.rows.map(merged);
521
+ }
522
+ export { DEFAULT_LABELS, MIN_WIDTH, drawBlock };
523
+
524
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","names":["columnsOf"],"sources":["../../src/tui/render.ts"],"sourcesContent":["/**\n * A parsed block as terminal rows. The tree is the one every target reads; what the dialect\n * marked on it (`core/forms.ts`) is drawn here in a terminal's own terms: a callout is a coloured\n * bar, steps a numbered rail, tabs one under another, each under its label, a file tree the\n * branches `tree` draws, figures a row of numbers over a row of what they count.\n *\n * A form this file does not know is drawn as the Markdown it still is: a list, a quote.\n */\nimport { parseFileTree } from '../core/file-tree-parse'\nimport { formOf, type Form } from '../core/forms'\nimport { DIAGRAM_FENCE_LANGUAGE, TREE_FENCE_LANGUAGE, type CalloutKind } from '../dialect'\nimport type { Node } from '../engine/types'\nimport { createCodeDrawer, type CodeDrawer } from './code'\nimport { mathRows, mathText } from './math'\nimport { printable } from './printable'\nimport {\n BOLD,\n CODE,\n DIM,\n DONE,\n FAILED,\n MATH,\n merged,\n patch,\n PLAIN,\n WAITING,\n type Line,\n type Span,\n type Style,\n} from './style'\nimport { drawTable } from './table'\nimport { fit, lineWidth, NO_BREAK, span, untabbed, wrap } from './wrap'\nimport { width as columnsOf } from './width'\n\n/** The words the renderer adds, for a terminal in another language. */\nexport interface TerminalLabels {\n callouts: Record<CalloutKind, string>\n /** What a `[!DETAILS]` form with no title of its own is called. */\n details: string\n /** What stands over a diagram's source, which a terminal cannot draw. */\n diagram: string\n}\n\nexport const DEFAULT_LABELS: TerminalLabels = {\n callouts: {\n note: 'Note',\n tip: 'Tip',\n important: 'Important',\n warning: 'Warning',\n caution: 'Caution',\n },\n details: 'Details',\n diagram: 'Diagram',\n}\n\nconst CALLOUT_STYLES: Record<CalloutKind, Style> = {\n note: CODE,\n tip: DONE,\n important: { color: 'magenta' },\n warning: WAITING,\n caution: FAILED,\n}\n\n/** The narrowest a terminal is drawn for: under it, rows are as wide as this. */\nexport const MIN_WIDTH = 8\n\n/**\n * The columns a nested block must leave to write in. A quote in a list in a quote would\n * otherwise leave none, and every row would run past the edge: past this depth the nesting is\n * no longer drawn, and the text still is.\n */\nconst MIN_ROOM = 4\n\n/** Inline forms set off from what is before them by a space the tree does not hold. */\nconst SET_OFF = new Set(['stat-label', 'source-note', 'step-meta'])\n\n/** Paragraphs that head what follows them: nothing parts the two. */\nconst HEADS = new Set([\n 'steps-title',\n 'stats-title',\n 'tabs-title',\n 'verdict-title',\n 'sources-title',\n])\n\n/** A heading by its depth: the first three stand out by weight, the rest by slant. */\nconst HEADINGS: Style[] = [{ bold: true, underline: true }, BOLD, { bold: true, italic: true }]\nconst DEEP_HEADING: Style = { italic: true }\n\n/** A list's bullet by how deep the list is: one shape, then the other, and round again. */\nconst BULLETS = ['•', '◦']\n\n/** The columns between one figure of `[!STATS]` and the next. */\nconst FIGURE_GAP = 3\n\ntype Lines = Span[][]\n\nconst children = (node: Node): Node[] => node.children ?? []\n\n/** A line ending inside a paragraph is a space; everything else is made safe to print. */\nconst flat = (text: string): string => printable(text.replace(/\\n/g, ' '))\n\nfunction push(lines: Lines, text: string, style: Style): void {\n lines.at(-1)!.push(span(flat(text), style))\n}\n\nfunction spans(nodes: Node[], style: Style, lines: Lines): void {\n for (const node of nodes) one(node, style, lines)\n}\n\n/** One inline and what it holds, added to the last of `lines`. */\nfunction one(node: Node, style: Style, lines: Lines): void {\n const form = formOf(node)?.form\n switch (node.type) {\n case 'text':\n case 'html':\n return push(lines, node.value ?? '', style)\n case 'inlineCode':\n // Code keeps to one row where a row holds it: a command broken in two reads as two.\n return push(lines, (node.value ?? '').replaceAll(' ', NO_BREAK), patch(style, CODE))\n case 'inlineMath':\n // A formula keeps to one row where a row holds it, as code does.\n return push(lines, mathText(node.value ?? '').replaceAll(' ', NO_BREAK), patch(style, MATH))\n case 'break':\n lines.push([])\n return\n case 'strong':\n return spans(children(node), patch(style, BOLD), lines)\n case 'delete':\n return spans(children(node), patch(style, { strike: true }), lines)\n case 'link':\n return link(node, patch(style, { underline: true }), lines)\n case 'image': {\n const alt = node.alt || node.url || ''\n push(lines, alt, style)\n if (node.url && node.url !== alt) push(lines, ` (${node.url})`, DIM)\n return\n }\n case 'footnoteReference':\n return push(lines, `[${node.index ?? node.label ?? '?'}]`, DIM)\n case 'emphasis': {\n // A source's host is its address again, and the address is printed after the link.\n if (form === 'source-host') return\n if (form === 'section-number') {\n spans(children(node), style, lines)\n return push(lines, ' ', style)\n }\n if (form === 'caption') {\n lines.push([])\n return spans(children(node), patch(style, DIM), lines)\n }\n if (form && SET_OFF.has(form)) {\n if (lines.at(-1)!.length > 0) push(lines, ' ', style)\n return spans(children(node), patch(style, DIM), lines)\n }\n return spans(children(node), patch(style, { italic: true }), lines)\n }\n default:\n if (node.children) return spans(node.children, style, lines)\n if (typeof node.value === 'string') push(lines, node.value, style)\n }\n}\n\n/** A link's text, and its address after it unless the text is the address. */\nfunction link(node: Node, style: Style, lines: Lines): void {\n const line = lines.length\n let from = lines.at(-1)!.length\n spans(children(node), style, lines)\n if (lines.length !== line) from = 0\n const text = lines\n .at(-1)!\n .slice(from)\n .map((run) => run.text)\n .join('')\n const url = node.url ?? ''\n if (url && text !== flat(url) && !url.startsWith('#')) push(lines, ` (${url})`, DIM)\n}\n\n/** `line` without the spaces at its two ends. */\nfunction trimmed(line: Line): Line {\n const out = line.map((run) => ({ ...run }))\n while (out.length > 0 && out[0]!.text.trimStart() === '') out.shift()\n while (out.length > 0 && out.at(-1)!.text.trimEnd() === '') out.pop()\n if (out.length > 0) {\n out[0]!.text = out[0]!.text.trimStart()\n out[out.length - 1]!.text = out.at(-1)!.text.trimEnd()\n }\n return out\n}\n\n/** A node's inlines as one line: a table cell, a figure. */\nfunction inline(nodes: Node[], style: Style = PLAIN): Line {\n const lines: Lines = [[]]\n spans(nodes, style, lines)\n // A break inside a cell is a space: a cell is one line until it is wrapped.\n return lines.flatMap((line, at) =>\n at > 0 && line.length > 0 ? [span(' ', PLAIN), ...line] : line,\n )\n}\n\nclass Renderer {\n rows: Line[] = []\n /** The prefix of the next row alone: a list marker, a step's number. */\n private first: Line | null = null\n /** The prefix of every row: the bars and indents of what the block is inside. */\n private rest: Line = []\n /** A blank row asked for and not yet drawn: it is, before the next row, if one comes. */\n private gap: Line | null = null\n /** The plain lists this is inside. */\n private depth = 0\n\n constructor(\n private readonly width: number,\n private readonly labels: TerminalLabels,\n private readonly code: CodeDrawer,\n /** The fence that may still be written to, in the last block of a reply that streams. */\n private readonly open: Node | null,\n ) {}\n\n /** A formula on rows of its own: set in from the text, a row for each of its lines. */\n private formula(node: Node): void {\n const rows = mathRows(node.value ?? '')\n if (rows.length === 0) return\n const inset = span(' ', PLAIN)\n this.within(inset, inset, () => {\n for (const row of rows) this.put([span(printable(row), MATH)])\n })\n }\n\n private emit(row: Line): void {\n if (this.gap) {\n if (this.rows.length > 0) this.rows.push(this.gap)\n this.gap = null\n }\n this.rows.push(row)\n }\n\n /** The columns left to write in. */\n private room(): number {\n return Math.max(1, this.width - lineWidth(this.rest))\n }\n\n /** A blank row before whatever is drawn next: the prefix with its trailing spaces dropped. */\n private blank(): Line {\n const row = [...this.rest]\n while (row.length > 0) {\n const last = row.at(-1)!\n const kept = last.text.trimEnd()\n if (kept) {\n row[row.length - 1] = span(kept, last.style)\n break\n }\n row.pop()\n }\n this.gap = row\n return row\n }\n\n private edges(): [Line, Line] {\n const first = this.first ?? this.rest\n this.first = null\n return [first, this.rest]\n }\n\n /** `line`, wrapped under the prefixes. A line with nothing to read draws nothing. */\n private put(line: Line): void {\n if (!line.some((run) => run.text.trim() !== '')) return\n const [first, rest] = this.edges()\n for (const row of wrap(line, this.width, first, rest)) this.emit(row)\n }\n\n /** `body` drawn with `first` before its first row and `rest` before the others. */\n private within(first: Span, rest: Span, body: () => void): void {\n // No room left to nest in: the text is drawn where it is, without the mark.\n if (lineWidth(this.rest) + columnsOf(rest.text) + MIN_ROOM > this.width) return body()\n const outer = this.rest\n this.first = [...(this.first ?? outer), first]\n this.rest = [...outer, rest]\n body()\n // Nothing was drawn under the mark: the mark is drawn alone (an empty list item).\n if (this.first) this.emit(this.first)\n this.first = null\n this.rest = outer\n }\n\n blocks(nodes: Node[], under: 'blocks' | 'item' = 'blocks'): void {\n let before: Node | null = null\n let asked: Line | null = null\n for (const node of nodes) {\n if (before) {\n const head = formOf(before)?.form\n const tight =\n (under === 'item' && (node.type === 'list' || before.type === 'list')) ||\n (head !== undefined && HEADS.has(head)) ||\n // A step's name and the line under it are one paragraph as written.\n (head === 'step-head' && formOf(node)?.form === 'step-body') ||\n formOf(node)?.form === 'quote-source'\n if (!tight) asked = this.blank()\n }\n this.block(node)\n before = node\n }\n // A blank asked for here and never drawn is not owed to whatever comes after this container.\n if (asked !== null && this.gap === asked) this.gap = null\n }\n\n private block(node: Node): void {\n const form = formOf(node)\n switch (node.type) {\n case 'paragraph': {\n // A formula alone in its paragraph (`$$ … $$` on one line) is one set on rows of its own.\n const only = children(node)\n if (!form && only.length === 1 && only[0]!.type === 'inlineMath')\n return this.formula(only[0]!)\n return this.paragraph(node, form)\n }\n case 'heading':\n return this.text(children(node), HEADINGS[(node.depth ?? 1) - 1] ?? DEEP_HEADING)\n case 'thematicBreak':\n return this.put([span('─'.repeat(Math.min(this.room(), 40)), DIM)])\n case 'blockquote':\n return this.quote(node, form)\n case 'list':\n return this.list(node, form)\n case 'code':\n return this.fence(node, form)\n case 'math':\n return this.formula(node)\n case 'html':\n for (const line of (node.value ?? '').split('\\n'))\n this.put([span(printable(untabbed(line)), PLAIN)])\n return\n case 'table':\n return this.table(node)\n case 'footnotes':\n return this.marked(children(node), (index, item) => [`[${item.index ?? index + 1}] `, DIM])\n default:\n if (node.children) return this.blocks(node.children)\n if (typeof node.value === 'string') this.put([span(flat(node.value), PLAIN)])\n }\n }\n\n private paragraph(node: Node, form: Form | undefined): void {\n switch (form?.form) {\n case 'steps-title':\n case 'stats-title':\n case 'tabs-title':\n case 'verdict-title':\n case 'sources-title':\n return this.text(children(node), BOLD)\n case 'quote-source':\n return this.put([span('— ', DIM), ...inline(children(node), DIM)])\n default:\n this.text(children(node), PLAIN)\n }\n }\n\n private text(nodes: Node[], style: Style): void {\n const lines: Lines = [[]]\n spans(nodes, style, lines)\n for (const line of lines) this.put(line)\n }\n\n private quote(node: Node, form: Form | undefined): void {\n switch (form?.form) {\n case 'callout':\n return this.callout(node, form)\n case 'tabs':\n return this.tabs(node)\n case 'verdict':\n return this.verdict(node, form)\n case 'details':\n return this.details(node)\n // What only groups its parts: a form and its title, the two sides of a verdict, one tab.\n case 'form':\n case 'verdicts':\n case 'tab':\n return this.blocks(children(node))\n default: {\n const bar = span('│ ', DIM)\n this.within(bar, bar, () => this.blocks(children(node)))\n }\n }\n }\n\n private callout(node: Node, form: Form): void {\n const kind = (form.kind ?? 'note') as CalloutKind\n const colour = CALLOUT_STYLES[kind] ?? CODE\n const bar = span('▌ ', colour)\n this.within(bar, bar, () => {\n const head: Line = [span(this.labels.callouts[kind] ?? kind, patch(colour, BOLD))]\n if (form.title) head.push(span(' · ', DIM), span(flat(form.title), BOLD))\n this.put(head)\n this.blocks(children(node))\n })\n }\n\n private tabs(node: Node): void {\n children(node).forEach((tab, index) => {\n if (index > 0) this.blank()\n const label = flat(formOf(tab)?.label ?? '')\n const rule = '─'.repeat(Math.max(0, Math.min(this.room(), 40) - (columnsOf(label) + 4)))\n this.put([span('─ ', DIM), span(label, BOLD), span(` ${rule}`, DIM)])\n this.blocks(children(tab))\n })\n }\n\n private verdict(node: Node, form: Form): void {\n const against = form.kind === 'cons'\n for (const part of children(node)) {\n if (part.type === 'list')\n this.marked(children(part), () => (against ? ['- ', FAILED] : ['+ ', DONE]), part.spread)\n else this.block(part)\n }\n }\n\n private details(node: Node): void {\n const parts = children(node)\n const summary = parts.find((part) => formOf(part)?.form === 'summary')\n const title = summary ? inline(children(summary), BOLD) : [span(this.labels.details, BOLD)]\n this.put([span('▾ ', BOLD), ...title])\n const indent = span(' ', PLAIN)\n this.within(indent, indent, () => this.blocks(parts.filter((part) => part !== summary)))\n }\n\n private list(node: Node, form: Form | undefined): void {\n const items = children(node)\n switch (form?.form) {\n case 'steps':\n return this.steps(node)\n case 'stats':\n return this.stats(node)\n case 'sources':\n return this.marked(\n items,\n (index) => [`[${index + 1}] `, DIM],\n node.spread,\n (item) => this.source(item),\n )\n default: {\n const start = node.start ?? 1\n const bullet = `${BULLETS[this.depth % BULLETS.length]} `\n this.depth++\n this.marked(\n items,\n (index, item) => {\n const number = node.ordered ? `${start + index}. ` : ''\n // A task's box stands for its bullet, and after its number.\n if (item.checked === true) return [`${number}✓ `, DONE]\n if (item.checked === false) return [`${number}☐ `, DIM]\n return [number || bullet, DIM]\n },\n node.spread,\n )\n this.depth--\n }\n }\n }\n\n /**\n * One of `[!SOURCES]`: what it is called and what is said of it on a row, and its address on\n * the next, where it is whole whatever the title's length.\n */\n private source(item: Node): void {\n const parts = children(item)\n const paragraph = parts.length === 1 && parts[0]!.type === 'paragraph' ? parts[0]! : null\n const links = children(paragraph ?? item).filter(\n (part) => part.type === 'link' && part.url && !part.url.startsWith('#'),\n )\n // Not the one link and its note this form is written as: the item is drawn as it is.\n if (!paragraph || links.length !== 1) return this.blocks(parts, 'item')\n const lines: Lines = [[]]\n for (const part of children(paragraph)) {\n if (part === links[0]) spans(children(part), { underline: true }, lines)\n else if (formOf(part)?.form === 'source-note') {\n // What is said of a source is set off from its name, which a colour alone would not do.\n push(lines, ' — ', DIM)\n spans(children(part), DIM, lines)\n } else one(part, PLAIN, lines)\n }\n for (const line of lines) this.put(line)\n const url = flat(links[0]!.url ?? '')\n const title = lines.flat().map((run) => run.text)\n if (!title.join('').includes(url)) this.put([span(url, DIM)])\n }\n\n /** Items, each behind its marker with its other rows hung under its text. */\n private marked(\n items: Node[],\n marker: (index: number, item: Node) => [string, Style],\n loose = false,\n body: (item: Node) => void = (item) => this.blocks(children(item), 'item'),\n ): void {\n items.forEach((item, index) => {\n if (index > 0 && loose) this.blank()\n const [mark, style] = marker(index, item)\n this.within(span(mark, style), span(' '.repeat(columnsOf(mark)), PLAIN), () => body(item))\n })\n }\n\n /** `[!STEPS]`: each step's number, and a rail down to the next. */\n private steps(node: Node): void {\n const items = children(node)\n const start = Math.max(1, node.start ?? 1)\n // The numbers end in one column, and the rail runs under it: 9 and 10 are one chain.\n const wide = String(start + items.length - 1).length\n items.forEach((item, index) => {\n if (index > 0) {\n this.gap = null\n this.emit([...this.rest, span('│'.padStart(wide), DIM)])\n }\n const number = span(`${String(start + index).padStart(wide)} `, patch(DONE, BOLD))\n const rail =\n index + 1 === items.length\n ? span(' '.repeat(wide + 2), PLAIN)\n : span(`${'│'.padStart(wide)} `, DIM)\n this.within(number, rail, () => this.blocks(children(item), 'item'))\n })\n }\n\n /**\n * `[!STATS]`: the figures side by side, each number over what it counts, while a row holds\n * them; and a list when it does not.\n */\n private stats(node: Node): void {\n const items = children(node)\n const listed = () => this.marked(items, () => ['• ', DIM], node.spread)\n const figures: Array<{ value: Line; label: Line }> = []\n for (const item of items) {\n const parts = children(item)\n if (parts.length !== 1 || parts[0]!.type !== 'paragraph') return listed()\n const inlines = children(parts[0]!)\n const labels = inlines.filter((part) => formOf(part)?.form === 'stat-label')\n figures.push({\n value: trimmed(inline(inlines.filter((part) => !labels.includes(part)))),\n label: trimmed(inline(labels.flatMap(children), DIM)),\n })\n }\n const widths = figures.map(({ value, label }) => Math.max(lineWidth(value), lineWidth(label)))\n const together =\n widths.reduce((sum, wide) => sum + wide, 0) + Math.max(0, figures.length - 1) * FIGURE_GAP\n if (together > this.room()) return listed()\n const row = (pick: (figure: { value: Line; label: Line }) => Line): void => {\n const line: Line = []\n let owed = 0\n figures.forEach((figure, index) => {\n const cell = pick(figure)\n // The space up to a figure is drawn only when a figure follows it: no row ends in spaces.\n if (cell.length > 0 && owed > 0) line.push(span(' '.repeat(owed), PLAIN))\n if (cell.length > 0) owed = 0\n line.push(...cell)\n owed += widths[index]! - lineWidth(cell) + FIGURE_GAP\n })\n if (line.length > 0) this.exact(line)\n }\n row((figure) => figure.value)\n row((figure) => figure.label)\n }\n\n private fence(node: Node, form: Form | undefined): void {\n const source = node.value ?? ''\n if (form?.title) this.put([span(flat(form.title), DIM)])\n // A diagram is its source here: the row over it says what the source is of.\n else if (node.lang === DIAGRAM_FENCE_LANGUAGE && source.trim())\n this.put([span(this.labels.diagram, DIM)])\n if (node.lang === TREE_FENCE_LANGUAGE) {\n const rows = parseFileTree(source)\n if (rows.length > 0) return this.tree(rows)\n }\n for (const row of this.code.rows(source.replace(/\\n+$/, ''), node.lang, node === this.open))\n this.exact(row)\n }\n\n /**\n * A file tree with the branches `tree` draws: each name hangs from its folder, and a folder's\n * line runs down to the last name in it. What is said of the names stands in one column where\n * a row holds it.\n */\n private tree(rows: ReturnType<typeof parseFileTree>): void {\n // Whether a row is the last in its folder, read from the end: one pass, whatever the nesting.\n const last: boolean[] = []\n const after: boolean[] = []\n for (let at = rows.length - 1; at >= 0; at--) {\n const { depth } = rows[at]!\n last[at] = !after[depth]\n after.length = depth\n after[depth] = true\n }\n /** For each depth, whether the folder there has names still to come. */\n const open: boolean[] = []\n const names = rows.map((row, at): Line => {\n open.length = row.depth\n open[row.depth] = !last[at]\n let guide = ''\n for (let depth = 1; depth < row.depth; depth++) guide += open[depth] ? '│ ' : ' '\n if (row.depth > 0) guide += last[at] ? '└─ ' : '├─ '\n return [span(guide, DIM), span(flat(row.name), row.folder ? BOLD : PLAIN)]\n })\n const said = rows.map((row) => (row.comment ? flat(row.comment) : ''))\n let column = 0\n let widest = 0\n rows.forEach((_, at) => {\n if (!said[at]) return\n column = Math.max(column, lineWidth(names[at]!))\n widest = Math.max(widest, columnsOf(said[at]!))\n })\n const aligned = column + 2 + widest <= this.room()\n rows.forEach((_, at) => {\n const line = names[at]!\n if (said[at]) {\n const gap = aligned ? column - lineWidth(line) + 2 : 2\n line.push(span(' '.repeat(gap), PLAIN), span(said[at]!, DIM))\n }\n this.exact(line)\n })\n }\n\n /** `line` as it is, cut where the row ends: what is aligned by its columns, not by its words. */\n private exact(line: Line): void {\n for (const row of fit(line, this.room())) {\n const [first] = this.edges()\n this.emit([...first, ...row])\n }\n }\n\n private table(node: Node): void {\n const rows = children(node).map((row) => children(row).map((cell) => inline(children(cell))))\n const [first, rest] = this.edges()\n for (const row of drawTable(rows, node.align ?? [], this.width, first, rest)) this.emit(row)\n }\n}\n\n/**\n * The last fence of a tree that holds code, in the order it is read: the one a reply still\n * arriving may be writing. (A fence with no code is passed over: closing a fence left open inside\n * a list leaves an empty one after it.)\n */\nfunction lastFence(node: Node): Node | null {\n const parts = children(node)\n for (let at = parts.length - 1; at >= 0; at--) {\n const found = lastFence(parts[at]!)\n if (found) return found\n }\n return node.type === 'code' && node.value ? node : null\n}\n\n/** Code in its one colour: what a block is drawn with when no view keeps a drawer for it. */\nconst PLAIN_CODE = createCodeDrawer(null)\n\n/**\n * A parsed block (its `root`) as terminal rows no wider than `columns`. `code` colours its fences;\n * `growing` says the block is the end of a reply still arriving: its last fence may gain code.\n */\nexport function drawBlock(\n root: Node,\n columns: number,\n labels: TerminalLabels,\n code: CodeDrawer = PLAIN_CODE,\n growing = false,\n): Line[] {\n const renderer = new Renderer(\n Math.max(MIN_WIDTH, Math.floor(columns)),\n labels,\n code,\n growing ? lastFence(root) : null,\n )\n renderer.blocks(children(root))\n return renderer.rows.map(merged)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2CA,MAAa,iBAAiC;CAC5C,UAAU;EACR,MAAM;EACN,KAAK;EACL,WAAW;EACX,SAAS;EACT,SAAS;CACX;CACA,SAAS;CACT,SAAS;AACX;AAEA,MAAM,iBAA6C;CACjD,MAAM;CACN,KAAK;CACL,WAAW,EAAE,OAAO,UAAU;CAC9B,SAAS;CACT,SAAS;AACX;;AAGA,MAAa,YAAY;;;;;;AAOzB,MAAM,WAAW;;AAGjB,MAAM,0BAAU,IAAI,IAAI;CAAC;CAAc;CAAe;AAAW,CAAC;;AAGlE,MAAM,wBAAQ,IAAI,IAAI;CACpB;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAM,WAAoB;CAAC;EAAE,MAAM;EAAM,WAAW;CAAK;CAAG;CAAM;EAAE,MAAM;EAAM,QAAQ;CAAK;AAAC;AAC9F,MAAM,eAAsB,EAAE,QAAQ,KAAK;;AAG3C,MAAM,UAAU,CAAC,KAAK,GAAG;;AAGzB,MAAM,aAAa;AAInB,MAAM,YAAY,SAAuB,KAAK,YAAY,CAAC;;AAG3D,MAAM,QAAQ,SAAyB,UAAU,KAAK,QAAQ,OAAO,GAAG,CAAC;AAEzE,SAAS,KAAK,OAAc,MAAc,OAAoB;CAC5D,MAAM,GAAG,EAAE,CAAC,CAAE,KAAK,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAC5C;AAEA,SAAS,MAAM,OAAe,OAAc,OAAoB;CAC9D,KAAK,MAAM,QAAQ,OAAO,IAAI,MAAM,OAAO,KAAK;AAClD;;AAGA,SAAS,IAAI,MAAY,OAAc,OAAoB;CACzD,MAAM,OAAO,OAAO,IAAI,CAAC,EAAE;CAC3B,QAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK,QACH,OAAO,KAAK,OAAO,KAAK,SAAS,IAAI,KAAK;EAC5C,KAAK,cAEH,OAAO,KAAK,QAAQ,KAAK,SAAS,GAAA,CAAI,WAAW,KAAA,MAAa,GAAG,MAAM,OAAO,IAAI,CAAC;EACrF,KAAK,cAEH,OAAO,KAAK,OAAO,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,WAAW,KAAA,MAAa,GAAG,MAAM,OAAO,IAAI,CAAC;EAC7F,KAAK;GACH,MAAM,KAAK,CAAC,CAAC;GACb;EACF,KAAK,UACH,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,OAAO,IAAI,GAAG,KAAK;EACxD,KAAK,UACH,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,OAAO,EAAE,QAAQ,KAAK,CAAC,GAAG,KAAK;EACpE,KAAK,QACH,OAAO,KAAK,MAAM,MAAM,OAAO,EAAE,WAAW,KAAK,CAAC,GAAG,KAAK;EAC5D,KAAK,SAAS;GACZ,MAAM,MAAM,KAAK,OAAO,KAAK,OAAO;GACpC,KAAK,OAAO,KAAK,KAAK;GACtB,IAAI,KAAK,OAAO,KAAK,QAAQ,KAAK,KAAK,OAAO,KAAK,KAAK,IAAI,IAAI,GAAG;GACnE;EACF;EACA,KAAK,qBACH,OAAO,KAAK,OAAO,IAAI,KAAK,SAAS,KAAK,SAAS,IAAI,IAAI,GAAG;EAChE,KAAK;GAEH,IAAI,SAAS,eAAe;GAC5B,IAAI,SAAS,kBAAkB;IAC7B,MAAM,SAAS,IAAI,GAAG,OAAO,KAAK;IAClC,OAAO,KAAK,OAAO,KAAK,KAAK;GAC/B;GACA,IAAI,SAAS,WAAW;IACtB,MAAM,KAAK,CAAC,CAAC;IACb,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,OAAO,GAAG,GAAG,KAAK;GACvD;GACA,IAAI,QAAQ,QAAQ,IAAI,IAAI,GAAG;IAC7B,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,SAAS,GAAG,KAAK,OAAO,KAAK,KAAK;IACpD,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,OAAO,GAAG,GAAG,KAAK;GACvD;GACA,OAAO,MAAM,SAAS,IAAI,GAAG,MAAM,OAAO,EAAE,QAAQ,KAAK,CAAC,GAAG,KAAK;EAEpE;GACE,IAAI,KAAK,UAAU,OAAO,MAAM,KAAK,UAAU,OAAO,KAAK;GAC3D,IAAI,OAAO,KAAK,UAAU,UAAU,KAAK,OAAO,KAAK,OAAO,KAAK;CACrE;AACF;;AAGA,SAAS,KAAK,MAAY,OAAc,OAAoB;CAC1D,MAAM,OAAO,MAAM;CACnB,IAAI,OAAO,MAAM,GAAG,EAAE,CAAC,CAAE;CACzB,MAAM,SAAS,IAAI,GAAG,OAAO,KAAK;CAClC,IAAI,MAAM,WAAW,MAAM,OAAO;CAClC,MAAM,OAAO,MACV,GAAG,EAAE,CAAC,CACN,MAAM,IAAI,CAAC,CACX,KAAK,QAAQ,IAAI,IAAI,CAAC,CACtB,KAAK,EAAE;CACV,MAAM,MAAM,KAAK,OAAO;CACxB,IAAI,OAAO,SAAS,KAAK,GAAG,KAAK,CAAC,IAAI,WAAW,GAAG,GAAG,KAAK,OAAO,KAAK,IAAI,IAAI,GAAG;AACrF;;AAGA,SAAS,QAAQ,MAAkB;CACjC,MAAM,MAAM,KAAK,KAAK,SAAS,EAAE,GAAG,IAAI,EAAE;CAC1C,OAAO,IAAI,SAAS,KAAK,IAAI,EAAE,CAAE,KAAK,UAAU,MAAM,IAAI,IAAI,MAAM;CACpE,OAAO,IAAI,SAAS,KAAK,IAAI,GAAG,EAAE,CAAC,CAAE,KAAK,QAAQ,MAAM,IAAI,IAAI,IAAI;CACpE,IAAI,IAAI,SAAS,GAAG;EAClB,IAAI,EAAE,CAAE,OAAO,IAAI,EAAE,CAAE,KAAK,UAAU;EACtC,IAAI,IAAI,SAAS,EAAE,CAAE,OAAO,IAAI,GAAG,EAAE,CAAC,CAAE,KAAK,QAAQ;CACvD;CACA,OAAO;AACT;;AAGA,SAAS,OAAO,OAAe,QAAe,OAAa;CACzD,MAAM,QAAe,CAAC,CAAC,CAAC;CACxB,MAAM,OAAO,OAAO,KAAK;CAEzB,OAAO,MAAM,SAAS,MAAM,OAC1B,KAAK,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK,KAAK,GAAG,GAAG,IAAI,IAAI,IAC5D;AACF;AAEA,IAAM,WAAN,MAAe;CAYM;CACA;CACA;CAEA;CAfnB,OAAe,CAAC;;CAEhB,QAA6B;;CAE7B,OAAqB,CAAC;;CAEtB,MAA2B;;CAE3B,QAAgB;CAEhB,YACE,OACA,QACA,MAEA,MACA;EALiB,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,OAAA;EAEA,KAAA,OAAA;CAChB;;CAGH,QAAgB,MAAkB;EAChC,MAAM,OAAO,SAAS,KAAK,SAAS,EAAE;EACtC,IAAI,KAAK,WAAW,GAAG;EACvB,MAAM,QAAQ,KAAK,MAAM,KAAK;EAC9B,KAAK,OAAO,OAAO,aAAa;GAC9B,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,CAAC,KAAK,UAAU,GAAG,GAAG,IAAI,CAAC,CAAC;EAC/D,CAAC;CACH;CAEA,KAAa,KAAiB;EAC5B,IAAI,KAAK,KAAK;GACZ,IAAI,KAAK,KAAK,SAAS,GAAG,KAAK,KAAK,KAAK,KAAK,GAAG;GACjD,KAAK,MAAM;EACb;EACA,KAAK,KAAK,KAAK,GAAG;CACpB;;CAGA,OAAuB;EACrB,OAAO,KAAK,IAAI,GAAG,KAAK,QAAQ,UAAU,KAAK,IAAI,CAAC;CACtD;;CAGA,QAAsB;EACpB,MAAM,MAAM,CAAC,GAAG,KAAK,IAAI;EACzB,OAAO,IAAI,SAAS,GAAG;GACrB,MAAM,OAAO,IAAI,GAAG,EAAE;GACtB,MAAM,OAAO,KAAK,KAAK,QAAQ;GAC/B,IAAI,MAAM;IACR,IAAI,IAAI,SAAS,KAAK,KAAK,MAAM,KAAK,KAAK;IAC3C;GACF;GACA,IAAI,IAAI;EACV;EACA,KAAK,MAAM;EACX,OAAO;CACT;CAEA,QAA8B;EAC5B,MAAM,QAAQ,KAAK,SAAS,KAAK;EACjC,KAAK,QAAQ;EACb,OAAO,CAAC,OAAO,KAAK,IAAI;CAC1B;;CAGA,IAAY,MAAkB;EAC5B,IAAI,CAAC,KAAK,MAAM,QAAQ,IAAI,KAAK,KAAK,MAAM,EAAE,GAAG;EACjD,MAAM,CAAC,OAAO,QAAQ,KAAK,MAAM;EACjC,KAAK,MAAM,OAAO,KAAK,MAAM,KAAK,OAAO,OAAO,IAAI,GAAG,KAAK,KAAK,GAAG;CACtE;;CAGA,OAAe,OAAa,MAAY,MAAwB;EAE9D,IAAI,UAAU,KAAK,IAAI,IAAIA,MAAU,KAAK,IAAI,IAAI,WAAW,KAAK,OAAO,OAAO,KAAK;EACrF,MAAM,QAAQ,KAAK;EACnB,KAAK,QAAQ,CAAC,GAAI,KAAK,SAAS,OAAQ,KAAK;EAC7C,KAAK,OAAO,CAAC,GAAG,OAAO,IAAI;EAC3B,KAAK;EAEL,IAAI,KAAK,OAAO,KAAK,KAAK,KAAK,KAAK;EACpC,KAAK,QAAQ;EACb,KAAK,OAAO;CACd;CAEA,OAAO,OAAe,QAA2B,UAAgB;EAC/D,IAAI,SAAsB;EAC1B,IAAI,QAAqB;EACzB,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,QAAQ;IACV,MAAM,OAAO,OAAO,MAAM,CAAC,EAAE;IAO7B,IAAI,EALD,UAAU,WAAW,KAAK,SAAS,UAAU,OAAO,SAAS,WAC7D,SAAS,KAAA,KAAa,MAAM,IAAI,IAAI,KAEpC,SAAS,eAAe,OAAO,IAAI,CAAC,EAAE,SAAS,eAChD,OAAO,IAAI,CAAC,EAAE,SAAS,iBACb,QAAQ,KAAK,MAAM;GACjC;GACA,KAAK,MAAM,IAAI;GACf,SAAS;EACX;EAEA,IAAI,UAAU,QAAQ,KAAK,QAAQ,OAAO,KAAK,MAAM;CACvD;CAEA,MAAc,MAAkB;EAC9B,MAAM,OAAO,OAAO,IAAI;EACxB,QAAQ,KAAK,MAAb;GACE,KAAK,aAAa;IAEhB,MAAM,OAAO,SAAS,IAAI;IAC1B,IAAI,CAAC,QAAQ,KAAK,WAAW,KAAK,KAAK,EAAE,CAAE,SAAS,cAClD,OAAO,KAAK,QAAQ,KAAK,EAAG;IAC9B,OAAO,KAAK,UAAU,MAAM,IAAI;GAClC;GACA,KAAK,WACH,OAAO,KAAK,KAAK,SAAS,IAAI,GAAG,UAAU,KAAK,SAAS,KAAK,MAAM,YAAY;GAClF,KAAK,iBACH,OAAO,KAAK,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC;GACpE,KAAK,cACH,OAAO,KAAK,MAAM,MAAM,IAAI;GAC9B,KAAK,QACH,OAAO,KAAK,KAAK,MAAM,IAAI;GAC7B,KAAK,QACH,OAAO,KAAK,MAAM,MAAM,IAAI;GAC9B,KAAK,QACH,OAAO,KAAK,QAAQ,IAAI;GAC1B,KAAK;IACH,KAAK,MAAM,SAAS,KAAK,SAAS,GAAA,CAAI,MAAM,IAAI,GAC9C,KAAK,IAAI,CAAC,KAAK,UAAU,SAAS,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IACnD;GACF,KAAK,SACH,OAAO,KAAK,MAAM,IAAI;GACxB,KAAK,aACH,OAAO,KAAK,OAAO,SAAS,IAAI,IAAI,OAAO,SAAS,CAAC,IAAI,KAAK,SAAS,QAAQ,EAAE,KAAK,GAAG,CAAC;GAC5F;IACE,IAAI,KAAK,UAAU,OAAO,KAAK,OAAO,KAAK,QAAQ;IACnD,IAAI,OAAO,KAAK,UAAU,UAAU,KAAK,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,KAAK,CAAC,CAAC;EAChF;CACF;CAEA,UAAkB,MAAY,MAA8B;EAC1D,QAAQ,MAAM,MAAd;GACE,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK,iBACH,OAAO,KAAK,KAAK,SAAS,IAAI,GAAG,IAAI;GACvC,KAAK,gBACH,OAAO,KAAK,IAAI,CAAC,KAAK,MAAM,GAAG,GAAG,GAAG,OAAO,SAAS,IAAI,GAAG,GAAG,CAAC,CAAC;GACnE,SACE,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK;EACnC;CACF;CAEA,KAAa,OAAe,OAAoB;EAC9C,MAAM,QAAe,CAAC,CAAC,CAAC;EACxB,MAAM,OAAO,OAAO,KAAK;EACzB,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI,IAAI;CACzC;CAEA,MAAc,MAAY,MAA8B;EACtD,QAAQ,MAAM,MAAd;GACE,KAAK,WACH,OAAO,KAAK,QAAQ,MAAM,IAAI;GAChC,KAAK,QACH,OAAO,KAAK,KAAK,IAAI;GACvB,KAAK,WACH,OAAO,KAAK,QAAQ,MAAM,IAAI;GAChC,KAAK,WACH,OAAO,KAAK,QAAQ,IAAI;GAE1B,KAAK;GACL,KAAK;GACL,KAAK,OACH,OAAO,KAAK,OAAO,SAAS,IAAI,CAAC;GACnC,SAAS;IACP,MAAM,MAAM,KAAK,MAAM,GAAG;IAC1B,KAAK,OAAO,KAAK,WAAW,KAAK,OAAO,SAAS,IAAI,CAAC,CAAC;GACzD;EACF;CACF;CAEA,QAAgB,MAAY,MAAkB;EAC5C,MAAM,OAAQ,KAAK,QAAQ;EAC3B,MAAM,SAAS,eAAe,SAAS;EACvC,MAAM,MAAM,KAAK,MAAM,MAAM;EAC7B,KAAK,OAAO,KAAK,WAAW;GAC1B,MAAM,OAAa,CAAC,KAAK,KAAK,OAAO,SAAS,SAAS,MAAM,MAAM,QAAQ,IAAI,CAAC,CAAC;GACjF,IAAI,KAAK,OAAO,KAAK,KAAK,KAAK,OAAO,GAAG,GAAG,KAAK,KAAK,KAAK,KAAK,GAAG,IAAI,CAAC;GACxE,KAAK,IAAI,IAAI;GACb,KAAK,OAAO,SAAS,IAAI,CAAC;EAC5B,CAAC;CACH;CAEA,KAAa,MAAkB;EAC7B,SAAS,IAAI,CAAC,CAAC,SAAS,KAAK,UAAU;GACrC,IAAI,QAAQ,GAAG,KAAK,MAAM;GAC1B,MAAM,QAAQ,KAAK,OAAO,GAAG,CAAC,EAAE,SAAS,EAAE;GAC3C,MAAM,OAAO,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,GAAG,EAAE,KAAKA,MAAU,KAAK,IAAI,EAAE,CAAC;GACvF,KAAK,IAAI;IAAC,KAAK,MAAM,GAAG;IAAG,KAAK,OAAO,IAAI;IAAG,KAAK,IAAI,QAAQ,GAAG;GAAC,CAAC;GACpE,KAAK,OAAO,SAAS,GAAG,CAAC;EAC3B,CAAC;CACH;CAEA,QAAgB,MAAY,MAAkB;EAC5C,MAAM,UAAU,KAAK,SAAS;EAC9B,KAAK,MAAM,QAAQ,SAAS,IAAI,GAC9B,IAAI,KAAK,SAAS,QAChB,KAAK,OAAO,SAAS,IAAI,SAAU,UAAU,CAAC,MAAM,MAAM,IAAI,CAAC,MAAM,IAAI,GAAI,KAAK,MAAM;OACrF,KAAK,MAAM,IAAI;CAExB;CAEA,QAAgB,MAAkB;EAChC,MAAM,QAAQ,SAAS,IAAI;EAC3B,MAAM,UAAU,MAAM,MAAM,SAAS,OAAO,IAAI,CAAC,EAAE,SAAS,SAAS;EACrE,MAAM,QAAQ,UAAU,OAAO,SAAS,OAAO,GAAG,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,SAAS,IAAI,CAAC;EAC1F,KAAK,IAAI,CAAC,KAAK,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC;EACrC,MAAM,SAAS,KAAK,MAAM,KAAK;EAC/B,KAAK,OAAO,QAAQ,cAAc,KAAK,OAAO,MAAM,QAAQ,SAAS,SAAS,OAAO,CAAC,CAAC;CACzF;CAEA,KAAa,MAAY,MAA8B;EACrD,MAAM,QAAQ,SAAS,IAAI;EAC3B,QAAQ,MAAM,MAAd;GACE,KAAK,SACH,OAAO,KAAK,MAAM,IAAI;GACxB,KAAK,SACH,OAAO,KAAK,MAAM,IAAI;GACxB,KAAK,WACH,OAAO,KAAK,OACV,QACC,UAAU,CAAC,IAAI,QAAQ,EAAE,KAAK,GAAG,GAClC,KAAK,SACJ,SAAS,KAAK,OAAO,IAAI,CAC5B;GACF,SAAS;IACP,MAAM,QAAQ,KAAK,SAAS;IAC5B,MAAM,SAAS,GAAG,QAAQ,KAAK,QAAQ,QAAQ,QAAQ;IACvD,KAAK;IACL,KAAK,OACH,QACC,OAAO,SAAS;KACf,MAAM,SAAS,KAAK,UAAU,GAAG,QAAQ,MAAM,MAAM;KAErD,IAAI,KAAK,YAAY,MAAM,OAAO,CAAC,GAAG,OAAO,KAAK,IAAI;KACtD,IAAI,KAAK,YAAY,OAAO,OAAO,CAAC,GAAG,OAAO,KAAK,GAAG;KACtD,OAAO,CAAC,UAAU,QAAQ,GAAG;IAC/B,GACA,KAAK,MACP;IACA,KAAK;GACP;EACF;CACF;;;;;CAMA,OAAe,MAAkB;EAC/B,MAAM,QAAQ,SAAS,IAAI;EAC3B,MAAM,YAAY,MAAM,WAAW,KAAK,MAAM,EAAE,CAAE,SAAS,cAAc,MAAM,KAAM;EACrF,MAAM,QAAQ,SAAS,aAAa,IAAI,CAAC,CAAC,QACvC,SAAS,KAAK,SAAS,UAAU,KAAK,OAAO,CAAC,KAAK,IAAI,WAAW,GAAG,CACxE;EAEA,IAAI,CAAC,aAAa,MAAM,WAAW,GAAG,OAAO,KAAK,OAAO,OAAO,MAAM;EACtE,MAAM,QAAe,CAAC,CAAC,CAAC;EACxB,KAAK,MAAM,QAAQ,SAAS,SAAS,GACnC,IAAI,SAAS,MAAM,IAAI,MAAM,SAAS,IAAI,GAAG,EAAE,WAAW,KAAK,GAAG,KAAK;OAClE,IAAI,OAAO,IAAI,CAAC,EAAE,SAAS,eAAe;GAE7C,KAAK,OAAO,OAAO,GAAG;GACtB,MAAM,SAAS,IAAI,GAAG,KAAK,KAAK;EAClC,OAAO,IAAI,MAAM,OAAO,KAAK;EAE/B,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI,IAAI;EACvC,MAAM,MAAM,KAAK,MAAM,EAAE,CAAE,OAAO,EAAE;EAEpC,IAAI,CADU,MAAM,KAAK,CAAC,CAAC,KAAK,QAAQ,IAAI,IACnC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,KAAK,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC;CAC9D;;CAGA,OACE,OACA,QACA,QAAQ,OACR,QAA8B,SAAS,KAAK,OAAO,SAAS,IAAI,GAAG,MAAM,GACnE;EACN,MAAM,SAAS,MAAM,UAAU;GAC7B,IAAI,QAAQ,KAAK,OAAO,KAAK,MAAM;GACnC,MAAM,CAAC,MAAM,SAAS,OAAO,OAAO,IAAI;GACxC,KAAK,OAAO,KAAK,MAAM,KAAK,GAAG,KAAK,IAAI,OAAOA,MAAU,IAAI,CAAC,GAAG,KAAK,SAAS,KAAK,IAAI,CAAC;EAC3F,CAAC;CACH;;CAGA,MAAc,MAAkB;EAC9B,MAAM,QAAQ,SAAS,IAAI;EAC3B,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC;EAEzC,MAAM,OAAO,OAAO,QAAQ,MAAM,SAAS,CAAC,CAAC,CAAC;EAC9C,MAAM,SAAS,MAAM,UAAU;GAC7B,IAAI,QAAQ,GAAG;IACb,KAAK,MAAM;IACX,KAAK,KAAK,CAAC,GAAG,KAAK,MAAM,KAAK,IAAI,SAAS,IAAI,GAAG,GAAG,CAAC,CAAC;GACzD;GACA,MAAM,SAAS,KAAK,GAAG,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,IAAI,EAAE,KAAK,MAAM,MAAM,IAAI,CAAC;GAClF,MAAM,OACJ,QAAQ,MAAM,MAAM,SAChB,KAAK,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,IAChC,KAAK,GAAG,IAAI,SAAS,IAAI,EAAE,KAAK,GAAG;GACzC,KAAK,OAAO,QAAQ,YAAY,KAAK,OAAO,SAAS,IAAI,GAAG,MAAM,CAAC;EACrE,CAAC;CACH;;;;;CAMA,MAAc,MAAkB;EAC9B,MAAM,QAAQ,SAAS,IAAI;EAC3B,MAAM,eAAe,KAAK,OAAO,aAAa,CAAC,MAAM,GAAG,GAAG,KAAK,MAAM;EACtE,MAAM,UAA+C,CAAC;EACtD,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,SAAS,IAAI;GAC3B,IAAI,MAAM,WAAW,KAAK,MAAM,EAAE,CAAE,SAAS,aAAa,OAAO,OAAO;GACxE,MAAM,UAAU,SAAS,MAAM,EAAG;GAClC,MAAM,SAAS,QAAQ,QAAQ,SAAS,OAAO,IAAI,CAAC,EAAE,SAAS,YAAY;GAC3E,QAAQ,KAAK;IACX,OAAO,QAAQ,OAAO,QAAQ,QAAQ,SAAS,CAAC,OAAO,SAAS,IAAI,CAAC,CAAC,CAAC;IACvE,OAAO,QAAQ,OAAO,OAAO,QAAQ,QAAQ,GAAG,GAAG,CAAC;GACtD,CAAC;EACH;EACA,MAAM,SAAS,QAAQ,KAAK,EAAE,OAAO,YAAY,KAAK,IAAI,UAAU,KAAK,GAAG,UAAU,KAAK,CAAC,CAAC;EAG7F,IADE,OAAO,QAAQ,KAAK,SAAS,MAAM,MAAM,CAAC,IAAI,KAAK,IAAI,GAAG,QAAQ,SAAS,CAAC,IAAI,aACnE,KAAK,KAAK,GAAG,OAAO,OAAO;EAC1C,MAAM,OAAO,SAA+D;GAC1E,MAAM,OAAa,CAAC;GACpB,IAAI,OAAO;GACX,QAAQ,SAAS,QAAQ,UAAU;IACjC,MAAM,OAAO,KAAK,MAAM;IAExB,IAAI,KAAK,SAAS,KAAK,OAAO,GAAG,KAAK,KAAK,KAAK,IAAI,OAAO,IAAI,GAAG,KAAK,CAAC;IACxE,IAAI,KAAK,SAAS,GAAG,OAAO;IAC5B,KAAK,KAAK,GAAG,IAAI;IACjB,QAAQ,OAAO,SAAU,UAAU,IAAI,IAAI;GAC7C,CAAC;GACD,IAAI,KAAK,SAAS,GAAG,KAAK,MAAM,IAAI;EACtC;EACA,KAAK,WAAW,OAAO,KAAK;EAC5B,KAAK,WAAW,OAAO,KAAK;CAC9B;CAEA,MAAc,MAAY,MAA8B;EACtD,MAAM,SAAS,KAAK,SAAS;EAC7B,IAAI,MAAM,OAAO,KAAK,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,GAAG,CAAC,CAAC;OAElD,IAAI,KAAK,SAAA,aAAmC,OAAO,KAAK,GAC3D,KAAK,IAAI,CAAC,KAAK,KAAK,OAAO,SAAS,GAAG,CAAC,CAAC;EAC3C,IAAI,KAAK,SAAA,QAA8B;GACrC,MAAM,OAAO,cAAc,MAAM;GACjC,IAAI,KAAK,SAAS,GAAG,OAAO,KAAK,KAAK,IAAI;EAC5C;EACA,KAAK,MAAM,OAAO,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,EAAE,GAAG,KAAK,MAAM,SAAS,KAAK,IAAI,GACxF,KAAK,MAAM,GAAG;CAClB;;;;;;CAOA,KAAa,MAA8C;EAEzD,MAAM,OAAkB,CAAC;EACzB,MAAM,QAAmB,CAAC;EAC1B,KAAK,IAAI,KAAK,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM;GAC5C,MAAM,EAAE,UAAU,KAAK;GACvB,KAAK,MAAM,CAAC,MAAM;GAClB,MAAM,SAAS;GACf,MAAM,SAAS;EACjB;;EAEA,MAAM,OAAkB,CAAC;EACzB,MAAM,QAAQ,KAAK,KAAK,KAAK,OAAa;GACxC,KAAK,SAAS,IAAI;GAClB,KAAK,IAAI,SAAS,CAAC,KAAK;GACxB,IAAI,QAAQ;GACZ,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,OAAO,SAAS,SAAS,KAAK,SAAS,QAAQ;GAC/E,IAAI,IAAI,QAAQ,GAAG,SAAS,KAAK,MAAM,QAAQ;GAC/C,OAAO,CAAC,KAAK,OAAO,GAAG,GAAG,KAAK,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,OAAO,KAAK,CAAC;EAC3E,CAAC;EACD,MAAM,OAAO,KAAK,KAAK,QAAS,IAAI,UAAU,KAAK,IAAI,OAAO,IAAI,EAAG;EACrE,IAAI,SAAS;EACb,IAAI,SAAS;EACb,KAAK,SAAS,GAAG,OAAO;GACtB,IAAI,CAAC,KAAK,KAAK;GACf,SAAS,KAAK,IAAI,QAAQ,UAAU,MAAM,GAAI,CAAC;GAC/C,SAAS,KAAK,IAAI,QAAQA,MAAU,KAAK,GAAI,CAAC;EAChD,CAAC;EACD,MAAM,UAAU,SAAS,IAAI,UAAU,KAAK,KAAK;EACjD,KAAK,SAAS,GAAG,OAAO;GACtB,MAAM,OAAO,MAAM;GACnB,IAAI,KAAK,KAAK;IACZ,MAAM,MAAM,UAAU,SAAS,UAAU,IAAI,IAAI,IAAI;IACrD,KAAK,KAAK,KAAK,IAAI,OAAO,GAAG,GAAG,KAAK,GAAG,KAAK,KAAK,KAAM,GAAG,CAAC;GAC9D;GACA,KAAK,MAAM,IAAI;EACjB,CAAC;CACH;;CAGA,MAAc,MAAkB;EAC9B,KAAK,MAAM,OAAO,IAAI,MAAM,KAAK,KAAK,CAAC,GAAG;GACxC,MAAM,CAAC,SAAS,KAAK,MAAM;GAC3B,KAAK,KAAK,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC;EAC9B;CACF;CAEA,MAAc,MAAkB;EAC9B,MAAM,OAAO,SAAS,IAAI,CAAC,CAAC,KAAK,QAAQ,SAAS,GAAG,CAAC,CAAC,KAAK,SAAS,OAAO,SAAS,IAAI,CAAC,CAAC,CAAC;EAC5F,MAAM,CAAC,OAAO,QAAQ,KAAK,MAAM;EACjC,KAAK,MAAM,OAAO,UAAU,MAAM,KAAK,SAAS,CAAC,GAAG,KAAK,OAAO,OAAO,IAAI,GAAG,KAAK,KAAK,GAAG;CAC7F;AACF;;;;;;AAOA,SAAS,UAAU,MAAyB;CAC1C,MAAM,QAAQ,SAAS,IAAI;CAC3B,KAAK,IAAI,KAAK,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM;EAC7C,MAAM,QAAQ,UAAU,MAAM,GAAI;EAClC,IAAI,OAAO,OAAO;CACpB;CACA,OAAO,KAAK,SAAS,UAAU,KAAK,QAAQ,OAAO;AACrD;;AAGA,MAAM,aAAa,iBAAiB,IAAI;;;;;AAMxC,SAAgB,UACd,MACA,SACA,QACA,OAAmB,YACnB,UAAU,OACF;CACR,MAAM,WAAW,IAAI,SACnB,KAAK,IAAA,GAAe,KAAK,MAAM,OAAO,CAAC,GACvC,QACA,MACA,UAAU,UAAU,IAAI,IAAI,IAC9B;CACA,SAAS,OAAO,SAAS,IAAI,CAAC;CAC9B,OAAO,SAAS,KAAK,IAAI,MAAM;AACjC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What a terminal row is made of: runs of text, each with how it is drawn. Nothing here knows an
3
+ * escape sequence but {@link toAnsi}: a host with its own screen (a TUI library, a test) reads
4
+ * the spans and draws them its own way.
5
+ */
6
+ /**
7
+ * The colours the renderer uses: the ones every terminal theme has a readable shade of. Blue is
8
+ * left out for that reason: on many dark themes it is too dark to read.
9
+ */
10
+ export type Colour = 'red' | 'green' | 'yellow' | 'cyan' | 'magenta';
11
+ /** How a run of text is drawn. A field left out is off. */
12
+ export interface Style {
13
+ bold?: boolean;
14
+ dim?: boolean;
15
+ italic?: boolean;
16
+ underline?: boolean;
17
+ strike?: boolean;
18
+ color?: Colour;
19
+ }
20
+ /** A run of text in one style. Its text holds no control character: see `printable.ts`. */
21
+ export interface Span {
22
+ text: string;
23
+ style: Style;
24
+ }
25
+ /** One row of the terminal. A blank row has no spans. */
26
+ export type Line = Span[];
27
+ /** The parameters of the escape sequence that turns `style` on; '' for plain text. */
28
+ export declare function sgr(style: Style): string;
29
+ /** A row as its text alone. */
30
+ export declare const lineText: (line: Line) => string;
31
+ /**
32
+ * Rows as text for a terminal, one per line. With `color`, each styled run is wrapped in the
33
+ * sequence that draws it and the one that resets it; without, the text is all there is.
34
+ */
35
+ export declare function toAnsi(lines: readonly Line[], color?: boolean): string;