@react-x11/components 0.10.0 → 0.12.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 (155) hide show
  1. package/dist/code/index.d.ts.map +1 -1
  2. package/dist/code/index.js +25 -24
  3. package/dist/code/index.js.map +1 -1
  4. package/dist/code-language/index.d.ts +1 -1
  5. package/dist/code-language/index.d.ts.map +1 -1
  6. package/dist/code-language/index.js +1 -1
  7. package/dist/code-language/index.js.map +1 -1
  8. package/dist/code-language/runs.d.ts +31 -0
  9. package/dist/code-language/runs.d.ts.map +1 -1
  10. package/dist/code-language/runs.js +105 -0
  11. package/dist/code-language/runs.js.map +1 -1
  12. package/dist/codeblock/index.d.ts +13 -0
  13. package/dist/codeblock/index.d.ts.map +1 -1
  14. package/dist/codeblock/index.js +37 -1
  15. package/dist/codeblock/index.js.map +1 -1
  16. package/dist/flow/node.d.ts +14 -6
  17. package/dist/flow/node.d.ts.map +1 -1
  18. package/dist/flow/node.js +82 -64
  19. package/dist/flow/node.js.map +1 -1
  20. package/dist/html/controls.d.ts +26 -7
  21. package/dist/html/controls.d.ts.map +1 -1
  22. package/dist/html/controls.js +28 -3
  23. package/dist/html/controls.js.map +1 -1
  24. package/dist/html/css/cascade.d.ts +32 -10
  25. package/dist/html/css/cascade.d.ts.map +1 -1
  26. package/dist/html/css/cascade.js +89 -31
  27. package/dist/html/css/cascade.js.map +1 -1
  28. package/dist/html/css/color.d.ts +19 -0
  29. package/dist/html/css/color.d.ts.map +1 -1
  30. package/dist/html/css/color.js +86 -0
  31. package/dist/html/css/color.js.map +1 -1
  32. package/dist/html/css/parse.d.ts +11 -0
  33. package/dist/html/css/parse.d.ts.map +1 -1
  34. package/dist/html/css/parse.js +114 -4
  35. package/dist/html/css/parse.js.map +1 -1
  36. package/dist/html/css/style.d.ts +45 -5
  37. package/dist/html/css/style.d.ts.map +1 -1
  38. package/dist/html/css/style.js +363 -14
  39. package/dist/html/css/style.js.map +1 -1
  40. package/dist/html/css/ua.js +22 -5
  41. package/dist/html/css/ua.js.map +1 -1
  42. package/dist/html/css/values.d.ts +10 -1
  43. package/dist/html/css/values.d.ts.map +1 -1
  44. package/dist/html/css/values.js +27 -2
  45. package/dist/html/css/values.js.map +1 -1
  46. package/dist/html/css/vars.d.ts +5 -0
  47. package/dist/html/css/vars.d.ts.map +1 -1
  48. package/dist/html/css/vars.js +2 -2
  49. package/dist/html/css/vars.js.map +1 -1
  50. package/dist/html/dom.d.ts.map +1 -1
  51. package/dist/html/dom.js +35 -2
  52. package/dist/html/dom.js.map +1 -1
  53. package/dist/html/index.d.ts.map +1 -1
  54. package/dist/html/index.js +38 -1
  55. package/dist/html/index.js.map +1 -1
  56. package/dist/html/layout/block.d.ts +11 -2
  57. package/dist/html/layout/block.d.ts.map +1 -1
  58. package/dist/html/layout/block.js +306 -25
  59. package/dist/html/layout/block.js.map +1 -1
  60. package/dist/html/layout/boxes.d.ts +61 -6
  61. package/dist/html/layout/boxes.d.ts.map +1 -1
  62. package/dist/html/layout/boxes.js +65 -16
  63. package/dist/html/layout/boxes.js.map +1 -1
  64. package/dist/html/layout/cache.d.ts +2 -2
  65. package/dist/html/layout/cache.d.ts.map +1 -1
  66. package/dist/html/layout/cache.js +8 -3
  67. package/dist/html/layout/cache.js.map +1 -1
  68. package/dist/html/layout/floats.d.ts +4 -0
  69. package/dist/html/layout/floats.d.ts.map +1 -1
  70. package/dist/html/layout/floats.js +26 -0
  71. package/dist/html/layout/floats.js.map +1 -1
  72. package/dist/html/layout/inline.d.ts +29 -2
  73. package/dist/html/layout/inline.d.ts.map +1 -1
  74. package/dist/html/layout/inline.js +1454 -125
  75. package/dist/html/layout/inline.js.map +1 -1
  76. package/dist/html/node.d.ts +33 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +608 -86
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +53 -1
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +746 -214
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/surfaces.d.ts +7 -2
  85. package/dist/html/surfaces.d.ts.map +1 -1
  86. package/dist/html/surfaces.js +16 -0
  87. package/dist/html/surfaces.js.map +1 -1
  88. package/dist/html/svg.d.ts.map +1 -1
  89. package/dist/html/svg.js +27 -20
  90. package/dist/html/svg.js.map +1 -1
  91. package/dist/internal/codelines.d.ts +42 -0
  92. package/dist/internal/codelines.d.ts.map +1 -0
  93. package/dist/internal/codelines.js +170 -0
  94. package/dist/internal/codelines.js.map +1 -0
  95. package/dist/internal/window.d.ts +22 -0
  96. package/dist/internal/window.d.ts.map +1 -1
  97. package/dist/internal/window.js +35 -8
  98. package/dist/internal/window.js.map +1 -1
  99. package/dist/markdown/index.d.ts.map +1 -1
  100. package/dist/markdown/index.js +98 -26
  101. package/dist/markdown/index.js.map +1 -1
  102. package/dist/rich-text-editor/index.d.ts.map +1 -1
  103. package/dist/rich-text-editor/index.js +3 -8
  104. package/dist/rich-text-editor/index.js.map +1 -1
  105. package/dist/rich-text-editor/markdown.d.ts.map +1 -1
  106. package/dist/rich-text-editor/markdown.js +30 -1
  107. package/dist/rich-text-editor/markdown.js.map +1 -1
  108. package/dist/rich-text-editor/replace.d.ts +13 -0
  109. package/dist/rich-text-editor/replace.d.ts.map +1 -0
  110. package/dist/rich-text-editor/replace.js +64 -0
  111. package/dist/rich-text-editor/replace.js.map +1 -0
  112. package/dist/richtext/node.d.ts +7 -0
  113. package/dist/richtext/node.d.ts.map +1 -1
  114. package/dist/richtext/node.js.map +1 -1
  115. package/dist/terminal-output/index.d.ts.map +1 -1
  116. package/dist/terminal-output/index.js +100 -25
  117. package/dist/terminal-output/index.js.map +1 -1
  118. package/dist/terminal-output/runs.d.ts +4 -1
  119. package/dist/terminal-output/runs.d.ts.map +1 -1
  120. package/dist/terminal-output/runs.js +5 -1
  121. package/dist/terminal-output/runs.js.map +1 -1
  122. package/package.json +5 -3
  123. package/src/code/index.ts +34 -32
  124. package/src/code-language/index.ts +1 -1
  125. package/src/code-language/runs.ts +111 -1
  126. package/src/codeblock/index.ts +48 -1
  127. package/src/flow/node.ts +80 -64
  128. package/src/html/controls.ts +50 -9
  129. package/src/html/css/cascade.ts +134 -43
  130. package/src/html/css/color.ts +87 -0
  131. package/src/html/css/parse.ts +107 -4
  132. package/src/html/css/style.ts +386 -23
  133. package/src/html/css/ua.ts +22 -5
  134. package/src/html/css/values.ts +31 -3
  135. package/src/html/css/vars.ts +2 -2
  136. package/src/html/dom.ts +38 -2
  137. package/src/html/index.ts +39 -1
  138. package/src/html/layout/block.ts +360 -22
  139. package/src/html/layout/boxes.ts +98 -23
  140. package/src/html/layout/cache.ts +8 -3
  141. package/src/html/layout/floats.ts +24 -0
  142. package/src/html/layout/inline.ts +1690 -119
  143. package/src/html/node.ts +648 -101
  144. package/src/html/paint.ts +883 -283
  145. package/src/html/surfaces.ts +21 -1
  146. package/src/html/svg.ts +30 -19
  147. package/src/internal/codelines.ts +200 -0
  148. package/src/internal/window.ts +36 -8
  149. package/src/markdown/index.ts +145 -26
  150. package/src/rich-text-editor/index.ts +3 -11
  151. package/src/rich-text-editor/markdown.ts +36 -1
  152. package/src/rich-text-editor/replace.ts +85 -0
  153. package/src/richtext/node.ts +7 -0
  154. package/src/terminal-output/index.ts +127 -34
  155. package/src/terminal-output/runs.ts +16 -2
