@react-x11/components 0.10.0 → 0.11.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/html/css/cascade.d.ts +32 -10
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +72 -27
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/style.d.ts +7 -4
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +37 -6
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +8 -5
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +127 -9
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +27 -0
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +30 -2
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +236 -14
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +118 -57
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +203 -37
- package/dist/html/paint.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/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/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 +1 -1
- 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/html/css/cascade.ts +117 -39
- package/src/html/css/style.ts +43 -15
- package/src/html/css/ua.ts +8 -5
- package/src/html/layout/block.ts +134 -8
- package/src/html/layout/boxes.ts +43 -2
- package/src/html/layout/inline.ts +257 -13
- package/src/html/node.ts +145 -79
- package/src/html/paint.ts +236 -45
- package/src/html/svg.ts +30 -19
- package/src/internal/codelines.ts +200 -0
- 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/terminal-output/index.ts +127 -34
- package/src/terminal-output/runs.ts +16 -2
|
@@ -27,13 +27,26 @@ import { useTheme } from 'react-x11';
|
|
|
27
27
|
import { ansiPalette, parseAnsi } from '../ansi/index.js';
|
|
28
28
|
import { registerRichText, RICHTEXT_ELEMENT, useLinkClicks, useSelectionMenu, } from '../richtext/index.js';
|
|
29
29
|
import { codeBlockLook, codeBlockStyle, codeTextStyle, } from '../codeblock/index.js';
|
|
30
|
-
import {
|
|
30
|
+
import { linesRuns } from './runs.js';
|
|
31
31
|
import { hx } from './hx.js';
|
|
32
32
|
import { useFontPrewarm } from '../internal/prewarm.js';
|
|
33
33
|
const h = React.createElement;
|
|
34
34
|
// The shared-module registration, at this component's own module scope — see
|
|
35
35
|
// the note in `../richtext/node.ts`.
|
|
36
36
|
registerRichText();
|
|
37
|
+
/**
|
|
38
|
+
* Lines a block of output holds.
|
|
39
|
+
*
|
|
40
|
+
* The output is a column of blocks rather than one element, so an append
|
|
41
|
+
* lays out again the block it lands in and not the whole capture: one
|
|
42
|
+
* `<richtext>` for all of it was laid out whole for every line appended,
|
|
43
|
+
* 0.9 s an append at 20,000 lines. A block keeps its element while its lines
|
|
44
|
+
* are the same objects, which the parser shares from one snapshot to the
|
|
45
|
+
* next. Blocks start at multiples of this in the capture's own numbering,
|
|
46
|
+
* `truncated` counted in, so `maxLines` dropping lines off the top changes
|
|
47
|
+
* only the first block.
|
|
48
|
+
*/
|
|
49
|
+
const BLOCK_LINES = 256;
|
|
37
50
|
function isDocument(source) {
|
|
38
51
|
return (typeof source === 'object' &&
|
|
39
52
|
source !== null &&
|
|
@@ -128,35 +141,88 @@ export function TerminalOutput(props) {
|
|
|
128
141
|
foreground: colors?.foreground ?? look.color,
|
|
129
142
|
background: colors?.background ?? String(theme.background ?? '#ffffff'),
|
|
130
143
|
}), [colors, look.color, theme.background]);
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
// Kept across renders while what a block is drawn with holds.
|
|
145
|
+
const blocks = React.useRef(null);
|
|
146
|
+
if (blocks.current?.look !== look ||
|
|
147
|
+
blocks.current.palette !== palette ||
|
|
148
|
+
blocks.current.wrap !== wrap ||
|
|
149
|
+
blocks.current.lineNumbers !== lineNumbers) {
|
|
150
|
+
blocks.current = { look, palette, wrap, lineNumbers, byIndex: new Map() };
|
|
151
|
+
}
|
|
152
|
+
const byIndex = blocks.current.byIndex;
|
|
153
|
+
const font = { family: look.family, size: look.size };
|
|
154
|
+
const outputStyle = codeTextStyle(look, wrap);
|
|
155
|
+
const gutterStyle = {
|
|
156
|
+
lineHeight: look.lineHeight,
|
|
157
|
+
textAlign: 'right',
|
|
135
158
|
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
159
|
+
// The capture's own numbering: the first line shown is the one after
|
|
160
|
+
// every line `maxLines` has dropped.
|
|
161
|
+
const first = document.truncated;
|
|
162
|
+
const end = first + document.lines.length;
|
|
163
|
+
const outputs = [];
|
|
164
|
+
const gutters = [];
|
|
165
|
+
const kept = new Set();
|
|
166
|
+
for (let start = first - (first % BLOCK_LINES); start < end || outputs.length === 0; start += BLOCK_LINES) {
|
|
167
|
+
const index = start / BLOCK_LINES;
|
|
168
|
+
const from = Math.max(start, first) - first;
|
|
169
|
+
const to = Math.min(start + BLOCK_LINES, end) - first;
|
|
170
|
+
const lines = document.lines.slice(from, to);
|
|
171
|
+
let block = byIndex.get(index);
|
|
172
|
+
if (!block || !sameLines(block.lines, lines)) {
|
|
173
|
+
const outputProps = {
|
|
174
|
+
runs: linesRuns(lines, font, palette),
|
|
175
|
+
style: outputStyle,
|
|
176
|
+
};
|
|
177
|
+
if (!wrap)
|
|
178
|
+
outputProps.wrap = false;
|
|
179
|
+
block = {
|
|
180
|
+
lines,
|
|
181
|
+
output: h(RICHTEXT_ELEMENT, {
|
|
182
|
+
key: index,
|
|
183
|
+
...outputProps,
|
|
184
|
+
}),
|
|
185
|
+
gutter: lineNumbers
|
|
186
|
+
? h(RICHTEXT_ELEMENT, {
|
|
187
|
+
key: index,
|
|
188
|
+
// The numbers are chrome, not text: `selectable={false}` keeps
|
|
189
|
+
// them out of a drag and out of the copied text.
|
|
190
|
+
selectable: false,
|
|
191
|
+
runs: lines.map((_, i) => ({
|
|
192
|
+
text: i === lines.length - 1
|
|
193
|
+
? `${first + from + i + 1}`
|
|
194
|
+
: `${first + from + i + 1}\n`,
|
|
195
|
+
family: look.family,
|
|
196
|
+
size: look.size,
|
|
197
|
+
color: look.dim,
|
|
198
|
+
})),
|
|
199
|
+
wrap: false,
|
|
200
|
+
style: gutterStyle,
|
|
201
|
+
})
|
|
202
|
+
: null,
|
|
203
|
+
};
|
|
204
|
+
byIndex.set(index, block);
|
|
205
|
+
}
|
|
206
|
+
kept.add(index);
|
|
207
|
+
outputs.push(block.output);
|
|
208
|
+
if (block.gutter)
|
|
209
|
+
gutters.push(block.gutter);
|
|
210
|
+
}
|
|
211
|
+
for (const index of byIndex.keys()) {
|
|
212
|
+
if (!kept.has(index))
|
|
213
|
+
byIndex.delete(index);
|
|
214
|
+
}
|
|
215
|
+
// One column of numbers per column of output, block beside block: the
|
|
216
|
+
// same font and line height keep them in register.
|
|
139
217
|
const gutter = lineNumbers
|
|
140
|
-
?
|
|
141
|
-
// One richtext for all the numbers: same font, same line height, so
|
|
142
|
-
// it stays in register with the output beside it. `selectable={false}`
|
|
143
|
-
// is what keeps it out of a drag and out of the copied text.
|
|
144
|
-
selectable: false,
|
|
145
|
-
runs: Array.from({ length: lineCount }, (_, i) => ({
|
|
146
|
-
text: i === lineCount - 1 ? `${i + 1}` : `${i + 1}\n`,
|
|
147
|
-
family: look.family,
|
|
148
|
-
size: look.size,
|
|
149
|
-
color: look.dim,
|
|
150
|
-
})),
|
|
151
|
-
wrap: false,
|
|
218
|
+
? hx('box', {
|
|
152
219
|
style: {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
minWidth: Math.max(String(lineCount).length, 2) * Math.ceil(look.size * 0.62),
|
|
220
|
+
flexDirection: 'column',
|
|
221
|
+
minWidth: Math.max(String(end).length, 2) * Math.ceil(look.size * 0.62),
|
|
156
222
|
marginRight: look.padding,
|
|
157
223
|
flexShrink: 0,
|
|
158
224
|
},
|
|
159
|
-
})
|
|
225
|
+
}, ...gutters)
|
|
160
226
|
: null;
|
|
161
227
|
const rootStyle = {
|
|
162
228
|
...codeBlockStyle(look),
|
|
@@ -179,6 +245,15 @@ export function TerminalOutput(props) {
|
|
|
179
245
|
...menu,
|
|
180
246
|
...links,
|
|
181
247
|
'data-testname': props['data-testname'],
|
|
182
|
-
}, gutter, hx('box', { style: { overflow: 'scroll', flexDirection: 'column', flexGrow: 1 } },
|
|
248
|
+
}, gutter, hx('box', { style: { overflow: 'scroll', flexDirection: 'column', flexGrow: 1 } }, ...outputs));
|
|
249
|
+
}
|
|
250
|
+
/** Whether two runs of lines are the same objects, one for one. */
|
|
251
|
+
function sameLines(a, b) {
|
|
252
|
+
if (a.length !== b.length)
|
|
253
|
+
return false;
|
|
254
|
+
for (let i = 0; i < a.length; i++)
|
|
255
|
+
if (a[i] !== b[i])
|
|
256
|
+
return false;
|
|
257
|
+
return true;
|
|
183
258
|
}
|
|
184
259
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terminal-output/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,8EAA8E;AAC9E,gEAAgE;AAChE,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,uBAAuB;AACvB,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAIrC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terminal-output/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnsiDocument, AnsiPalette, AnsiSpan } from '../ansi/index.js';
|
|
1
|
+
import type { AnsiDocument, AnsiLine, AnsiPalette, AnsiSpan } from '../ansi/index.js';
|
|
2
2
|
import type { TextRun } from '../richtext/index.js';
|
|
3
3
|
/** The font a capture is set in. The palette is separate because it changes
|
|
4
4
|
* for a different reason — a theme flip rather than a size prop. */
|
|
@@ -24,4 +24,7 @@ export declare function spanRun(span: AnsiSpan, font: OutputFont, palette: AnsiP
|
|
|
24
24
|
* terminal never drew.
|
|
25
25
|
*/
|
|
26
26
|
export declare function documentRuns(document: AnsiDocument, font: OutputFont, palette: AnsiPalette): TextRun[];
|
|
27
|
+
/** Some of a document's lines as runs, the way {@link documentRuns} makes
|
|
28
|
+
* all of them: a block of `<TerminalOutput>`'s output. */
|
|
29
|
+
export declare function linesRuns(lines: readonly AnsiLine[], font: OutputFont, palette: AnsiPalette): TextRun[];
|
|
27
30
|
//# sourceMappingURL=runs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/terminal-output/runs.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/terminal-output/runs.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;qEACqE;AACrE,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,CAuBT;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,EAAE,CAEX;AAED;2DAC2D;AAC3D,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,EAAE,CAWX"}
|
|
@@ -50,8 +50,12 @@ export function spanRun(span, font, palette) {
|
|
|
50
50
|
* terminal never drew.
|
|
51
51
|
*/
|
|
52
52
|
export function documentRuns(document, font, palette) {
|
|
53
|
+
return linesRuns(document.lines, font, palette);
|
|
54
|
+
}
|
|
55
|
+
/** Some of a document's lines as runs, the way {@link documentRuns} makes
|
|
56
|
+
* all of them: a block of `<TerminalOutput>`'s output. */
|
|
57
|
+
export function linesRuns(lines, font, palette) {
|
|
53
58
|
const runs = [];
|
|
54
|
-
const { lines } = document;
|
|
55
59
|
for (let i = 0; i < lines.length; i++) {
|
|
56
60
|
for (const span of lines[i].spans) {
|
|
57
61
|
if (span.text)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/terminal-output/runs.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4DAA4D;AAC5D,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,kDAAkD;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/terminal-output/runs.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4DAA4D;AAC5D,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,kDAAkD;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAgBrD;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CACrB,IAAc,EACd,IAAgB,EAChB,OAAoB;IAEpB,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,GAAG,GAAY;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,EAAE;KACV,CAAC;IACF,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;QACZ,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IACD,IAAI,IAAI,CAAC,IAAI;QAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM;QAAE,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC;IACtC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,cAAc;YACjC,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,cAAc,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE;YAC5D,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC;IACtC,CAAC;IACD,IAAI,IAAI,CAAC,MAAM;QAAE,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;IACjC,IAAI,IAAI,CAAC,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACpC,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,QAAsB,EACtB,IAAgB,EAChB,OAAoB;IAEpB,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AAED;2DAC2D;AAC3D,MAAM,UAAU,SAAS,CACvB,KAA0B,EAC1B,IAAgB,EAChB,OAAoB;IAEpB,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
package/src/code/index.ts
CHANGED
|
@@ -17,21 +17,18 @@ import type { ReactElement } from 'react';
|
|
|
17
17
|
import { useTheme } from 'react-x11';
|
|
18
18
|
import type { Style } from 'react-x11/style';
|
|
19
19
|
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
RICHTEXT_ELEMENT,
|
|
23
|
-
useSelectionMenu,
|
|
24
|
-
} from '../richtext/index.js';
|
|
25
|
-
import type { RichTextProps, TextRun } from '../richtext/index.js';
|
|
20
|
+
import { registerRichText, useSelectionMenu } from '../richtext/index.js';
|
|
21
|
+
import type { TextRun } from '../richtext/index.js';
|
|
26
22
|
import {
|
|
27
23
|
codeBlockLook,
|
|
28
|
-
|
|
24
|
+
CodeBlockRunCache,
|
|
29
25
|
codeBlockStyle,
|
|
30
26
|
codeTextStyle,
|
|
31
27
|
} from '../codeblock/index.js';
|
|
32
28
|
import type { Language, TokenStyles } from '../code-language/index.js';
|
|
33
29
|
import { hx } from './hx.js';
|
|
34
30
|
import { useFontPrewarm } from '../internal/prewarm.js';
|
|
31
|
+
import { CodeGutter, CodeLines } from '../internal/codelines.js';
|
|
35
32
|
|
|
36
33
|
const h = React.createElement;
|
|
37
34
|
|
|
@@ -107,39 +104,44 @@ export function Code(props: CodeProps): ReactElement {
|
|
|
107
104
|
);
|
|
108
105
|
useFontPrewarm(look.family);
|
|
109
106
|
|
|
107
|
+
// Kept between renders, so a source that changes at its end is tokenized
|
|
108
|
+
// again from the line that changed (`CodeBlockRunCache`).
|
|
109
|
+
const runCache = React.useRef<CodeBlockRunCache | null>(null);
|
|
110
|
+
runCache.current ??= new CodeBlockRunCache();
|
|
111
|
+
React.useEffect(() => () => runCache.current?.dispose(), []);
|
|
110
112
|
const runs: TextRun[] = React.useMemo(
|
|
111
|
-
() =>
|
|
113
|
+
() =>
|
|
114
|
+
runCache.current!.runs(source, look, { lang, language, resolveLanguage }),
|
|
112
115
|
[source, lang, language, resolveLanguage, look],
|
|
113
116
|
);
|
|
114
117
|
|
|
115
118
|
const lineCount = React.useMemo(() => source.split('\n').length, [source]);
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
// In blocks of lines, so a change lays out the blocks it touches
|
|
121
|
+
// (`../internal/codelines.ts`).
|
|
122
|
+
const code = h(CodeLines, { runs, style: codeTextStyle(look, wrap), wrap });
|
|
119
123
|
|
|
120
124
|
const gutter = lineNumbers
|
|
121
|
-
?
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
})),
|
|
133
|
-
wrap: false,
|
|
134
|
-
style: {
|
|
135
|
-
lineHeight: look.lineHeight,
|
|
136
|
-
textAlign: 'right',
|
|
137
|
-
minWidth:
|
|
138
|
-
Math.max(String(lineCount).length, 2) * Math.ceil(look.size * 0.62),
|
|
139
|
-
marginRight: look.padding,
|
|
140
|
-
flexShrink: 0,
|
|
125
|
+
? hx(
|
|
126
|
+
'box',
|
|
127
|
+
{
|
|
128
|
+
style: {
|
|
129
|
+
flexDirection: 'column',
|
|
130
|
+
minWidth:
|
|
131
|
+
Math.max(String(lineCount).length, 2) *
|
|
132
|
+
Math.ceil(look.size * 0.62),
|
|
133
|
+
marginRight: look.padding,
|
|
134
|
+
flexShrink: 0,
|
|
135
|
+
},
|
|
141
136
|
},
|
|
142
|
-
|
|
137
|
+
// numbers in the same font and line height as the code, blocked the
|
|
138
|
+
// same way, so the two stay in register
|
|
139
|
+
h(CodeGutter, {
|
|
140
|
+
lines: lineCount,
|
|
141
|
+
run: { family: look.family, size: look.size, color: look.dim },
|
|
142
|
+
style: { lineHeight: look.lineHeight, textAlign: 'right' },
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
143
145
|
: null;
|
|
144
146
|
|
|
145
147
|
const rootStyle: Style = {
|
|
@@ -170,7 +172,7 @@ export function Code(props: CodeProps): ReactElement {
|
|
|
170
172
|
hx(
|
|
171
173
|
'box',
|
|
172
174
|
{ style: { overflow: 'scroll', flexDirection: 'column', flexGrow: 1 } },
|
|
173
|
-
|
|
175
|
+
code,
|
|
174
176
|
),
|
|
175
177
|
);
|
|
176
178
|
}
|
|
@@ -34,7 +34,7 @@ export {
|
|
|
34
34
|
} from './theme.js';
|
|
35
35
|
|
|
36
36
|
export { languageForTag, tokenizeText } from './registry.js';
|
|
37
|
-
export { codeRuns } from './runs.js';
|
|
37
|
+
export { codeRuns, CodeRunCache } from './runs.js';
|
|
38
38
|
export type { CodeRun, CodeRunOptions } from './runs.js';
|
|
39
39
|
|
|
40
40
|
export { TOKEN_FALLBACK } from './types.js';
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// AGENTS.md on tree-shaking, and `textmate.ts` for the same shape.
|
|
24
24
|
import { languageForTag, tokenizeText } from './registry.js';
|
|
25
25
|
import { tokenStyleFor } from './theme.js';
|
|
26
|
-
import type { Language, TokenStyles } from './types.js';
|
|
26
|
+
import type { Language, Token, Tokenizer, TokenStyles } from './types.js';
|
|
27
27
|
|
|
28
28
|
/** One styled run of code. Structurally a `<richtext>` run. */
|
|
29
29
|
export interface CodeRun {
|
|
@@ -126,3 +126,113 @@ function push(
|
|
|
126
126
|
out.push(run);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `codeRuns`, kept between calls for a text that changes a little at a
|
|
132
|
+
* time: a fence being streamed, a source being appended to.
|
|
133
|
+
*
|
|
134
|
+
* One tokenizer lives as long as the cache. It is told which lines changed
|
|
135
|
+
* (`Tokenizer.edit`, the interface `<CodeEditor>` already drives) and asked
|
|
136
|
+
* again only for those and the lines after them its state has to walk
|
|
137
|
+
* through, and every other line keeps the runs it had. `codeRuns` tokenized
|
|
138
|
+
* the whole text afresh: at 5,000 lines, most of the time an appended line
|
|
139
|
+
* took on the client.
|
|
140
|
+
*
|
|
141
|
+
* The runs are the same text in the same colours `codeRuns` gives, cut a
|
|
142
|
+
* little differently: a line's runs never reach into the next, and each
|
|
143
|
+
* newline is a run of its own, so that a line keeps its runs while others
|
|
144
|
+
* change. `decorate` is applied to every run once, when it is made — the
|
|
145
|
+
* family and size a block of code is set in, say.
|
|
146
|
+
*
|
|
147
|
+
* Call `dispose()` when done with it: an engine may keep timers.
|
|
148
|
+
*/
|
|
149
|
+
export class CodeRunCache<R = CodeRun> {
|
|
150
|
+
private key: readonly unknown[] | null = null;
|
|
151
|
+
private tokenizer: Tokenizer | null = null;
|
|
152
|
+
private lines: string[] = [];
|
|
153
|
+
private tokens: (readonly Token[] | undefined)[] = [];
|
|
154
|
+
private lineRuns: (R[] | undefined)[] = [];
|
|
155
|
+
private newline: R | null = null;
|
|
156
|
+
|
|
157
|
+
constructor(
|
|
158
|
+
private readonly decorate: (run: CodeRun) => R = (run) => run as R,
|
|
159
|
+
) {}
|
|
160
|
+
|
|
161
|
+
runs(text: string, tag: string, opts: CodeRunOptions): R[] {
|
|
162
|
+
if (text.length === 0)
|
|
163
|
+
return [this.decorate({ text: '', color: opts.color })];
|
|
164
|
+
const language =
|
|
165
|
+
opts.language ??
|
|
166
|
+
(tag ? (opts.resolveLanguage?.(tag) ?? languageForTag(tag)) : null);
|
|
167
|
+
if (!language) return [this.decorate({ text, color: opts.color })];
|
|
168
|
+
|
|
169
|
+
const key = [language, opts.styles, opts.color, opts.resolveToken];
|
|
170
|
+
if (!this.key || key.some((part, i) => part !== this.key![i])) {
|
|
171
|
+
this.dispose();
|
|
172
|
+
this.key = key;
|
|
173
|
+
this.lines = [];
|
|
174
|
+
this.tokens = [];
|
|
175
|
+
this.lineRuns = [];
|
|
176
|
+
this.newline = this.decorate({ text: '\n', color: opts.color });
|
|
177
|
+
this.tokenizer = language.createTokenizer({ invalidate: () => {} });
|
|
178
|
+
this.tokenizer.setLines(this.lines);
|
|
179
|
+
}
|
|
180
|
+
const tokenizer = this.tokenizer!;
|
|
181
|
+
|
|
182
|
+
// the lines the text shares with the last one, from the top: the edit
|
|
183
|
+
// is everything after them
|
|
184
|
+
const next = text.split('\n');
|
|
185
|
+
const lines = this.lines;
|
|
186
|
+
const shared = Math.min(lines.length, next.length);
|
|
187
|
+
let from = 0;
|
|
188
|
+
while (from < shared && lines[from] === next[from]) from++;
|
|
189
|
+
if (from < lines.length || from < next.length) {
|
|
190
|
+
const removed = lines.length - from;
|
|
191
|
+
const inserted = next.length - from;
|
|
192
|
+
lines.length = from;
|
|
193
|
+
for (let i = from; i < next.length; i++) lines.push(next[i]!);
|
|
194
|
+
tokenizer.edit({ fromLine: from, removed, inserted });
|
|
195
|
+
this.tokens.length = from;
|
|
196
|
+
this.lineRuns.length = from;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Asked in order from the top, so a stream engine walks its frontier
|
|
200
|
+
// line by line and answers each exactly, never from a guess.
|
|
201
|
+
const out: R[] = [];
|
|
202
|
+
for (let i = 0; i < lines.length; i++) {
|
|
203
|
+
if (i > 0) out.push(this.newline!);
|
|
204
|
+
const tokens = tokenizer.lineTokens(i);
|
|
205
|
+
let runs = this.lineRuns[i];
|
|
206
|
+
if (!runs || this.tokens[i] !== tokens) {
|
|
207
|
+
runs = lineRuns(lines[i]!, tokens, opts).map(this.decorate);
|
|
208
|
+
this.lineRuns[i] = runs;
|
|
209
|
+
this.tokens[i] = tokens;
|
|
210
|
+
}
|
|
211
|
+
for (const run of runs) out.push(run);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
dispose(): void {
|
|
217
|
+
this.tokenizer?.dispose?.();
|
|
218
|
+
this.tokenizer = null;
|
|
219
|
+
this.key = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** One line's runs, as `codeRuns` makes them, up to its end. */
|
|
224
|
+
function lineRuns(
|
|
225
|
+
line: string,
|
|
226
|
+
tokens: readonly Token[],
|
|
227
|
+
opts: CodeRunOptions,
|
|
228
|
+
): CodeRun[] {
|
|
229
|
+
const out: CodeRun[] = [];
|
|
230
|
+
let at = 0;
|
|
231
|
+
for (const t of tokens) {
|
|
232
|
+
if (t.from > at) push(out, line.slice(at, t.from), undefined, opts);
|
|
233
|
+
push(out, line.slice(t.from, t.to), t.type, opts);
|
|
234
|
+
at = t.to;
|
|
235
|
+
}
|
|
236
|
+
if (at < line.length) push(out, line.slice(at), undefined, opts);
|
|
237
|
+
return out;
|
|
238
|
+
}
|
package/src/codeblock/index.ts
CHANGED
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
// A shared module, not a component: no element registration, no React, and
|
|
16
16
|
// nothing imported from either of its consumers (a component never imports
|
|
17
17
|
// another component).
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
codeRuns,
|
|
20
|
+
autoTokenStyles,
|
|
21
|
+
CodeRunCache,
|
|
22
|
+
} from '../code-language/index.js';
|
|
19
23
|
import type { Language, TokenStyles } from '../code-language/index.js';
|
|
20
24
|
import type { TextRun } from '../richtext/index.js';
|
|
21
25
|
import { tint } from 'react-x11/style';
|
|
@@ -130,6 +134,49 @@ export function codeBlockRuns(
|
|
|
130
134
|
}).map((run) => ({ ...run, family: look.family, size: look.size }));
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
/**
|
|
138
|
+
* `codeBlockRuns` kept between calls, for a block of code that changes a
|
|
139
|
+
* little at a time — a fence being streamed, a source being appended to:
|
|
140
|
+
* the tokenizer is told which lines changed and the rest keep their runs
|
|
141
|
+
* (`CodeRunCache`). Call `dispose()` when done with it.
|
|
142
|
+
*/
|
|
143
|
+
export class CodeBlockRunCache {
|
|
144
|
+
private cache: CodeRunCache<TextRun> | null = null;
|
|
145
|
+
private family: string | undefined;
|
|
146
|
+
private size: number | undefined;
|
|
147
|
+
|
|
148
|
+
runs(
|
|
149
|
+
source: string,
|
|
150
|
+
look: CodeBlockLook,
|
|
151
|
+
options: CodeBlockRunOptions = {},
|
|
152
|
+
): TextRun[] {
|
|
153
|
+
if (!this.cache || this.family !== look.family || this.size !== look.size) {
|
|
154
|
+
this.cache?.dispose();
|
|
155
|
+
const { family, size } = look;
|
|
156
|
+
this.family = family;
|
|
157
|
+
this.size = size;
|
|
158
|
+
this.cache = new CodeRunCache<TextRun>((run) => ({
|
|
159
|
+
...run,
|
|
160
|
+
family,
|
|
161
|
+
size,
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
const { lang = '', language, resolveLanguage, highlight = true } = options;
|
|
165
|
+
return this.cache.runs(source, highlight ? lang : '', {
|
|
166
|
+
styles: look.styles,
|
|
167
|
+
color: look.color,
|
|
168
|
+
...(highlight && language ? { language } : null),
|
|
169
|
+
...(highlight && resolveLanguage ? { resolveLanguage } : null),
|
|
170
|
+
resolveToken: look.resolveToken,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
dispose(): void {
|
|
175
|
+
this.cache?.dispose();
|
|
176
|
+
this.cache = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
133
180
|
/** The block's own box: fill, radius, padding. What surrounds it — a row
|
|
134
181
|
* with a gutter, a column in a document — is the component's. */
|
|
135
182
|
export function codeBlockStyle(look: CodeBlockLook): Style {
|