@vectojs/markdown 0.1.2 → 0.2.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/Markdown.d.ts +40 -1
- package/dist/index.js +146 -9
- package/dist/index.mjs +144 -8
- package/package.json +1 -1
package/dist/Markdown.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Entity, IRenderer, type ContentProjection } from '@vectojs/core';
|
|
1
|
+
import { Entity, type DevtoolsDescriptor, GlyphRasterAtlas, type GlyphRasterAtlasStats, IRenderer, type ContentProjection } from '@vectojs/core';
|
|
2
2
|
import { type Token } from 'marked';
|
|
3
3
|
import { Stack, UIComponent } from '@vectojs/ui';
|
|
4
4
|
/** Color and typography theme for Markdown rendering. */
|
|
@@ -71,6 +71,25 @@ export declare class CodeBlock extends UIComponent {
|
|
|
71
71
|
isPointInside(): boolean;
|
|
72
72
|
render(r: IRenderer): void;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Instrumentation for the shared code-block glyph atlas, or `null` before first
|
|
76
|
+
* use.
|
|
77
|
+
*
|
|
78
|
+
* Exposed so an app or benchmark can confirm the atlas is actually active and
|
|
79
|
+
* reusing slots. Watch `resets`: a steadily climbing count means the glyph set is
|
|
80
|
+
* unbounded for the atlas size, so every reset re-rasterizes everything and the
|
|
81
|
+
* atlas is doing net harm rather than saving work.
|
|
82
|
+
*/
|
|
83
|
+
export declare function codeAtlasStats(): GlyphRasterAtlasStats | null;
|
|
84
|
+
/**
|
|
85
|
+
* The shared code-block atlas itself, or `null` before first use.
|
|
86
|
+
*
|
|
87
|
+
* For instrumentation that must map a traced `drawImage` back to the glyph it
|
|
88
|
+
* painted — a blit carries only a source rect, so `slotAt()` is the only way to
|
|
89
|
+
* recover the cluster and its metrics. Used by `e2e/text-projection.e2e.ts` to
|
|
90
|
+
* keep the code-grid positioning assertions working on the blit path.
|
|
91
|
+
*/
|
|
92
|
+
export declare function codeAtlas(): GlyphRasterAtlas | null;
|
|
74
93
|
export interface MarkdownOptions {
|
|
75
94
|
maxWidth?: number;
|
|
76
95
|
theme?: MarkdownTheme;
|
|
@@ -105,6 +124,16 @@ export declare class Markdown extends UIComponent {
|
|
|
105
124
|
private tokens;
|
|
106
125
|
private appendInFlight;
|
|
107
126
|
private appendPending;
|
|
127
|
+
/**
|
|
128
|
+
* Streaming counters for the DevTools inspector.
|
|
129
|
+
*
|
|
130
|
+
* Cheap enough to keep always-on (four integer increments per append) and the
|
|
131
|
+
* only way to see, from outside, whether incremental reuse is actually working:
|
|
132
|
+
* a stable-prefix ratio near 1 means the worker is matching almost everything
|
|
133
|
+
* and only the tail is re-lexed, while a ratio near 0 means every chunk is
|
|
134
|
+
* re-parsing the whole document.
|
|
135
|
+
*/
|
|
136
|
+
private streamStats;
|
|
108
137
|
private pendingWorkerIds;
|
|
109
138
|
private readonly workerInstanceId;
|
|
110
139
|
private tokenVersion;
|
|
@@ -130,6 +159,16 @@ export declare class Markdown extends UIComponent {
|
|
|
130
159
|
* content subtree via `super.destroy()` so every block's resources are freed.
|
|
131
160
|
*/
|
|
132
161
|
destroy(): void;
|
|
162
|
+
/**
|
|
163
|
+
* Streaming and parse state — the markdown streaming inspector.
|
|
164
|
+
*
|
|
165
|
+
* Source length, chunk count, worker in-flight state, and the stable-prefix
|
|
166
|
+
* versus re-lexed-tail split. That last ratio is the one worth watching: it is
|
|
167
|
+
* how you tell incremental reuse is working from outside, and nothing else
|
|
168
|
+
* surfaces it. A ratio near 1 means the worker matched almost the whole prefix
|
|
169
|
+
* and only re-lexed the tail; near 0 means every chunk re-parses the document.
|
|
170
|
+
*/
|
|
171
|
+
getDevtoolsDescriptor(): DevtoolsDescriptor;
|
|
133
172
|
/** Enable or disable native selection for existing and future Markdown text. */
|
|
134
173
|
setSelectable(selectable: boolean): this;
|
|
135
174
|
/** Append a markdown chunk incrementally. Reuses unchanged prefix entities. */
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CodeBlock: () => CodeBlock,
|
|
24
|
-
Markdown: () => Markdown
|
|
24
|
+
Markdown: () => Markdown,
|
|
25
|
+
codeAtlas: () => codeAtlas,
|
|
26
|
+
codeAtlasStats: () => codeAtlasStats
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
27
29
|
|
|
@@ -553,6 +555,9 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
553
555
|
r.roundRect(0, 0, this.width, this.height, 8);
|
|
554
556
|
r.fill(this.theme.codeBgColor);
|
|
555
557
|
const grid = this.ensureGrid();
|
|
558
|
+
const atlas = codeGlyphAtlas(r);
|
|
559
|
+
const atlasSource = atlas?.source ?? null;
|
|
560
|
+
const blit = atlas ? r.drawImageRect : void 0;
|
|
556
561
|
for (let row = 0; row < grid.lines.length; row++) {
|
|
557
562
|
const yBaseline = this.pad + row * this.lineH + this.lineH * 0.75;
|
|
558
563
|
const segments = this.lines[row];
|
|
@@ -567,17 +572,51 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
567
572
|
}
|
|
568
573
|
const sourceText = this.source.slice(cell.sourceStart, cell.sourceEnd);
|
|
569
574
|
if (cell.advance <= 0 || sourceText === " " || sourceText === " ") continue;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
575
|
+
const color = segments[segmentIndex]?.color ?? this.theme.codeColor;
|
|
576
|
+
const x = this.pad + cell.x;
|
|
577
|
+
if (blit && atlas) {
|
|
578
|
+
const slot = atlas.get(this.codeFont, color, cell.glyph);
|
|
579
|
+
const src = atlasSource ?? atlas.source;
|
|
580
|
+
if (slot && src) {
|
|
581
|
+
blit.call(
|
|
582
|
+
r,
|
|
583
|
+
src,
|
|
584
|
+
slot.sx,
|
|
585
|
+
slot.sy,
|
|
586
|
+
slot.sw,
|
|
587
|
+
slot.sh,
|
|
588
|
+
x - slot.offsetX,
|
|
589
|
+
yBaseline - slot.offsetY,
|
|
590
|
+
slot.w,
|
|
591
|
+
slot.h
|
|
592
|
+
);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
r.fillText(cell.glyph, x, yBaseline, this.codeFont, color);
|
|
577
597
|
}
|
|
578
598
|
}
|
|
579
599
|
}
|
|
580
600
|
};
|
|
601
|
+
var sharedCodeAtlas = null;
|
|
602
|
+
function codeGlyphAtlas(r) {
|
|
603
|
+
if (typeof r.drawImageRect !== "function") return void 0;
|
|
604
|
+
if (typeof document === "undefined") return void 0;
|
|
605
|
+
sharedCodeAtlas ??= new import_core.GlyphRasterAtlas({
|
|
606
|
+
// Match the display so a HiDPI blit stays crisp. Capped at 3 because atlas
|
|
607
|
+
// area grows with dpr² and a 4x display would otherwise blow the size cap
|
|
608
|
+
// with a few hundred glyphs.
|
|
609
|
+
dpr: typeof window !== "undefined" ? Math.min(window.devicePixelRatio || 1, 3) : 1,
|
|
610
|
+
maxSize: 2048
|
|
611
|
+
});
|
|
612
|
+
return sharedCodeAtlas;
|
|
613
|
+
}
|
|
614
|
+
function codeAtlasStats() {
|
|
615
|
+
return sharedCodeAtlas ? sharedCodeAtlas.stats : null;
|
|
616
|
+
}
|
|
617
|
+
function codeAtlas() {
|
|
618
|
+
return sharedCodeAtlas;
|
|
619
|
+
}
|
|
581
620
|
function decodeEntities(text) {
|
|
582
621
|
return text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
583
622
|
}
|
|
@@ -717,6 +756,23 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
717
756
|
// with the latest accumulated text once it resolves.
|
|
718
757
|
appendInFlight = false;
|
|
719
758
|
appendPending = false;
|
|
759
|
+
/**
|
|
760
|
+
* Streaming counters for the DevTools inspector.
|
|
761
|
+
*
|
|
762
|
+
* Cheap enough to keep always-on (four integer increments per append) and the
|
|
763
|
+
* only way to see, from outside, whether incremental reuse is actually working:
|
|
764
|
+
* a stable-prefix ratio near 1 means the worker is matching almost everything
|
|
765
|
+
* and only the tail is re-lexed, while a ratio near 0 means every chunk is
|
|
766
|
+
* re-parsing the whole document.
|
|
767
|
+
*/
|
|
768
|
+
streamStats = {
|
|
769
|
+
appends: 0,
|
|
770
|
+
workerResponses: 0,
|
|
771
|
+
/** Sum of `matchLen` across responses, i.e. tokens reused rather than re-lexed. */
|
|
772
|
+
tokensReused: 0,
|
|
773
|
+
/** Sum of returned tail lengths, i.e. tokens the worker had to re-lex. */
|
|
774
|
+
tokensRelexed: 0
|
|
775
|
+
};
|
|
720
776
|
// Worker request ids dispatched by *this* instance that haven't resolved yet.
|
|
721
777
|
// The module-level `workerCallbacks` map holds a closure capturing `this`, so
|
|
722
778
|
// destroying a Markdown mid-stream would pin the whole entity (and its subtree)
|
|
@@ -809,6 +865,81 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
809
865
|
});
|
|
810
866
|
super.destroy();
|
|
811
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Streaming and parse state — the markdown streaming inspector.
|
|
870
|
+
*
|
|
871
|
+
* Source length, chunk count, worker in-flight state, and the stable-prefix
|
|
872
|
+
* versus re-lexed-tail split. That last ratio is the one worth watching: it is
|
|
873
|
+
* how you tell incremental reuse is working from outside, and nothing else
|
|
874
|
+
* surfaces it. A ratio near 1 means the worker matched almost the whole prefix
|
|
875
|
+
* and only re-lexed the tail; near 0 means every chunk re-parses the document.
|
|
876
|
+
*/
|
|
877
|
+
getDevtoolsDescriptor() {
|
|
878
|
+
const s = this.streamStats;
|
|
879
|
+
const lexed = s.tokensReused + s.tokensRelexed;
|
|
880
|
+
const reuseRatio = lexed > 0 ? s.tokensReused / lexed : 0;
|
|
881
|
+
return {
|
|
882
|
+
kind: "Markdown",
|
|
883
|
+
groups: [
|
|
884
|
+
{
|
|
885
|
+
label: "Source",
|
|
886
|
+
fields: [
|
|
887
|
+
{ label: "sourceLength", value: this.rawMarkdown.length, readOnly: true },
|
|
888
|
+
{ label: "topLevelTokens", value: this.tokens.length, readOnly: true },
|
|
889
|
+
{ label: "childEntities", value: this.content.children.length, readOnly: true },
|
|
890
|
+
{ label: "selectable", value: this.selectable }
|
|
891
|
+
]
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
label: "Streaming",
|
|
895
|
+
fields: [
|
|
896
|
+
{ label: "appends", value: s.appends, readOnly: true },
|
|
897
|
+
{
|
|
898
|
+
label: "workerResponses",
|
|
899
|
+
value: s.workerResponses,
|
|
900
|
+
hint: "Fewer than appends means chunks were coalesced while a request was in flight",
|
|
901
|
+
readOnly: true
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
label: "appendInFlight",
|
|
905
|
+
value: this.appendInFlight,
|
|
906
|
+
hint: "One lex request at a time; the delta protocol requires it",
|
|
907
|
+
readOnly: true
|
|
908
|
+
},
|
|
909
|
+
{ label: "appendPending", value: this.appendPending, readOnly: true }
|
|
910
|
+
]
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
label: "Incremental reuse",
|
|
914
|
+
fields: [
|
|
915
|
+
{
|
|
916
|
+
label: "tokensReused",
|
|
917
|
+
value: s.tokensReused,
|
|
918
|
+
hint: "Sum of matchLen: prefix tokens the worker matched and did not re-lex",
|
|
919
|
+
readOnly: true
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
label: "tokensRelexed",
|
|
923
|
+
value: s.tokensRelexed,
|
|
924
|
+
hint: "Sum of returned tail lengths: tokens the worker had to re-lex",
|
|
925
|
+
readOnly: true
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
label: "reuseRatio",
|
|
929
|
+
value: Math.round(reuseRatio * 1e3) / 1e3,
|
|
930
|
+
hint: "reused / (reused + relexed). Near 1 is healthy; near 0 means no reuse",
|
|
931
|
+
readOnly: true
|
|
932
|
+
}
|
|
933
|
+
]
|
|
934
|
+
}
|
|
935
|
+
],
|
|
936
|
+
notes: s.workerResponses === 0 && s.appends > 0 ? [
|
|
937
|
+
"No worker responses yet: either the worker is unavailable and parsing ran synchronously on the main thread, or the first request is still in flight."
|
|
938
|
+
] : reuseRatio > 0 && reuseRatio < 0.5 ? [
|
|
939
|
+
`Only ${Math.round(reuseRatio * 100)}% of lexed tokens were reused, so most of the document is being re-lexed per chunk. Expect O(document) work per append.`
|
|
940
|
+
] : void 0
|
|
941
|
+
};
|
|
942
|
+
}
|
|
812
943
|
/** Enable or disable native selection for existing and future Markdown text. */
|
|
813
944
|
setSelectable(selectable) {
|
|
814
945
|
this.selectable = selectable;
|
|
@@ -824,6 +955,7 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
824
955
|
/** Append a markdown chunk incrementally. Reuses unchanged prefix entities. */
|
|
825
956
|
appendMarkdown(chunk) {
|
|
826
957
|
this.rawMarkdown += chunk;
|
|
958
|
+
this.streamStats.appends++;
|
|
827
959
|
if (!markdownWorker) {
|
|
828
960
|
const newTokens = import_marked.marked.lexer(this.rawMarkdown);
|
|
829
961
|
this.updateTokens(newTokens);
|
|
@@ -858,6 +990,9 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
858
990
|
workerCallbacks.set(id, {
|
|
859
991
|
cb: (matchLen, tail) => {
|
|
860
992
|
this.pendingWorkerIds.delete(id);
|
|
993
|
+
this.streamStats.workerResponses++;
|
|
994
|
+
this.streamStats.tokensReused += matchLen;
|
|
995
|
+
this.streamStats.tokensRelexed += tail.length;
|
|
861
996
|
this.appendInFlight = false;
|
|
862
997
|
const newTokens = [...oldTokensSnapshot.slice(0, matchLen), ...tail];
|
|
863
998
|
this.updateTokens(newTokens, matchLen);
|
|
@@ -1225,5 +1360,7 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
1225
1360
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1226
1361
|
0 && (module.exports = {
|
|
1227
1362
|
CodeBlock,
|
|
1228
|
-
Markdown
|
|
1363
|
+
Markdown,
|
|
1364
|
+
codeAtlas,
|
|
1365
|
+
codeAtlasStats
|
|
1229
1366
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BidiResolver,
|
|
4
4
|
Entity,
|
|
5
|
+
GlyphRasterAtlas,
|
|
5
6
|
prepareContentGrid,
|
|
6
7
|
SVGEntity
|
|
7
8
|
} from "@vectojs/core";
|
|
@@ -531,6 +532,9 @@ var CodeBlock = class extends UIComponent {
|
|
|
531
532
|
r.roundRect(0, 0, this.width, this.height, 8);
|
|
532
533
|
r.fill(this.theme.codeBgColor);
|
|
533
534
|
const grid = this.ensureGrid();
|
|
535
|
+
const atlas = codeGlyphAtlas(r);
|
|
536
|
+
const atlasSource = atlas?.source ?? null;
|
|
537
|
+
const blit = atlas ? r.drawImageRect : void 0;
|
|
534
538
|
for (let row = 0; row < grid.lines.length; row++) {
|
|
535
539
|
const yBaseline = this.pad + row * this.lineH + this.lineH * 0.75;
|
|
536
540
|
const segments = this.lines[row];
|
|
@@ -545,17 +549,51 @@ var CodeBlock = class extends UIComponent {
|
|
|
545
549
|
}
|
|
546
550
|
const sourceText = this.source.slice(cell.sourceStart, cell.sourceEnd);
|
|
547
551
|
if (cell.advance <= 0 || sourceText === " " || sourceText === " ") continue;
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
552
|
+
const color = segments[segmentIndex]?.color ?? this.theme.codeColor;
|
|
553
|
+
const x = this.pad + cell.x;
|
|
554
|
+
if (blit && atlas) {
|
|
555
|
+
const slot = atlas.get(this.codeFont, color, cell.glyph);
|
|
556
|
+
const src = atlasSource ?? atlas.source;
|
|
557
|
+
if (slot && src) {
|
|
558
|
+
blit.call(
|
|
559
|
+
r,
|
|
560
|
+
src,
|
|
561
|
+
slot.sx,
|
|
562
|
+
slot.sy,
|
|
563
|
+
slot.sw,
|
|
564
|
+
slot.sh,
|
|
565
|
+
x - slot.offsetX,
|
|
566
|
+
yBaseline - slot.offsetY,
|
|
567
|
+
slot.w,
|
|
568
|
+
slot.h
|
|
569
|
+
);
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
r.fillText(cell.glyph, x, yBaseline, this.codeFont, color);
|
|
555
574
|
}
|
|
556
575
|
}
|
|
557
576
|
}
|
|
558
577
|
};
|
|
578
|
+
var sharedCodeAtlas = null;
|
|
579
|
+
function codeGlyphAtlas(r) {
|
|
580
|
+
if (typeof r.drawImageRect !== "function") return void 0;
|
|
581
|
+
if (typeof document === "undefined") return void 0;
|
|
582
|
+
sharedCodeAtlas ??= new GlyphRasterAtlas({
|
|
583
|
+
// Match the display so a HiDPI blit stays crisp. Capped at 3 because atlas
|
|
584
|
+
// area grows with dpr² and a 4x display would otherwise blow the size cap
|
|
585
|
+
// with a few hundred glyphs.
|
|
586
|
+
dpr: typeof window !== "undefined" ? Math.min(window.devicePixelRatio || 1, 3) : 1,
|
|
587
|
+
maxSize: 2048
|
|
588
|
+
});
|
|
589
|
+
return sharedCodeAtlas;
|
|
590
|
+
}
|
|
591
|
+
function codeAtlasStats() {
|
|
592
|
+
return sharedCodeAtlas ? sharedCodeAtlas.stats : null;
|
|
593
|
+
}
|
|
594
|
+
function codeAtlas() {
|
|
595
|
+
return sharedCodeAtlas;
|
|
596
|
+
}
|
|
559
597
|
function decodeEntities(text) {
|
|
560
598
|
return text.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
561
599
|
}
|
|
@@ -695,6 +733,23 @@ var Markdown = class extends UIComponent {
|
|
|
695
733
|
// with the latest accumulated text once it resolves.
|
|
696
734
|
appendInFlight = false;
|
|
697
735
|
appendPending = false;
|
|
736
|
+
/**
|
|
737
|
+
* Streaming counters for the DevTools inspector.
|
|
738
|
+
*
|
|
739
|
+
* Cheap enough to keep always-on (four integer increments per append) and the
|
|
740
|
+
* only way to see, from outside, whether incremental reuse is actually working:
|
|
741
|
+
* a stable-prefix ratio near 1 means the worker is matching almost everything
|
|
742
|
+
* and only the tail is re-lexed, while a ratio near 0 means every chunk is
|
|
743
|
+
* re-parsing the whole document.
|
|
744
|
+
*/
|
|
745
|
+
streamStats = {
|
|
746
|
+
appends: 0,
|
|
747
|
+
workerResponses: 0,
|
|
748
|
+
/** Sum of `matchLen` across responses, i.e. tokens reused rather than re-lexed. */
|
|
749
|
+
tokensReused: 0,
|
|
750
|
+
/** Sum of returned tail lengths, i.e. tokens the worker had to re-lex. */
|
|
751
|
+
tokensRelexed: 0
|
|
752
|
+
};
|
|
698
753
|
// Worker request ids dispatched by *this* instance that haven't resolved yet.
|
|
699
754
|
// The module-level `workerCallbacks` map holds a closure capturing `this`, so
|
|
700
755
|
// destroying a Markdown mid-stream would pin the whole entity (and its subtree)
|
|
@@ -787,6 +842,81 @@ var Markdown = class extends UIComponent {
|
|
|
787
842
|
});
|
|
788
843
|
super.destroy();
|
|
789
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* Streaming and parse state — the markdown streaming inspector.
|
|
847
|
+
*
|
|
848
|
+
* Source length, chunk count, worker in-flight state, and the stable-prefix
|
|
849
|
+
* versus re-lexed-tail split. That last ratio is the one worth watching: it is
|
|
850
|
+
* how you tell incremental reuse is working from outside, and nothing else
|
|
851
|
+
* surfaces it. A ratio near 1 means the worker matched almost the whole prefix
|
|
852
|
+
* and only re-lexed the tail; near 0 means every chunk re-parses the document.
|
|
853
|
+
*/
|
|
854
|
+
getDevtoolsDescriptor() {
|
|
855
|
+
const s = this.streamStats;
|
|
856
|
+
const lexed = s.tokensReused + s.tokensRelexed;
|
|
857
|
+
const reuseRatio = lexed > 0 ? s.tokensReused / lexed : 0;
|
|
858
|
+
return {
|
|
859
|
+
kind: "Markdown",
|
|
860
|
+
groups: [
|
|
861
|
+
{
|
|
862
|
+
label: "Source",
|
|
863
|
+
fields: [
|
|
864
|
+
{ label: "sourceLength", value: this.rawMarkdown.length, readOnly: true },
|
|
865
|
+
{ label: "topLevelTokens", value: this.tokens.length, readOnly: true },
|
|
866
|
+
{ label: "childEntities", value: this.content.children.length, readOnly: true },
|
|
867
|
+
{ label: "selectable", value: this.selectable }
|
|
868
|
+
]
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
label: "Streaming",
|
|
872
|
+
fields: [
|
|
873
|
+
{ label: "appends", value: s.appends, readOnly: true },
|
|
874
|
+
{
|
|
875
|
+
label: "workerResponses",
|
|
876
|
+
value: s.workerResponses,
|
|
877
|
+
hint: "Fewer than appends means chunks were coalesced while a request was in flight",
|
|
878
|
+
readOnly: true
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
label: "appendInFlight",
|
|
882
|
+
value: this.appendInFlight,
|
|
883
|
+
hint: "One lex request at a time; the delta protocol requires it",
|
|
884
|
+
readOnly: true
|
|
885
|
+
},
|
|
886
|
+
{ label: "appendPending", value: this.appendPending, readOnly: true }
|
|
887
|
+
]
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
label: "Incremental reuse",
|
|
891
|
+
fields: [
|
|
892
|
+
{
|
|
893
|
+
label: "tokensReused",
|
|
894
|
+
value: s.tokensReused,
|
|
895
|
+
hint: "Sum of matchLen: prefix tokens the worker matched and did not re-lex",
|
|
896
|
+
readOnly: true
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
label: "tokensRelexed",
|
|
900
|
+
value: s.tokensRelexed,
|
|
901
|
+
hint: "Sum of returned tail lengths: tokens the worker had to re-lex",
|
|
902
|
+
readOnly: true
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
label: "reuseRatio",
|
|
906
|
+
value: Math.round(reuseRatio * 1e3) / 1e3,
|
|
907
|
+
hint: "reused / (reused + relexed). Near 1 is healthy; near 0 means no reuse",
|
|
908
|
+
readOnly: true
|
|
909
|
+
}
|
|
910
|
+
]
|
|
911
|
+
}
|
|
912
|
+
],
|
|
913
|
+
notes: s.workerResponses === 0 && s.appends > 0 ? [
|
|
914
|
+
"No worker responses yet: either the worker is unavailable and parsing ran synchronously on the main thread, or the first request is still in flight."
|
|
915
|
+
] : reuseRatio > 0 && reuseRatio < 0.5 ? [
|
|
916
|
+
`Only ${Math.round(reuseRatio * 100)}% of lexed tokens were reused, so most of the document is being re-lexed per chunk. Expect O(document) work per append.`
|
|
917
|
+
] : void 0
|
|
918
|
+
};
|
|
919
|
+
}
|
|
790
920
|
/** Enable or disable native selection for existing and future Markdown text. */
|
|
791
921
|
setSelectable(selectable) {
|
|
792
922
|
this.selectable = selectable;
|
|
@@ -802,6 +932,7 @@ var Markdown = class extends UIComponent {
|
|
|
802
932
|
/** Append a markdown chunk incrementally. Reuses unchanged prefix entities. */
|
|
803
933
|
appendMarkdown(chunk) {
|
|
804
934
|
this.rawMarkdown += chunk;
|
|
935
|
+
this.streamStats.appends++;
|
|
805
936
|
if (!markdownWorker) {
|
|
806
937
|
const newTokens = marked.lexer(this.rawMarkdown);
|
|
807
938
|
this.updateTokens(newTokens);
|
|
@@ -836,6 +967,9 @@ var Markdown = class extends UIComponent {
|
|
|
836
967
|
workerCallbacks.set(id, {
|
|
837
968
|
cb: (matchLen, tail) => {
|
|
838
969
|
this.pendingWorkerIds.delete(id);
|
|
970
|
+
this.streamStats.workerResponses++;
|
|
971
|
+
this.streamStats.tokensReused += matchLen;
|
|
972
|
+
this.streamStats.tokensRelexed += tail.length;
|
|
839
973
|
this.appendInFlight = false;
|
|
840
974
|
const newTokens = [...oldTokensSnapshot.slice(0, matchLen), ...tail];
|
|
841
975
|
this.updateTokens(newTokens, matchLen);
|
|
@@ -1202,5 +1336,7 @@ var Markdown = class extends UIComponent {
|
|
|
1202
1336
|
};
|
|
1203
1337
|
export {
|
|
1204
1338
|
CodeBlock,
|
|
1205
|
-
Markdown
|
|
1339
|
+
Markdown,
|
|
1340
|
+
codeAtlas,
|
|
1341
|
+
codeAtlasStats
|
|
1206
1342
|
};
|