@hydranium/core 1.0.0-next.385 → 1.0.0-next.387
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/lib/documents/model-ledger.d.ts +1 -1
- package/lib/documents/model-ledger.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.d.ts +22 -1
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +45 -3
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/module.js +3 -3
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/residency/cst-residency-service.d.ts +13 -10
- package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
- package/lib/langium/residency/cst-residency-service.js +19 -19
- package/lib/langium/residency/cst-residency-service.js.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +44 -4
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.js +115 -9
- package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
- package/lib/node/event-loop-monitor.d.ts.map +1 -1
- package/lib/node/event-loop-monitor.js +1 -1
- package/lib/node/event-loop-monitor.js.map +1 -1
- package/lib/node/server-state-snapshot.d.ts +5 -3
- package/lib/node/server-state-snapshot.d.ts.map +1 -1
- package/lib/node/server-state-snapshot.js +16 -13
- package/lib/node/server-state-snapshot.js.map +1 -1
- package/package.json +5 -5
- package/src/documents/model-ledger.ts +1 -1
- package/src/langium/document-builder/document-builder.ts +50 -3
- package/src/langium/module.ts +3 -3
- package/src/langium/residency/cst-residency-service.ts +20 -21
- package/src/langium/workspace/hydranium-langium-document-factory.ts +136 -8
- package/src/node/event-loop-monitor.ts +1 -3
- package/src/node/server-state-snapshot.ts +22 -13
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
URI
|
|
24
24
|
} from '@hydranium/langium';
|
|
25
25
|
import { type HydraniumTextDocuments } from '../../documents/hydranium-text-documents.js';
|
|
26
|
+
import { type ModelLedger } from '../../documents/model-ledger.js';
|
|
26
27
|
import { type LogNameOptions } from '../diagnostics/logger.js';
|
|
27
28
|
import { type ServerSharedServicesMinimal } from '../shared-services.js';
|
|
28
29
|
|
|
@@ -90,19 +91,17 @@ export interface CstResidencyOptions extends LogNameOptions {
|
|
|
90
91
|
export const CST_REHYDRATION_RESET_STATE: DocumentState = DocumentState.Changed;
|
|
91
92
|
|
|
92
93
|
/**
|
|
93
|
-
* Whether `document`
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* seam
|
|
94
|
+
* Whether `document` has a resident AST without a CST, and `ledger` holds the
|
|
95
|
+
* text to graft one from. That text is the one a residency policy recorded when
|
|
96
|
+
* it shed the CST, or the serialized text of a `fromModel` document. A
|
|
97
|
+
* not-yet-parsed document has no CST to graft, and nor has a `fromModel`
|
|
98
|
+
* document without a serializer. The single definition of "shed", shared by the
|
|
99
|
+
* document builder's rehydrate-on-re-entry seam and the server-state snapshot
|
|
100
|
+
* so the two cannot drift.
|
|
99
101
|
*/
|
|
100
|
-
export function isCstShed(document: LangiumDocument): boolean {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
document.parseResult?.value !== undefined &&
|
|
104
|
-
document.parseResult.value.$cstNode === undefined
|
|
105
|
-
);
|
|
102
|
+
export function isCstShed(document: LangiumDocument, ledger: ModelLedger): boolean {
|
|
103
|
+
const root = document.parseResult?.value;
|
|
104
|
+
return document.state >= DocumentState.Parsed && root !== undefined && root.$cstNode === undefined && ledger.textOf(root) !== undefined;
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
/**
|
|
@@ -485,8 +484,9 @@ export class DefaultCstResidencyService implements CstResidencyService {
|
|
|
485
484
|
* references. Only a `Validated` document qualifies: the idle timer can fire
|
|
486
485
|
* while a build waits between documents, and a build that indexes a
|
|
487
486
|
* document's references without their CST drops them from the reference
|
|
488
|
-
* index. Its root needs a recorded version,
|
|
489
|
-
* text
|
|
487
|
+
* index. Its root needs a recorded version, since without one the ledger
|
|
488
|
+
* keeps no text to graft the CST back from, and `shed` refuses it for a
|
|
489
|
+
* caller that skips this check. A document whose graft diverged on its
|
|
490
490
|
* current text would cost a parse on every re-entry. Checked before
|
|
491
491
|
* {@link isSheddable}, so an override of that policy keeps these guards.
|
|
492
492
|
*/
|
|
@@ -515,22 +515,21 @@ export class DefaultCstResidencyService implements CstResidencyService {
|
|
|
515
515
|
/**
|
|
516
516
|
* Shed one document's CST: null every AST node's `$cstNode` and every
|
|
517
517
|
* reference's `$refNode`. Returns the number of `$cstNode`s cleared (0 if the
|
|
518
|
-
* document is unparsed or
|
|
518
|
+
* document is unparsed, already shed, or has no recorded version).
|
|
519
519
|
*/
|
|
520
520
|
protected shed(document: LangiumDocument): number {
|
|
521
521
|
const root = document.parseResult?.value;
|
|
522
|
-
|
|
522
|
+
const ledger = this.services.workspace.ModelLedger;
|
|
523
|
+
// Without a version the ledger keeps no text, and without the text no
|
|
524
|
+
// build can graft the CST back or tell the document is shed.
|
|
525
|
+
if (root === undefined || root.$cstNode === undefined || ledger.versionOf(root) === UNRECORDED_VERSION) {
|
|
523
526
|
return 0;
|
|
524
527
|
}
|
|
525
528
|
// Readers of the parsed text, the `'text-diagnostics'` fingerprint among
|
|
526
529
|
// them, otherwise see none once the CST is gone. Recorded in the ledger
|
|
527
530
|
// directly: no root is produced, and reconciling it with the store would
|
|
528
531
|
// step a closed document's sequence for a root behind its text.
|
|
529
|
-
|
|
530
|
-
const version = ledger.versionOf(root);
|
|
531
|
-
if (version !== UNRECORDED_VERSION) {
|
|
532
|
-
ledger.record(root, version, ledger.textOf(root));
|
|
533
|
-
}
|
|
532
|
+
ledger.record(root, ledger.versionOf(root), ledger.textOf(root));
|
|
534
533
|
let cleared = 0;
|
|
535
534
|
for (const node of AstUtils.streamAst(root)) {
|
|
536
535
|
if (node.$cstNode) {
|
|
@@ -9,11 +9,19 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
type AstNode,
|
|
12
|
+
type AstReflection,
|
|
12
13
|
AstUtils,
|
|
13
14
|
DefaultLangiumDocumentFactory,
|
|
15
|
+
DocumentState,
|
|
16
|
+
isAstNode,
|
|
17
|
+
isMultiReference,
|
|
14
18
|
type LangiumDocument,
|
|
19
|
+
type Linker,
|
|
20
|
+
type MultiReference,
|
|
15
21
|
type Mutable,
|
|
16
22
|
type ParseResult,
|
|
23
|
+
type Reference,
|
|
24
|
+
type ReferenceInfo,
|
|
17
25
|
type URI
|
|
18
26
|
} from '@hydranium/langium';
|
|
19
27
|
import { type CancellationToken } from 'vscode-languageserver-protocol';
|
|
@@ -44,10 +52,18 @@ interface WithSerializer {
|
|
|
44
52
|
* scope descriptions pointing at them, stay valid. A `fromString` document
|
|
45
53
|
* already carries its source, so only `fromModel` needs help.
|
|
46
54
|
*
|
|
55
|
+
* The first build grafts the CST of that text onto the model, as for a shed
|
|
56
|
+
* document, which gives every reference the `$refNode` the reference index
|
|
57
|
+
* needs. A model that does not pair node for node with its serialized text is
|
|
58
|
+
* re-parsed instead, and its nodes are replaced: from its file, or from the
|
|
59
|
+
* text it holds when no file is at its URI, since reading the URI would throw.
|
|
60
|
+
* While the document is open, the store's text wins.
|
|
61
|
+
*
|
|
47
62
|
* Degrades to Langium's text-less `fromModel` when no serializer is bound for
|
|
48
|
-
* the URI's language (the serializer throws or is absent)
|
|
49
|
-
*
|
|
50
|
-
*
|
|
63
|
+
* the URI's language (the serializer throws or is absent). Its first build
|
|
64
|
+
* re-reads it from the store or its file, replacing its nodes, as Langium
|
|
65
|
+
* does. With neither, it keeps its nodes but gets no CST, so its references
|
|
66
|
+
* stay out of the reference index and it is not relinked.
|
|
51
67
|
*
|
|
52
68
|
* Also links the model's container properties (`$container` /
|
|
53
69
|
* `$containerProperty` / `$containerIndex`), which Langium's parser sets via
|
|
@@ -71,9 +87,13 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
71
87
|
protected readonly modelLedger: () => ModelLedger;
|
|
72
88
|
/** A thunk: the residency service resolves `LangiumDocuments`, which is built with this factory. */
|
|
73
89
|
protected readonly cstResidency: () => CstResidencyService;
|
|
90
|
+
/** The code-built documents, which are re-read from the text they hold when no file is at their URI. */
|
|
91
|
+
protected readonly codeBuiltDocuments = new WeakSet<LangiumDocument>();
|
|
92
|
+
protected readonly astReflection: AstReflection;
|
|
74
93
|
|
|
75
94
|
constructor(services: ServerSharedServicesMinimal) {
|
|
76
95
|
super(services);
|
|
96
|
+
this.astReflection = services.AstReflection;
|
|
77
97
|
this.versionSyncService = () => services.workspace.VersionSyncService;
|
|
78
98
|
this.modelLedger = () => services.workspace.ModelLedger;
|
|
79
99
|
this.cstResidency = () => services.workspace.CstResidencyService;
|
|
@@ -109,12 +129,67 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
109
129
|
// Make the code-built AST as structurally complete as a parsed one — the
|
|
110
130
|
// parser links containers, `fromModel` does not. In place (identity kept).
|
|
111
131
|
AstUtils.linkContentToContainer(model, { deep: true });
|
|
132
|
+
// The parser fills in the defaults a model built in code may leave out.
|
|
133
|
+
// Filled before serializing, since a serializer may read them, and
|
|
134
|
+
// without them the model does not pair with the parse of its text.
|
|
135
|
+
for (const node of AstUtils.streamAst(model)) {
|
|
136
|
+
AstUtils.assignMandatoryProperties(this.astReflection, node);
|
|
137
|
+
}
|
|
112
138
|
const text = this.serializeModel(model, uri);
|
|
113
139
|
if (text === undefined) {
|
|
114
140
|
return super.fromModel(model, uri);
|
|
115
141
|
}
|
|
116
142
|
const parseResult: ParseResult<T> = { value: model, parserErrors: [], lexerErrors: [] };
|
|
117
|
-
|
|
143
|
+
const document = this.createLangiumDocument<T>(parseResult, uri, undefined, text);
|
|
144
|
+
this.codeBuiltDocuments.add(document);
|
|
145
|
+
const linker = this.serviceRegistry.getServices(uri).references.Linker;
|
|
146
|
+
document.references = AstUtils.streamAst(model)
|
|
147
|
+
.flatMap(node => AstUtils.streamReferences(node))
|
|
148
|
+
.toArray()
|
|
149
|
+
.flatMap(info => this.rebuildReference(linker, info) ?? []);
|
|
150
|
+
return document;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Replace a code-set reference with one the linker built, holding the same
|
|
155
|
+
* target. A relink resets and the reference index reads only such
|
|
156
|
+
* references, and only those listed in `document.references`. The first
|
|
157
|
+
* build unlinks them before its link phase, which resolves and indexes them.
|
|
158
|
+
* The target is kept in the field Langium's references hold it in, so a
|
|
159
|
+
* linker whose references have no such field resolves it on first read.
|
|
160
|
+
* `undefined` when the reference has no slot to go back in.
|
|
161
|
+
*/
|
|
162
|
+
protected rebuildReference(
|
|
163
|
+
linker: Linker,
|
|
164
|
+
{ reference, container, property, index }: ReferenceInfo
|
|
165
|
+
): Reference | MultiReference | undefined {
|
|
166
|
+
const owner: unknown = Reflect.get(container, property);
|
|
167
|
+
if (index !== undefined && !Array.isArray(owner)) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
let rebuilt: Reference | MultiReference;
|
|
171
|
+
if (isMultiReference(reference)) {
|
|
172
|
+
// Read through the field, as `items` would resolve a linker-built reference.
|
|
173
|
+
const items = '_items' in reference ? reference._items : reference.items;
|
|
174
|
+
const multi = linker.buildMultiReference(container, property, reference.$refNode, reference.$refText);
|
|
175
|
+
if ('_items' in multi) {
|
|
176
|
+
multi._items = Array.isArray(items) ? [...items] : undefined;
|
|
177
|
+
}
|
|
178
|
+
rebuilt = multi;
|
|
179
|
+
} else {
|
|
180
|
+
const target = '_ref' in reference ? reference._ref : reference.ref;
|
|
181
|
+
const single = linker.buildReference(container, property, reference.$refNode, reference.$refText);
|
|
182
|
+
if ('_ref' in single) {
|
|
183
|
+
single._ref = isAstNode(target) ? target : undefined;
|
|
184
|
+
}
|
|
185
|
+
rebuilt = single;
|
|
186
|
+
}
|
|
187
|
+
if (Array.isArray(owner) && index !== undefined) {
|
|
188
|
+
owner[index] = rebuilt;
|
|
189
|
+
} else {
|
|
190
|
+
Reflect.set(container, property, rebuilt);
|
|
191
|
+
}
|
|
192
|
+
return rebuilt;
|
|
118
193
|
}
|
|
119
194
|
|
|
120
195
|
protected override createLangiumDocument<T extends AstNode = AstNode>(
|
|
@@ -127,7 +202,10 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
127
202
|
// Without `textDocument` Langium creates one at version 0 on first read,
|
|
128
203
|
// which needs a language for `uri` that a folder URI does not have.
|
|
129
204
|
const parsed = parseResult.value.$cstNode?.root.fullText ?? text ?? textDocument?.getText();
|
|
130
|
-
|
|
205
|
+
const version = this.versionOfParsedText(uri, parsed, textDocument?.version ?? 0);
|
|
206
|
+
// A `fromModel` root has no CST to hold its serialized text. Recorded, it
|
|
207
|
+
// is the text a build grafts a CST from, keeping the model's nodes.
|
|
208
|
+
this.versionSyncService().modelProduced(document, { version, text: parseResult.value.$cstNode ? undefined : text });
|
|
131
209
|
return document;
|
|
132
210
|
}
|
|
133
211
|
|
|
@@ -171,8 +249,12 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
171
249
|
// documents, and a reset now would drop their build state.
|
|
172
250
|
const root = document.parseResult.value;
|
|
173
251
|
const parsedFrom = root.$cstNode === undefined ? this.modelLedger().textOf(root) : undefined;
|
|
252
|
+
const codeBuilt = await this.codeBuiltSource(document);
|
|
174
253
|
if (parsedFrom !== undefined) {
|
|
175
|
-
const text =
|
|
254
|
+
const text =
|
|
255
|
+
codeBuilt?.text ??
|
|
256
|
+
this.textDocuments?.get(document.uri.toString())?.getText() ??
|
|
257
|
+
(await this.fileSystemProvider.readFile(document.uri));
|
|
176
258
|
if (text === parsedFrom) {
|
|
177
259
|
this.cstResidency().rehydrate(document);
|
|
178
260
|
if (root.$cstNode === undefined) {
|
|
@@ -180,8 +262,11 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
180
262
|
}
|
|
181
263
|
}
|
|
182
264
|
}
|
|
183
|
-
const stored = this.textDocuments?.get(document.uri.toString())?.version;
|
|
184
|
-
const updated =
|
|
265
|
+
const stored = codeBuilt === undefined ? this.textDocuments?.get(document.uri.toString())?.version : codeBuilt.version;
|
|
266
|
+
const updated =
|
|
267
|
+
codeBuilt === undefined
|
|
268
|
+
? await super.update(document, cancellationToken)
|
|
269
|
+
: await this.reparse(document, codeBuilt.text, cancellationToken);
|
|
185
270
|
const version =
|
|
186
271
|
stored ??
|
|
187
272
|
this.versionOfParsedText(
|
|
@@ -193,6 +278,49 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
193
278
|
return updated;
|
|
194
279
|
}
|
|
195
280
|
|
|
281
|
+
/**
|
|
282
|
+
* The text a code-built document is read from when no file is at its URI:
|
|
283
|
+
* the store's while it is open, else the text it holds, serialized at
|
|
284
|
+
* creation or last read, as `VirtualFileSystemProvider` serves it.
|
|
285
|
+
* `undefined` for any other document, which is read as Langium reads it.
|
|
286
|
+
* The store's version is read with its text, since an edit can land
|
|
287
|
+
* before the caller resumes. Held text has no version.
|
|
288
|
+
*/
|
|
289
|
+
protected async codeBuiltSource(document: LangiumDocument): Promise<{ text: string; version: number | undefined } | undefined> {
|
|
290
|
+
if (!this.codeBuiltDocuments.has(document) || (await this.fileSystemProvider.exists(document.uri))) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const open = this.textDocuments?.get(document.uri.toString());
|
|
294
|
+
return open === undefined
|
|
295
|
+
? { text: document.textDocument.getText(), version: undefined }
|
|
296
|
+
: { text: open.getText(), version: open.version };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Langium's `update` with `text` in place of the text it reads from the
|
|
301
|
+
* store or the URI. Mirrors `DefaultLangiumDocumentFactory.update` of
|
|
302
|
+
* Langium 4.3.1, so a Langium upgrade has to compare the two. From
|
|
303
|
+
* Langium 4.4, an override of `getUpdatedText` replaces it.
|
|
304
|
+
*/
|
|
305
|
+
protected async reparse<T extends AstNode = AstNode>(
|
|
306
|
+
document: Mutable<LangiumDocument<T>>,
|
|
307
|
+
text: string,
|
|
308
|
+
cancellationToken: CancellationToken
|
|
309
|
+
): Promise<LangiumDocument<T>> {
|
|
310
|
+
const textDocument = this.textDocuments?.get(document.uri.toString());
|
|
311
|
+
Object.defineProperty(
|
|
312
|
+
document,
|
|
313
|
+
'textDocument',
|
|
314
|
+
textDocument ? { value: textDocument } : { get: this.createTextDocumentGetter(document.uri, text) }
|
|
315
|
+
);
|
|
316
|
+
if (document.parseResult.value.$cstNode?.root.fullText !== text) {
|
|
317
|
+
document.parseResult = await this.parseAsync<T>(document.uri, text, cancellationToken);
|
|
318
|
+
(document.parseResult.value as Mutable<AstNode>).$document = document;
|
|
319
|
+
}
|
|
320
|
+
document.state = DocumentState.Parsed;
|
|
321
|
+
return document;
|
|
322
|
+
}
|
|
323
|
+
|
|
196
324
|
/**
|
|
197
325
|
* `version`, or {@link STALE_VERSION} when the store holds `uri` open with
|
|
198
326
|
* other text than `parsed`: text read from the file while an open landed.
|
|
@@ -52,9 +52,7 @@ export function startEventLoopMonitor(shared: ServerSharedServicesMinimal, optio
|
|
|
52
52
|
const onStallSnapshot =
|
|
53
53
|
options.onStallSnapshot ??
|
|
54
54
|
((maxMs: number) =>
|
|
55
|
-
shared.Logger.for('Process').info(
|
|
56
|
-
formatServerState(shared.workspace.LangiumDocuments, `auto: event-loop stall ${Math.round(maxMs)}ms`)
|
|
57
|
-
));
|
|
55
|
+
shared.Logger.for('Process').info(formatServerState(shared.workspace, `auto: event-loop stall ${Math.round(maxMs)}ms`)));
|
|
58
56
|
const histogram = monitorEventLoopDelay({ resolution: 10 });
|
|
59
57
|
histogram.enable();
|
|
60
58
|
const interval = setInterval(() => {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { Format, type Stopwatch, SystemClock } from '@hydranium/protocol';
|
|
11
11
|
import { DocumentState, type LangiumDocuments } from '@hydranium/langium';
|
|
12
|
+
import { type ModelLedger } from '../documents/model-ledger.js';
|
|
13
|
+
import { type ServerSharedServicesMinimal } from '../langium/shared-services.js';
|
|
12
14
|
import { isCstShed } from '../langium/residency/cst-residency-service.js';
|
|
13
15
|
import { performance } from 'node:perf_hooks';
|
|
14
16
|
import * as v8 from 'node:v8';
|
|
@@ -58,9 +60,15 @@ export interface ServerStateSnapshotOptions {
|
|
|
58
60
|
* logs the result, e.g. `tracer.info(formatServerState(...))`), matching
|
|
59
61
|
* `formatProcessMemory` / `formatPodMemory`. Tracks deltas across calls so
|
|
60
62
|
* subsequent snapshots show "since last snapshot" rates. Optional sections are
|
|
61
|
-
* requested via `options` ({@link ServerStateSnapshotOptions}).
|
|
63
|
+
* requested via `options` ({@link ServerStateSnapshotOptions}). `workspace`
|
|
64
|
+
* adds the document counts. Its ledger tells a shed document from one that
|
|
65
|
+
* never had a CST.
|
|
62
66
|
*/
|
|
63
|
-
export function formatServerState(
|
|
67
|
+
export function formatServerState(
|
|
68
|
+
workspace?: Pick<ServerSharedServicesMinimal['workspace'], 'LangiumDocuments' | 'ModelLedger'>,
|
|
69
|
+
label?: string,
|
|
70
|
+
options?: ServerStateSnapshotOptions
|
|
71
|
+
): string {
|
|
64
72
|
const sinceSample = new SystemClock().stopwatch();
|
|
65
73
|
const mem = process.memoryUsage();
|
|
66
74
|
const heap = v8.getHeapStatistics();
|
|
@@ -110,7 +118,8 @@ export function formatServerState(documents?: LangiumDocuments, label?: string,
|
|
|
110
118
|
const eluPct = eluTotal > 0 ? Math.round((elu.active / eluTotal) * 100) : 0;
|
|
111
119
|
lines.push(` loop utilization ${eluPct}% (cumulative since process start)`);
|
|
112
120
|
}
|
|
113
|
-
if (
|
|
121
|
+
if (workspace) {
|
|
122
|
+
const { LangiumDocuments: documents, ModelLedger: ledger } = workspace;
|
|
114
123
|
const counts = countDocumentsByState(documents);
|
|
115
124
|
const total = Object.values(counts).reduce((sum, n) => sum + n, 0);
|
|
116
125
|
const breakdown = Object.entries(counts)
|
|
@@ -118,9 +127,9 @@ export function formatServerState(documents?: LangiumDocuments, label?: string,
|
|
|
118
127
|
.map(([state, n]) => `${state}: ${n}`)
|
|
119
128
|
.join(', ');
|
|
120
129
|
lines.push(` documents ${total} tracked${breakdown ? ` (${breakdown})` : ''}`);
|
|
121
|
-
// CST-residency breakdown
|
|
122
|
-
//
|
|
123
|
-
const residency = countCstResidency(documents);
|
|
130
|
+
// CST-residency breakdown, emitted once a document waits for a CST graft:
|
|
131
|
+
// shed by a residency policy, or built from code and not built yet.
|
|
132
|
+
const residency = countCstResidency(documents, ledger);
|
|
124
133
|
if (residency.shed > 0) {
|
|
125
134
|
lines.push(` cst ${residency.resident} resident, ${residency.shed} shed`);
|
|
126
135
|
}
|
|
@@ -143,17 +152,17 @@ export function formatServerState(documents?: LangiumDocuments, label?: string,
|
|
|
143
152
|
|
|
144
153
|
/**
|
|
145
154
|
* Count documents whose CST is resident vs. shed by a residency policy
|
|
146
|
-
* ({@link isCstShed}). A document can also be in neither bucket
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* on the preceding `documents` line.
|
|
155
|
+
* ({@link isCstShed}). A document can also be in neither bucket: a
|
|
156
|
+
* not-yet-parsed document, a synthetic placeholder, or a `fromModel` document
|
|
157
|
+
* without a serializer has no CST and no text to graft one from. So
|
|
158
|
+
* `resident + shed` is at most the total tracked, with the remainder explained
|
|
159
|
+
* by the state breakdown on the preceding `documents` line.
|
|
151
160
|
*/
|
|
152
|
-
function countCstResidency(documents: LangiumDocuments): { resident: number; shed: number } {
|
|
161
|
+
function countCstResidency(documents: LangiumDocuments, ledger: ModelLedger): { resident: number; shed: number } {
|
|
153
162
|
let resident = 0;
|
|
154
163
|
let shed = 0;
|
|
155
164
|
for (const doc of documents.all) {
|
|
156
|
-
if (isCstShed(doc)) {
|
|
165
|
+
if (isCstShed(doc, ledger)) {
|
|
157
166
|
shed++;
|
|
158
167
|
} else if (doc.parseResult?.value?.$cstNode !== undefined) {
|
|
159
168
|
resident++;
|