@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.
- package/dist/code/index.d.ts.map +1 -1
- package/dist/code/index.js +25 -24
- package/dist/code/index.js.map +1 -1
- package/dist/code-language/index.d.ts +1 -1
- package/dist/code-language/index.d.ts.map +1 -1
- package/dist/code-language/index.js +1 -1
- package/dist/code-language/index.js.map +1 -1
- package/dist/code-language/runs.d.ts +31 -0
- package/dist/code-language/runs.d.ts.map +1 -1
- package/dist/code-language/runs.js +105 -0
- package/dist/code-language/runs.js.map +1 -1
- package/dist/codeblock/index.d.ts +13 -0
- package/dist/codeblock/index.d.ts.map +1 -1
- package/dist/codeblock/index.js +37 -1
- package/dist/codeblock/index.js.map +1 -1
- package/dist/flow/node.d.ts +14 -6
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +82 -64
- package/dist/flow/node.js.map +1 -1
- package/dist/html/controls.d.ts +26 -7
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +28 -3
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +32 -10
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +89 -31
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +19 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +86 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +11 -0
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +114 -4
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +45 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +363 -14
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +22 -5
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +10 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +27 -2
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/css/vars.d.ts +5 -0
- package/dist/html/css/vars.d.ts.map +1 -1
- package/dist/html/css/vars.js +2 -2
- package/dist/html/css/vars.js.map +1 -1
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +35 -2
- package/dist/html/dom.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +38 -1
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +11 -2
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +306 -25
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +61 -6
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +65 -16
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +2 -2
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +8 -3
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/floats.d.ts +4 -0
- package/dist/html/layout/floats.d.ts.map +1 -1
- package/dist/html/layout/floats.js +26 -0
- package/dist/html/layout/floats.js.map +1 -1
- package/dist/html/layout/inline.d.ts +29 -2
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +1454 -125
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +33 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +608 -86
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +53 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +746 -214
- package/dist/html/paint.js.map +1 -1
- package/dist/html/surfaces.d.ts +7 -2
- package/dist/html/surfaces.d.ts.map +1 -1
- package/dist/html/surfaces.js +16 -0
- package/dist/html/surfaces.js.map +1 -1
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +27 -20
- package/dist/html/svg.js.map +1 -1
- package/dist/internal/codelines.d.ts +42 -0
- package/dist/internal/codelines.d.ts.map +1 -0
- package/dist/internal/codelines.js +170 -0
- package/dist/internal/codelines.js.map +1 -0
- package/dist/internal/window.d.ts +22 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +35 -8
- package/dist/internal/window.js.map +1 -1
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +98 -26
- package/dist/markdown/index.js.map +1 -1
- package/dist/rich-text-editor/index.d.ts.map +1 -1
- package/dist/rich-text-editor/index.js +3 -8
- package/dist/rich-text-editor/index.js.map +1 -1
- package/dist/rich-text-editor/markdown.d.ts.map +1 -1
- package/dist/rich-text-editor/markdown.js +30 -1
- package/dist/rich-text-editor/markdown.js.map +1 -1
- package/dist/rich-text-editor/replace.d.ts +13 -0
- package/dist/rich-text-editor/replace.d.ts.map +1 -0
- package/dist/rich-text-editor/replace.js +64 -0
- package/dist/rich-text-editor/replace.js.map +1 -0
- package/dist/richtext/node.d.ts +7 -0
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/terminal-output/index.d.ts.map +1 -1
- package/dist/terminal-output/index.js +100 -25
- package/dist/terminal-output/index.js.map +1 -1
- package/dist/terminal-output/runs.d.ts +4 -1
- package/dist/terminal-output/runs.d.ts.map +1 -1
- package/dist/terminal-output/runs.js +5 -1
- package/dist/terminal-output/runs.js.map +1 -1
- package/package.json +5 -3
- package/src/code/index.ts +34 -32
- package/src/code-language/index.ts +1 -1
- package/src/code-language/runs.ts +111 -1
- package/src/codeblock/index.ts +48 -1
- package/src/flow/node.ts +80 -64
- package/src/html/controls.ts +50 -9
- package/src/html/css/cascade.ts +134 -43
- package/src/html/css/color.ts +87 -0
- package/src/html/css/parse.ts +107 -4
- package/src/html/css/style.ts +386 -23
- package/src/html/css/ua.ts +22 -5
- package/src/html/css/values.ts +31 -3
- package/src/html/css/vars.ts +2 -2
- package/src/html/dom.ts +38 -2
- package/src/html/index.ts +39 -1
- package/src/html/layout/block.ts +360 -22
- package/src/html/layout/boxes.ts +98 -23
- package/src/html/layout/cache.ts +8 -3
- package/src/html/layout/floats.ts +24 -0
- package/src/html/layout/inline.ts +1690 -119
- package/src/html/node.ts +648 -101
- package/src/html/paint.ts +883 -283
- package/src/html/surfaces.ts +21 -1
- package/src/html/svg.ts +30 -19
- package/src/internal/codelines.ts +200 -0
- package/src/internal/window.ts +36 -8
- package/src/markdown/index.ts +145 -26
- package/src/rich-text-editor/index.ts +3 -11
- package/src/rich-text-editor/markdown.ts +36 -1
- package/src/rich-text-editor/replace.ts +85 -0
- package/src/richtext/node.ts +7 -0
- package/src/terminal-output/index.ts +127 -34
- 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: (
|
|
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
|
+
}
|
package/src/richtext/node.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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));
|