@@ -27,6 +27,7 @@ import { parse } from '../internal/markdown/parse.js';
27
27
  import { stringify } from '../internal/markdown/stringify.js';
28
28
  import type {
29
29
  BlockNode,
30
+ Document,
30
31
  InlineNode,
31
32
  LinkInline,
32
33
  ListItem,
@@ -43,11 +44,45 @@ export interface MarkdownCodec {
43
44
  /** The codec for `schema`: the reference names, TipTap's, or both. */
44
45
  export function markdownCodec(schema: Schema): MarkdownCodec {
45
46
  return {
46
- parse: (source) => docFromMarkdown(schema, source),
47
+ parse: markdownReader(schema),
47
48
  serialize: (doc) => markdownFromDoc(doc),
48
49
  };
49
50
  }
50
51
 
52
+ /**
53
+ * `docFromMarkdown` for a document read again and again: a value that
54
+ * streams in, or a controlled value an app keeps handing over. Each parse
55
+ * resumes from the last one (`ParseOptions.previous`), which hands back
56
+ * the blocks it did not have to read again as the same objects, and a
57
+ * block seen before becomes the nodes it became then. So a word streamed
58
+ * into a long document converts the paragraph it lands in, and the
59
+ * document's other blocks are the nodes the editor already holds.
60
+ */
61
+ function markdownReader(schema: Schema): (source: string) => PMNode {
62
+ let last: Document | null = null;
63
+ const converted = new WeakMap<BlockNode, PMNode[]>();
64
+ return (source) => {
65
+ const ast = parse(
66
+ source,
67
+ last ? { partial: false, previous: last } : { partial: false },
68
+ );
69
+ last = ast;
70
+ const blocks: PMNode[] = [];
71
+ for (const block of ast.blocks) {
72
+ let nodes = converted.get(block);
73
+ if (!nodes) {
74
+ nodes = blockToNodes(schema, block);
75
+ converted.set(block, nodes);
76
+ }
77
+ for (const node of nodes) blocks.push(node);
78
+ }
79
+ return (
80
+ schema.topNodeType.createAndFill(null, blocks) ??
81
+ schema.topNodeType.createAndFill()!
82
+ );
83
+ };
84
+ }
85
+
51
86
  // --- names -------------------------------------------------------------------
52
87
 
53
88
  /** The spellings each thing goes by: ProseMirror's reference schemas first,
@@ -0,0 +1,85 @@
1
+ // A document handed in from outside — a controlled `value`, the handle's
2
+ // `setValue` — put into the editor.
3
+ //
4
+ // Replacing the whole document with it made every block a new node: each
5
+ // one was keyed again and drawn again, and a plugin that walks what a
6
+ // transaction changed (prosemirror-tables' `fixTables`) walked all of it. A
7
+ // value that streams in — a model writing into the editor — did that for
8
+ // every word it gained: 75 ms a word at 3,200 paragraphs. Pure: no React,
9
+ // and `test/rich-text-editor-model.test.ts` asks it the rest.
10
+ import type { Node as PMNode } from 'prosemirror-model';
11
+ import { Selection } from 'prosemirror-state';
12
+ import type { EditorState, Transaction } from 'prosemirror-state';
13
+
14
+ /**
15
+ * The transaction that makes `state`'s document `doc`: a reset, as it
16
+ * always was. To the history, the selection and every plugin that maps a
17
+ * position through it, the whole document is replaced — so an undo reaches
18
+ * nothing from before, the draft an app just cleared included. What changes
19
+ * is which nodes the new document is made of: where it is the same as the
20
+ * old one, it is the old one's nodes, so the blocks the value left alone
21
+ * keep their keys and their drawn elements.
22
+ */
23
+ export function replaceDocumentTr(
24
+ state: EditorState,
25
+ doc: PMNode,
26
+ addToHistory: boolean,
27
+ ): Transaction {
28
+ // First the document as itself, over the whole of it: the step the
29
+ // history and the mapping see, the one they always saw — every position
30
+ // inside the document replaced. It puts back the node objects that were
31
+ // there, so nothing is drawn again for it.
32
+ const tr = state.tr.replaceWith(0, state.doc.content.size, state.doc.content);
33
+ // Then the change, in steps made against the same document.
34
+ for (const step of changedPart(state, doc)?.steps ?? []) tr.step(step);
35
+ // keep the caret about where it was, inside what is there now
36
+ tr.setSelection(
37
+ Selection.near(
38
+ tr.doc.resolve(Math.min(state.selection.head, tr.doc.content.size)),
39
+ ),
40
+ );
41
+ if (!addToHistory) tr.setMeta('addToHistory', false);
42
+ return tr;
43
+ }
44
+
45
+ /** A transaction replacing only where `doc` differs from `state`'s
46
+ * document; null when it does not. */
47
+ function changedPart(state: EditorState, doc: PMNode): Transaction | null {
48
+ const before = state.doc.content;
49
+ const start = before.findDiffStart(doc.content);
50
+ if (start === null) return null;
51
+ let { a: endA, b: endB } = before.findDiffEnd(doc.content)!;
52
+ // Where the change repeats what is beside it ("aa" to "aaa"), the ends
53
+ // found from the back reach past the start found from the front: move
54
+ // both ends on by the overlap, over content the two documents share.
55
+ const overlap = start - Math.min(endA, endB);
56
+ if (overlap > 0) {
57
+ endA += overlap;
58
+ endB += overlap;
59
+ }
60
+ let tr = state.tr.replace(start, endA, doc.slice(start, endB));
61
+ if (tr.doc.eq(doc)) return tr;
62
+ // A slice that opens inside a table can be fitted into another shape than
63
+ // the value's. Whole blocks always fit between the ones the two documents
64
+ // share: the top-level blocks the change reaches, then.
65
+ const from = blockStart(state.doc, start);
66
+ tr = state.tr.replaceWith(
67
+ from,
68
+ blockEnd(state.doc, endA),
69
+ doc.content.cut(from, blockEnd(doc, endB)),
70
+ );
71
+ if (tr.doc.eq(doc)) return tr;
72
+ return state.tr.replaceWith(0, state.doc.content.size, doc.content);
73
+ }
74
+
75
+ /** Where the top-level block `pos` is in starts; `pos` itself between two. */
76
+ function blockStart(doc: PMNode, pos: number): number {
77
+ const $pos = doc.resolve(pos);
78
+ return $pos.depth === 0 ? pos : $pos.before(1);
79
+ }
80
+
81
+ /** Where the top-level block `pos` is in ends; `pos` itself between two. */
82
+ function blockEnd(doc: PMNode, pos: number): number {
83
+ const $pos = doc.resolve(pos);
84
+ return $pos.depth === 0 ? pos : $pos.after(1);
85
+ }
@@ -129,6 +129,13 @@ export interface TextRun {
129
129
  * either side of an inline box's margin, border or padding. ntk's, from
130
130
  * 8.14.2. */
131
131
  shapeApart?: boolean;
132
+ /** Kerned with the runs either side of it though its letter spacing is
133
+ * not theirs: the spacing a justified line or `word-spacing` gives a
134
+ * space is in addition to kerning and no element's (CSS Text 3, 7.2,
135
+ * 7.3), where a run of an element's own letter spacing is shaped apart,
136
+ * as a browser shapes it. ntk's, from 8.15.0; CoreText takes a spaced
137
+ * glyph's spacing in place of its pairs either way. */
138
+ kernAcross?: boolean;
132
139
  }
133
140
 
134
141
  /** The props `<richtext>` takes. */
@@ -32,6 +32,7 @@ import { ansiPalette, parseAnsi } from '../ansi/index.js';
32
32
  import type {
33
33
  AnsiDocument,
34
34
  AnsiInput,
35
+ AnsiLine,
35
36
  AnsiPaletteOptions,
36
37
  } from '../ansi/index.js';
37
38
  import {
@@ -40,13 +41,13 @@ import {
40
41
  useLinkClicks,
41
42
  useSelectionMenu,
42
43
  } from '../richtext/index.js';
43
- import type { RichTextProps, TextRun } from '../richtext/index.js';
44
+ import type { RichTextProps } from '../richtext/index.js';
44
45
  import {
45
46
  codeBlockLook,
46
47
  codeBlockStyle,
47
48
  codeTextStyle,
48
49
  } from '../codeblock/index.js';
49
- import { documentRuns } from './runs.js';
50
+ import { linesRuns } from './runs.js';
50
51
  import { hx } from './hx.js';
51
52
  import { useFontPrewarm } from '../internal/prewarm.js';
52
53
 
@@ -56,6 +57,27 @@ const h = React.createElement;
56
57
  // the note in `../richtext/node.ts`.
57
58
  registerRichText();
58
59
 
60
+ /**
61
+ * Lines a block of output holds.
62
+ *
63
+ * The output is a column of blocks rather than one element, so an append
64
+ * lays out again the block it lands in and not the whole capture: one
65
+ * `<richtext>` for all of it was laid out whole for every line appended,
66
+ * 0.9 s an append at 20,000 lines. A block keeps its element while its lines
67
+ * are the same objects, which the parser shares from one snapshot to the
68
+ * next. Blocks start at multiples of this in the capture's own numbering,
69
+ * `truncated` counted in, so `maxLines` dropping lines off the top changes
70
+ * only the first block.
71
+ */
72
+ const BLOCK_LINES = 256;
73
+
74
+ /** One block's lines, and the elements they were drawn as. */
75
+ interface Block {
76
+ lines: readonly AnsiLine[];
77
+ output: ReactElement;
78
+ gutter: ReactElement | null;
79
+ }
80
+
59
81
  /** What `data` accepts on top of raw bytes: a document parsed elsewhere. */
60
82
  export type TerminalOutputSource = AnsiInput | AnsiDocument;
61
83
 
@@ -273,41 +295,105 @@ export function TerminalOutput(props: TerminalOutputProps): ReactElement {
273
295
  [colors, look.color, theme.background],
274
296
  );
275
297
 
276
- const runs: TextRun[] = React.useMemo(
277
- () =>
278
- documentRuns(document, { family: look.family, size: look.size }, palette),
279
- [document, look.family, look.size, palette],
280
- );
281
-
282
- const outputProps: RichTextProps = {
283
- runs,
284
- style: codeTextStyle(look, wrap),
298
+ // Kept across renders while what a block is drawn with holds.
299
+ const blocks = React.useRef<{
300
+ look: typeof look;
301
+ palette: typeof palette;
302
+ wrap: boolean;
303
+ lineNumbers: boolean;
304
+ byIndex: Map<number, Block>;
305
+ } | null>(null);
306
+ if (
307
+ blocks.current?.look !== look ||
308
+ blocks.current.palette !== palette ||
309
+ blocks.current.wrap !== wrap ||
310
+ blocks.current.lineNumbers !== lineNumbers
311
+ ) {
312
+ blocks.current = { look, palette, wrap, lineNumbers, byIndex: new Map() };
313
+ }
314
+ const byIndex = blocks.current.byIndex;
315
+ const font = { family: look.family, size: look.size };
316
+ const outputStyle = codeTextStyle(look, wrap);
317
+ const gutterStyle: Style = {
318
+ lineHeight: look.lineHeight,
319
+ textAlign: 'right',
285
320
  };
286
- if (!wrap) outputProps.wrap = false;
287
321
 
288
- const lineCount = document.lines.length;
322
+ // The capture's own numbering: the first line shown is the one after
323
+ // every line `maxLines` has dropped.
324
+ const first = document.truncated;
325
+ const end = first + document.lines.length;
326
+ const outputs: ReactElement[] = [];
327
+ const gutters: ReactElement[] = [];
328
+ const kept = new Set<number>();
329
+ for (
330
+ let start = first - (first % BLOCK_LINES);
331
+ start < end || outputs.length === 0;
332
+ start += BLOCK_LINES
333
+ ) {
334
+ const index = start / BLOCK_LINES;
335
+ const from = Math.max(start, first) - first;
336
+ const to = Math.min(start + BLOCK_LINES, end) - first;
337
+ const lines = document.lines.slice(from, to);
338
+ let block = byIndex.get(index);
339
+ if (!block || !sameLines(block.lines, lines)) {
340
+ const outputProps: RichTextProps = {
341
+ runs: linesRuns(lines, font, palette),
342
+ style: outputStyle,
343
+ };
344
+ if (!wrap) outputProps.wrap = false;
345
+ block = {
346
+ lines,
347
+ output: h(RICHTEXT_ELEMENT, {
348
+ key: index,
349
+ ...outputProps,
350
+ } as Record<string, unknown>),
351
+ gutter: lineNumbers
352
+ ? h(RICHTEXT_ELEMENT, {
353
+ key: index,
354
+ // The numbers are chrome, not text: `selectable={false}` keeps
355
+ // them out of a drag and out of the copied text.
356
+ selectable: false,
357
+ runs: lines.map((_, i) => ({
358
+ text:
359
+ i === lines.length - 1
360
+ ? `${first + from + i + 1}`
361
+ : `${first + from + i + 1}\n`,
362
+ family: look.family,
363
+ size: look.size,
364
+ color: look.dim,
365
+ })),
366
+ wrap: false,
367
+ style: gutterStyle,
368
+ } as Record<string, unknown>)
369
+ : null,
370
+ };
371
+ byIndex.set(index, block);
372
+ }
373
+ kept.add(index);
374
+ outputs.push(block.output);
375
+ if (block.gutter) gutters.push(block.gutter);
376
+ }
377
+ for (const index of byIndex.keys()) {
378
+ if (!kept.has(index)) byIndex.delete(index);
379
+ }
380
+
381
+ // One column of numbers per column of output, block beside block: the
382
+ // same font and line height keep them in register.
289
383
  const gutter = lineNumbers
290
- ? h(RICHTEXT_ELEMENT, {
291
- // One richtext for all the numbers: same font, same line height, so
292
- // it stays in register with the output beside it. `selectable={false}`
293
- // is what keeps it out of a drag and out of the copied text.
294
- selectable: false,
295
- runs: Array.from({ length: lineCount }, (_, i) => ({
296
- text: i === lineCount - 1 ? `${i + 1}` : `${i + 1}\n`,
297
- family: look.family,
298
- size: look.size,
299
- color: look.dim,
300
- })),
301
- wrap: false,
302
- style: {
303
- lineHeight: look.lineHeight,
304
- textAlign: 'right',
305
- minWidth:
306
- Math.max(String(lineCount).length, 2) * Math.ceil(look.size * 0.62),
307
- marginRight: look.padding,
308
- flexShrink: 0,
384
+ ? hx(
385
+ 'box',
386
+ {
387
+ style: {
388
+ flexDirection: 'column',
389
+ minWidth:
390
+ Math.max(String(end).length, 2) * Math.ceil(look.size * 0.62),
391
+ marginRight: look.padding,
392
+ flexShrink: 0,
393
+ },
309
394
  },
310
- } as Record<string, unknown>)
395
+ ...gutters,
396
+ )
311
397
  : null;
312
398
 
313
399
  const rootStyle: Style = {
@@ -339,7 +425,14 @@ export function TerminalOutput(props: TerminalOutputProps): ReactElement {
339
425
  hx(
340
426
  'box',
341
427
  { style: { overflow: 'scroll', flexDirection: 'column', flexGrow: 1 } },
342
- h(RICHTEXT_ELEMENT, outputProps as unknown as Record<string, unknown>),
428
+ ...outputs,
343
429
  ),
344
430
  );
345
431
  }
432
+
433
+ /** Whether two runs of lines are the same objects, one for one. */
434
+ function sameLines(a: readonly AnsiLine[], b: readonly AnsiLine[]): boolean {
435
+ if (a.length !== b.length) return false;
436
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
437
+ return true;
438
+ }
@@ -5,7 +5,12 @@
5
5
  // parser said "ANSI 2", `../ansi/palette.ts` said which pixels that is, and
6
6
  // here it becomes a run the text layout can take.
7
7
  import { resolveAnsiColors } from '../ansi/index.js';
8
- import type { AnsiDocument, AnsiPalette, AnsiSpan } from '../ansi/index.js';
8
+ import type {
9
+ AnsiDocument,
10
+ AnsiLine,
11
+ AnsiPalette,
12
+ AnsiSpan,
13
+ } from '../ansi/index.js';
9
14
  import type { TextRun } from '../richtext/index.js';
10
15
 
11
16
  /** The font a capture is set in. The palette is separate because it changes
@@ -64,9 +69,18 @@ export function documentRuns(
64
69
  document: AnsiDocument,
65
70
  font: OutputFont,
66
71
  palette: AnsiPalette,
72
+ ): TextRun[] {
73
+ return linesRuns(document.lines, font, palette);
74
+ }
75
+
76
+ /** Some of a document's lines as runs, the way {@link documentRuns} makes
77
+ * all of them: a block of `<TerminalOutput>`'s output. */
78
+ export function linesRuns(
79
+ lines: readonly AnsiLine[],
80
+ font: OutputFont,
81
+ palette: AnsiPalette,
67
82
  ): TextRun[] {
68
83
  const runs: TextRun[] = [];
69
- const { lines } = document;
70
84
  for (let i = 0; i < lines.length; i++) {
71
85
  for (const span of lines[i]!.spans) {
72
86
  if (span.text) runs.push(spanRun(span, font, palette));