@hydranium/core 1.0.0-next.187 → 1.0.0-next.189
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/ast-document-manager.d.ts +26 -44
- package/lib/documents/ast-document-manager.d.ts.map +1 -1
- package/lib/documents/ast-document-manager.js +20 -50
- package/lib/documents/ast-document-manager.js.map +1 -1
- package/lib/documents/hydranium-text-documents.d.ts +87 -30
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +201 -37
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/documents/index.d.ts +2 -0
- package/lib/documents/index.d.ts.map +1 -1
- package/lib/documents/index.js +2 -0
- package/lib/documents/index.js.map +1 -1
- package/lib/documents/model-ledger.d.ts +47 -0
- package/lib/documents/model-ledger.d.ts.map +1 -0
- package/lib/documents/model-ledger.js +30 -0
- package/lib/documents/model-ledger.js.map +1 -0
- package/lib/documents/version-sync-service.d.ts +134 -0
- package/lib/documents/version-sync-service.d.ts.map +1 -0
- package/lib/documents/version-sync-service.js +171 -0
- package/lib/documents/version-sync-service.js.map +1 -0
- package/lib/langium/bootstrap.d.ts.map +1 -1
- package/lib/langium/bootstrap.js +16 -0
- package/lib/langium/bootstrap.js.map +1 -1
- package/lib/langium/document-builder/document-builder.d.ts +31 -25
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +43 -39
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-service.js +6 -15
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/model-service/client-session.d.ts +14 -10
- package/lib/langium/model-service/client-session.d.ts.map +1 -1
- package/lib/langium/model-service/client-session.js +18 -15
- package/lib/langium/model-service/client-session.js.map +1 -1
- package/lib/langium/model-service/model-service.d.ts +91 -41
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +127 -50
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/module.d.ts +6 -0
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js +5 -1
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
- package/lib/langium/residency/cst-residency-service.js +10 -1
- package/lib/langium/residency/cst-residency-service.js.map +1 -1
- package/lib/langium/shared-services.d.ts +6 -2
- package/lib/langium/shared-services.d.ts.map +1 -1
- package/lib/langium/shared-services.js.map +1 -1
- package/lib/langium/transfer/transfer-encoder.d.ts +19 -10
- package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
- package/lib/langium/transfer/transfer-encoder.js +8 -7
- package/lib/langium/transfer/transfer-encoder.js.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +31 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.js +54 -0
- package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
- package/lib/langium/workspace/langium-documents.d.ts +7 -0
- package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
- package/lib/langium/workspace/langium-documents.js +10 -0
- package/lib/langium/workspace/langium-documents.js.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.js +1 -0
- package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
- package/lib/testing/make-test-services.d.ts +7 -0
- package/lib/testing/make-test-services.d.ts.map +1 -1
- package/lib/testing/make-test-services.js +10 -2
- package/lib/testing/make-test-services.js.map +1 -1
- package/lib/testing/stub-ast-document-manager.d.ts +3 -2
- package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
- package/lib/testing/stub-ast-document-manager.js +7 -1
- package/lib/testing/stub-ast-document-manager.js.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.d.ts +2 -2
- package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.js +6 -1
- package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
- package/package.json +5 -5
- package/src/documents/ast-document-manager.ts +45 -77
- package/src/documents/hydranium-text-documents.ts +225 -46
- package/src/documents/index.ts +2 -0
- package/src/documents/model-ledger.ts +66 -0
- package/src/documents/version-sync-service.ts +267 -0
- package/src/langium/bootstrap.ts +16 -0
- package/src/langium/document-builder/document-builder.ts +76 -37
- package/src/langium/integrity/integrity-service.ts +6 -15
- package/src/langium/model-service/client-session.ts +29 -21
- package/src/langium/model-service/model-service.ts +182 -64
- package/src/langium/module.ts +9 -1
- package/src/langium/residency/cst-residency-service.ts +10 -1
- package/src/langium/shared-services.ts +6 -2
- package/src/langium/transfer/transfer-encoder.ts +25 -18
- package/src/langium/workspace/hydranium-langium-document-factory.ts +83 -0
- package/src/langium/workspace/langium-documents.ts +11 -0
- package/src/lsp/hydranium-document-update-handler.ts +3 -0
- package/src/testing/make-test-services.ts +15 -2
- package/src/testing/stub-ast-document-manager.ts +10 -2
- package/src/testing/stub-hydranium-text-documents.ts +8 -3
|
@@ -12,6 +12,8 @@ import type { LangiumSharedCoreServices } from '@hydranium/langium';
|
|
|
12
12
|
import type { ServerLocale } from '../locale/server-locale.js';
|
|
13
13
|
import type { MessageRenderer } from '../messages/renderer.js';
|
|
14
14
|
import type { SelfSaveRegistry } from '../documents/self-save-registry.js';
|
|
15
|
+
import type { ModelLedger } from '../documents/model-ledger.js';
|
|
16
|
+
import type { VersionSyncService } from '../documents/version-sync-service.js';
|
|
15
17
|
import type { WritableFileSystemProvider } from '../documents/ast-document-manager.js';
|
|
16
18
|
import type { HydraniumDocumentRegistry } from './workspace/langium-documents.js';
|
|
17
19
|
import type { BuildPhasePassService } from './build-phase-pass/build-phase-pass-service.js';
|
|
@@ -35,8 +37,8 @@ import type { AdditionalDocumentContribution } from './workspace/additional-docu
|
|
|
35
37
|
* an {@link ExtendedServiceRegistry}, and on `workspace` a writable file-system
|
|
36
38
|
* provider plus {@link HydraniumWorkspaceManager}, {@link ProjectManager},
|
|
37
39
|
* {@link SelfSaveRegistry}, {@link BuildPipelineIntegration},
|
|
38
|
-
* {@link BuildPhasePassService}, {@link CstResidencyService}
|
|
39
|
-
* {@link DocumentUriPolicy}.
|
|
40
|
+
* {@link BuildPhasePassService}, {@link CstResidencyService},
|
|
41
|
+
* {@link DocumentUriPolicy}, {@link ModelLedger} and {@link VersionSyncService}.
|
|
40
42
|
*
|
|
41
43
|
* Declared structurally (interface extends + intersection on nested slots)
|
|
42
44
|
* so the full `ServerSharedServices` and any consumer-extended variant
|
|
@@ -98,6 +100,8 @@ export interface ServerSharedServicesMinimal<TProject extends Project = Project>
|
|
|
98
100
|
BuildPhasePassService: BuildPhasePassService;
|
|
99
101
|
CstResidencyService: CstResidencyService;
|
|
100
102
|
DocumentUriPolicy: DocumentUriPolicy;
|
|
103
|
+
ModelLedger: ModelLedger;
|
|
104
|
+
VersionSyncService: VersionSyncService;
|
|
101
105
|
};
|
|
102
106
|
}
|
|
103
107
|
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
asModelVersion,
|
|
11
12
|
hasMessageIdentity,
|
|
13
|
+
type TransferModelSnapshot,
|
|
12
14
|
type Tracer,
|
|
13
15
|
type TransferDiagnostic,
|
|
14
16
|
type TransferElement,
|
|
15
|
-
TransferDocument,
|
|
16
17
|
type TransferTypeFor
|
|
17
18
|
} from '@hydranium/protocol';
|
|
18
19
|
import { AstUtils, type AstNode, DocumentCache, DocumentState, isAstNode, isReference, type LangiumDocument } from '@hydranium/langium';
|
|
@@ -75,11 +76,21 @@ export interface EncodeContext {
|
|
|
75
76
|
* `diagnostics` accepts either the wire shape (an already-projected
|
|
76
77
|
* `TDiagnostic`, as a snapshot may carry) or a raw LSP {@link Diagnostic}; the
|
|
77
78
|
* assembly projects each through {@link TransferEncoder.toTransferDiagnostic}.
|
|
79
|
+
* Absent for a document not validated yet, and left absent on the wire.
|
|
78
80
|
*/
|
|
79
81
|
export interface TransferEnvelopeSource<TDiagnostic> {
|
|
80
82
|
readonly uri: string;
|
|
81
83
|
readonly version: number;
|
|
82
|
-
readonly diagnostics
|
|
84
|
+
readonly diagnostics?: readonly (TDiagnostic | Diagnostic)[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A `TransferDocument` as the encoder produces it: the model block without
|
|
89
|
+
* its `hash`, which the data server computes from the encoded model.
|
|
90
|
+
*/
|
|
91
|
+
export interface EncodedTransferDocument<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic> {
|
|
92
|
+
uri: string;
|
|
93
|
+
model: Omit<TransferModelSnapshot<TTransfer, TDiagnostic>, 'hash'>;
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
/**
|
|
@@ -106,10 +117,10 @@ export interface TransferEnvelopeSource<TDiagnostic> {
|
|
|
106
117
|
*/
|
|
107
118
|
export interface TransferEncoder<TDiagnostic extends TransferDiagnostic = TransferDiagnostic> {
|
|
108
119
|
toTransfer<T extends AstNode>(ast: T, mode?: TransferMode): TransferElement;
|
|
109
|
-
toTransferDocument(langiumDocument: LangiumDocument):
|
|
120
|
+
toTransferDocument(langiumDocument: LangiumDocument): EncodedTransferDocument<TransferElement, TDiagnostic>;
|
|
110
121
|
astDocumentToTransferDocument<TAst extends AstNode>(
|
|
111
122
|
document: AstDocument<TAst, AstDiagnostic>
|
|
112
|
-
):
|
|
123
|
+
): EncodedTransferDocument<TransferElement, TDiagnostic>;
|
|
113
124
|
toTransferDiagnostic(diagnostic: AstDiagnostic): TDiagnostic;
|
|
114
125
|
}
|
|
115
126
|
|
|
@@ -153,7 +164,7 @@ export interface TransferEncoder<TDiagnostic extends TransferDiagnostic = Transf
|
|
|
153
164
|
* to special-case reference encoding or to drop specific value shapes.
|
|
154
165
|
* Envelope-level seams:
|
|
155
166
|
* - {@link toTransferDocument} — bundle root + diagnostics into the wire
|
|
156
|
-
* {@link
|
|
167
|
+
* {@link EncodedTransferDocument} envelope. Override to filter / merge
|
|
157
168
|
* diagnostics from auxiliary sources or to project a synthesised root.
|
|
158
169
|
* - {@link toTransferDiagnostic} — project a {@link AstDiagnostic}
|
|
159
170
|
* (the framework validator's output) to {@link TDiagnostic}.
|
|
@@ -336,7 +347,7 @@ export class DefaultTransferEncoder<
|
|
|
336
347
|
}
|
|
337
348
|
|
|
338
349
|
/**
|
|
339
|
-
* Build a wire {@link
|
|
350
|
+
* Build a wire {@link EncodedTransferDocument} envelope from a built
|
|
340
351
|
* {@link LangiumDocument}. The `'full'` root is served from the internal
|
|
341
352
|
* {@link rootCache} (memoised per document until it next rebuilds), under
|
|
342
353
|
* {@link rootCacheKey} — so the data-server's fingerprint and emitted
|
|
@@ -360,7 +371,7 @@ export class DefaultTransferEncoder<
|
|
|
360
371
|
*/
|
|
361
372
|
toTransferDocument<TAst extends AstNode>(
|
|
362
373
|
langiumDocument: LangiumDocument
|
|
363
|
-
):
|
|
374
|
+
): EncodedTransferDocument<TransferTypeFor<TAst, TTransferMap>, TDiagnostic> {
|
|
364
375
|
const context = this.createEncodeContext('full', langiumDocument.uri.toString());
|
|
365
376
|
const root = this.rootCache.get(langiumDocument.uri, this.rootCacheKey(context), () =>
|
|
366
377
|
this.encodeNode(langiumDocument.parseResult.value, context)
|
|
@@ -368,8 +379,8 @@ export class DefaultTransferEncoder<
|
|
|
368
379
|
return this.assembleTransferDocument<TAst>(
|
|
369
380
|
{
|
|
370
381
|
uri: langiumDocument.textDocument.uri,
|
|
371
|
-
version: langiumDocument.
|
|
372
|
-
diagnostics: langiumDocument.diagnostics
|
|
382
|
+
version: this.services.workspace.ModelLedger.versionOf(langiumDocument.parseResult.value),
|
|
383
|
+
diagnostics: langiumDocument.diagnostics
|
|
373
384
|
},
|
|
374
385
|
root
|
|
375
386
|
);
|
|
@@ -390,17 +401,13 @@ export class DefaultTransferEncoder<
|
|
|
390
401
|
protected assembleTransferDocument<TAst extends AstNode>(
|
|
391
402
|
source: TransferEnvelopeSource<TDiagnostic>,
|
|
392
403
|
root: TransferTypeFor<TAst, TTransferMap>
|
|
393
|
-
):
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
source.version,
|
|
397
|
-
root,
|
|
398
|
-
source.diagnostics.map(diagnostic => this.toTransferDiagnostic(diagnostic as AstDiagnostic))
|
|
399
|
-
);
|
|
404
|
+
): EncodedTransferDocument<TransferTypeFor<TAst, TTransferMap>, TDiagnostic> {
|
|
405
|
+
const diagnostics = source.diagnostics?.map(diagnostic => this.toTransferDiagnostic(diagnostic as AstDiagnostic));
|
|
406
|
+
return { uri: source.uri, model: { root, version: asModelVersion(source.version), ...(diagnostics ? { diagnostics } : {}) } };
|
|
400
407
|
}
|
|
401
408
|
|
|
402
409
|
/**
|
|
403
|
-
* Build a wire {@link
|
|
410
|
+
* Build a wire {@link EncodedTransferDocument} envelope from a server-internal
|
|
404
411
|
* {@link AstDocument} snapshot (the AST-typed envelope emitted by
|
|
405
412
|
* `AstDocumentManager.onUpdate` / `onSave` and returned from
|
|
406
413
|
* `ModelService.request` / `update` / `save`). Distinct from
|
|
@@ -422,7 +429,7 @@ export class DefaultTransferEncoder<
|
|
|
422
429
|
*/
|
|
423
430
|
astDocumentToTransferDocument<TAst extends AstNode>(
|
|
424
431
|
document: AstDocument<TAst, AstDiagnostic>
|
|
425
|
-
):
|
|
432
|
+
): EncodedTransferDocument<TransferTypeFor<TAst, TTransferMap>, TDiagnostic> {
|
|
426
433
|
const root = this.encodeNode(document.root, this.createEncodeContext('full', document.uri)) as TransferTypeFor<TAst, TTransferMap>;
|
|
427
434
|
return this.assembleTransferDocument<TAst>(document, root);
|
|
428
435
|
}
|
|
@@ -12,10 +12,17 @@ import {
|
|
|
12
12
|
AstUtils,
|
|
13
13
|
DefaultLangiumDocumentFactory,
|
|
14
14
|
type LangiumDocument,
|
|
15
|
+
type Mutable,
|
|
15
16
|
type ParseResult,
|
|
16
17
|
type URI
|
|
17
18
|
} from '@hydranium/langium';
|
|
19
|
+
import { type CancellationToken } from 'vscode-languageserver-protocol';
|
|
20
|
+
import { type TextDocument } from 'vscode-languageserver-textdocument';
|
|
21
|
+
import { STALE_VERSION } from '@hydranium/protocol';
|
|
22
|
+
import { type ModelLedger } from '../../documents/model-ledger.js';
|
|
23
|
+
import { type VersionSyncService } from '../../documents/version-sync-service.js';
|
|
18
24
|
import { type ExtendedServiceRegistry } from '../service-registry.js';
|
|
25
|
+
import { type ServerSharedServicesMinimal } from '../shared-services.js';
|
|
19
26
|
import { type Serializer } from '../serialization/serializer.js';
|
|
20
27
|
|
|
21
28
|
/** Structural view of the per-language serializer slot on the resolved services. */
|
|
@@ -57,6 +64,16 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
57
64
|
*/
|
|
58
65
|
declare protected readonly serviceRegistry: ExtendedServiceRegistry;
|
|
59
66
|
|
|
67
|
+
/** Resolved per call: the sync service reaches the registry, which is built with this factory. */
|
|
68
|
+
protected readonly versionSyncService: () => VersionSyncService;
|
|
69
|
+
protected readonly modelLedger: () => ModelLedger;
|
|
70
|
+
|
|
71
|
+
constructor(services: ServerSharedServicesMinimal) {
|
|
72
|
+
super(services);
|
|
73
|
+
this.versionSyncService = () => services.workspace.VersionSyncService;
|
|
74
|
+
this.modelLedger = () => services.workspace.ModelLedger;
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
/**
|
|
61
78
|
* Parse `text` for `uri` under the grammar `languageId` names, instead of
|
|
62
79
|
* the one `uri` routes to.
|
|
@@ -95,6 +112,72 @@ export class HydraniumLangiumDocumentFactory extends DefaultLangiumDocumentFacto
|
|
|
95
112
|
return this.createLangiumDocument<T>(parseResult, uri, undefined, text);
|
|
96
113
|
}
|
|
97
114
|
|
|
115
|
+
protected override createLangiumDocument<T extends AstNode = AstNode>(
|
|
116
|
+
parseResult: ParseResult<T>,
|
|
117
|
+
uri: URI,
|
|
118
|
+
textDocument?: TextDocument,
|
|
119
|
+
text?: string
|
|
120
|
+
): LangiumDocument<T> {
|
|
121
|
+
const document = super.createLangiumDocument(parseResult, uri, textDocument, text);
|
|
122
|
+
// Without `textDocument` Langium creates one at version 0 on first read,
|
|
123
|
+
// which needs a language for `uri` that a folder URI does not have.
|
|
124
|
+
const parsed = parseResult.value.$cstNode?.root.fullText ?? text ?? textDocument?.getText();
|
|
125
|
+
this.versionSyncService().modelProduced(document, { version: this.versionOfParsedText(uri, parsed, textDocument?.version ?? 0) });
|
|
126
|
+
return document;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The version is read before the parse, for the same reason as in
|
|
131
|
+
* {@link update}: the text document is the store's, which an edit updates
|
|
132
|
+
* in place while the parse runs.
|
|
133
|
+
*/
|
|
134
|
+
protected override async createAsync<T extends AstNode = AstNode>(
|
|
135
|
+
uri: URI,
|
|
136
|
+
content: string | TextDocument,
|
|
137
|
+
cancelToken: CancellationToken
|
|
138
|
+
): Promise<LangiumDocument<T>> {
|
|
139
|
+
const version = typeof content === 'string' ? undefined : content.version;
|
|
140
|
+
const document = await super.createAsync<T>(uri, content, cancelToken);
|
|
141
|
+
if (version !== undefined) {
|
|
142
|
+
this.versionSyncService().modelProduced(document, { version });
|
|
143
|
+
}
|
|
144
|
+
return document;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The store's version is read before Langium's `update`, which reads the
|
|
149
|
+
* store's text before its first await: read after, it can belong to an
|
|
150
|
+
* edit that arrived while the parse ran.
|
|
151
|
+
*
|
|
152
|
+
* Reconciled with the store here rather than in a `Parsed` listener, which
|
|
153
|
+
* a cancel skips, leaving the root on the version of its earlier text.
|
|
154
|
+
*/
|
|
155
|
+
override async update<T extends AstNode = AstNode>(
|
|
156
|
+
document: Mutable<LangiumDocument<T>>,
|
|
157
|
+
cancellationToken: CancellationToken
|
|
158
|
+
): Promise<LangiumDocument<T>> {
|
|
159
|
+
const stored = this.textDocuments?.get(document.uri.toString())?.version;
|
|
160
|
+
const updated = await super.update(document, cancellationToken);
|
|
161
|
+
const version =
|
|
162
|
+
stored ??
|
|
163
|
+
this.versionOfParsedText(
|
|
164
|
+
updated.uri,
|
|
165
|
+
this.modelLedger().textOf(updated.parseResult.value) ?? updated.textDocument.getText(),
|
|
166
|
+
updated.textDocument.version
|
|
167
|
+
);
|
|
168
|
+
this.versionSyncService().modelProduced(updated, { version });
|
|
169
|
+
return updated;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* `version`, or {@link STALE_VERSION} when the store holds `uri` open with
|
|
174
|
+
* other text than `parsed`: text read from the file while an open landed.
|
|
175
|
+
*/
|
|
176
|
+
protected versionOfParsedText(uri: URI, parsed: string | undefined, version: number): number {
|
|
177
|
+
const open = this.textDocuments?.get(uri.toString());
|
|
178
|
+
return open !== undefined && parsed !== undefined && open.getText() !== parsed ? STALE_VERSION : version;
|
|
179
|
+
}
|
|
180
|
+
|
|
98
181
|
/**
|
|
99
182
|
* Serialize `model` via the per-language serializer, or `undefined` if none
|
|
100
183
|
* is bound, it fails, or it is async — `fromModel` is synchronous, so an
|
|
@@ -132,6 +132,17 @@ export class HydraniumLangiumDocuments extends DefaultLangiumDocuments implement
|
|
|
132
132
|
return this.langiumDocumentFactory.fromString('', uri);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Registers `document`, then reports it to `VersionSyncService.modelProduced`:
|
|
137
|
+
* a first load parses outside the build, so no build's re-parse reports it.
|
|
138
|
+
* That marks one below `Parsed`, such as the builder's placeholder for a new
|
|
139
|
+
* file, as a placeholder. A rejected duplicate is not reported.
|
|
140
|
+
*/
|
|
141
|
+
override addDocument(document: LangiumDocument): void {
|
|
142
|
+
super.addDocument(document);
|
|
143
|
+
this.services.workspace.VersionSyncService.modelProduced(document);
|
|
144
|
+
}
|
|
145
|
+
|
|
135
146
|
override async getOrCreateDocument(uri: URI): Promise<LangiumDocument<AstNode>> {
|
|
136
147
|
const resolved = this.uriPolicy.loadUri(uri);
|
|
137
148
|
if (resolved) {
|
|
@@ -171,6 +171,9 @@ export class HydraniumDocumentUpdateHandler extends DefaultDocumentUpdateHandler
|
|
|
171
171
|
this.bypassNonLanguageClientChanges = options.bypassNonLanguageClientChanges ?? true;
|
|
172
172
|
this.willSaveGateMs = options.willSaveGateMs ?? 1000;
|
|
173
173
|
this.textDocuments.onDidSaveInLanguageClient(event => this.editorSaves.get(event.uri)?.());
|
|
174
|
+
services.workspace.VersionSyncService.registerDeferredBuilds(
|
|
175
|
+
uri => this.pendingChanged.has(uri.toString()) || this.pendingDeleted.has(uri.toString())
|
|
176
|
+
);
|
|
174
177
|
// A change still debounced for a document the store has released would
|
|
175
178
|
// build it once more after the store's own revert: from the provider
|
|
176
179
|
// again, or, for a document the revert removed, by reading a file that
|
|
@@ -35,6 +35,8 @@ import { DefaultTransferEncoder, type TransferEncoder } from '../langium/transfe
|
|
|
35
35
|
import { DefaultDocumentUriPolicy, type DocumentUriPolicy } from '../langium/workspace/document-uri-policy.js';
|
|
36
36
|
import { HydraniumWorkspaceLock } from '../langium/workspace/hydranium-workspace-lock.js';
|
|
37
37
|
import { DefaultFileSystemTaskQueue, type FileSystemTaskQueue } from '../documents/file-system-task-queue.js';
|
|
38
|
+
import { DefaultModelLedger, type ModelLedger } from '../documents/model-ledger.js';
|
|
39
|
+
import { DefaultVersionSyncService, type VersionSyncService } from '../documents/version-sync-service.js';
|
|
38
40
|
import type { FakeDocumentOptions } from './fake-document.js';
|
|
39
41
|
import { makeStubDocumentBuilder, type StubDocumentBuilder } from './stub-document-builder.js';
|
|
40
42
|
import { makeStubIndexManager, type StubIndexManager } from './stub-index-manager.js';
|
|
@@ -109,6 +111,10 @@ export interface TestSharedServices<
|
|
|
109
111
|
* through this tree and let a reentrant write pass unnoticed.
|
|
110
112
|
*/
|
|
111
113
|
WorkspaceLock: WorkspaceLock;
|
|
114
|
+
/** The REAL ledger: it is a weak map with no dependencies. */
|
|
115
|
+
ModelLedger: ModelLedger;
|
|
116
|
+
/** The REAL service, over this tree's stubs; it reconciles nothing against the stub store. */
|
|
117
|
+
VersionSyncService: VersionSyncService;
|
|
112
118
|
};
|
|
113
119
|
readonly model: {
|
|
114
120
|
TransferEncoder: TransferEncoder<TTransferDiagnostic>;
|
|
@@ -266,6 +272,7 @@ export interface TestServicesBundle<
|
|
|
266
272
|
readonly selfSaveRegistry: StubSelfSaveRegistry;
|
|
267
273
|
readonly projectManager: StubProjectManager<TProject>;
|
|
268
274
|
readonly documentUriPolicy: DocumentUriPolicy;
|
|
275
|
+
readonly modelLedger: ModelLedger;
|
|
269
276
|
/**
|
|
270
277
|
* The registry bound on the `ServiceRegistry` slot, or `undefined` when no
|
|
271
278
|
* {@link MakeTestServicesOptions.languages} were declared.
|
|
@@ -320,7 +327,8 @@ export function makeTestServices<
|
|
|
320
327
|
const selfSaveRegistry = makeStubSelfSaveRegistry();
|
|
321
328
|
const fileSystem = makeStubWritableFileSystem(selfSaveRegistry);
|
|
322
329
|
const projectManager = makeStubProjectManager<TProject>(options.seedProjects);
|
|
323
|
-
const
|
|
330
|
+
const modelLedger = new DefaultModelLedger();
|
|
331
|
+
const astDocumentManager = makeStubAstDocumentManager<TAst, TDiagnostic>(textDocuments, fileSystem, documents, modelLedger);
|
|
324
332
|
const documentUriPolicy = options.documentUriPolicy ?? new DefaultDocumentUriPolicy();
|
|
325
333
|
const serviceRegistry = options.languages
|
|
326
334
|
? makeStubServiceRegistry(options.languages, { openLanguageIds: options.openLanguageIds })
|
|
@@ -368,7 +376,10 @@ export function makeTestServices<
|
|
|
368
376
|
// be genuinely ABSENT, not present-and-undefined, when no index was seeded.
|
|
369
377
|
...(indexManager ? { IndexManager: indexManager } : {}),
|
|
370
378
|
WorkspaceLock: workspaceLock,
|
|
371
|
-
FileSystemTaskQueue: new DefaultFileSystemTaskQueue({ workspace: { DocumentUriPolicy: documentUriPolicy } })
|
|
379
|
+
FileSystemTaskQueue: new DefaultFileSystemTaskQueue({ workspace: { DocumentUriPolicy: documentUriPolicy } }),
|
|
380
|
+
ModelLedger: modelLedger,
|
|
381
|
+
// Patched in below: it reads the tree it belongs to.
|
|
382
|
+
VersionSyncService: {} as VersionSyncService
|
|
372
383
|
},
|
|
373
384
|
model: {} as TestSharedServices<TAst, TDiagnostic, TTransfer, TProject, TTransferDiagnostic>['model'],
|
|
374
385
|
ServerLocale: {} as ServerLocale,
|
|
@@ -378,6 +389,7 @@ export function makeTestServices<
|
|
|
378
389
|
|
|
379
390
|
// Patched in after the literal, like `model` below: both read the tree they
|
|
380
391
|
// belong to, and the renderer reads the locale service.
|
|
392
|
+
services.workspace.VersionSyncService = new DefaultVersionSyncService(sharedServices);
|
|
381
393
|
const mutableMessages = services as { ServerLocale: ServerLocale; MessageRenderer: MessageRenderer };
|
|
382
394
|
const serverLocale = new DefaultServerLocale(sharedServices);
|
|
383
395
|
if (options.locale) {
|
|
@@ -414,6 +426,7 @@ export function makeTestServices<
|
|
|
414
426
|
selfSaveRegistry,
|
|
415
427
|
projectManager,
|
|
416
428
|
documentUriPolicy,
|
|
429
|
+
modelLedger,
|
|
417
430
|
serviceRegistry,
|
|
418
431
|
indexManager,
|
|
419
432
|
modelService,
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
type WritableFileSystemProvider
|
|
19
19
|
} from '../documents/ast-document-manager.js';
|
|
20
20
|
import { UNKNOWN_CLIENT_ID } from '../documents/client-ids.js';
|
|
21
|
-
import
|
|
21
|
+
import { DefaultModelLedger, type ModelLedger } from '../documents/model-ledger.js';
|
|
22
|
+
import { type CloseModelArgs, type OpenModelArgs } from '@hydranium/protocol';
|
|
22
23
|
import type { StubHydraniumTextDocuments } from './stub-hydranium-text-documents.js';
|
|
23
24
|
import type { StubLangiumDocuments } from './stub-langium-documents.js';
|
|
24
25
|
|
|
@@ -60,6 +61,7 @@ export interface StubAstDocumentManager<TAst extends AstNode, TDiagnostic extend
|
|
|
60
61
|
| 'onClientClosed'
|
|
61
62
|
| 'getAuthor'
|
|
62
63
|
| 'getDocument'
|
|
64
|
+
| 'toAstDocument'
|
|
63
65
|
| 'attributeUpdate'
|
|
64
66
|
> {
|
|
65
67
|
readonly openClients: Map<string, Set<string>>;
|
|
@@ -109,7 +111,8 @@ export interface StubAstDocumentManager<TAst extends AstNode, TDiagnostic extend
|
|
|
109
111
|
export function makeStubAstDocumentManager<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic>(
|
|
110
112
|
textDocuments: StubHydraniumTextDocuments,
|
|
111
113
|
fileSystem: Pick<WritableFileSystemProvider, 'writeFile'>,
|
|
112
|
-
documents?: StubLangiumDocuments<TAst, TDiagnostic
|
|
114
|
+
documents?: StubLangiumDocuments<TAst, TDiagnostic>,
|
|
115
|
+
ledger: ModelLedger = new DefaultModelLedger()
|
|
113
116
|
): StubAstDocumentManager<TAst, TDiagnostic> {
|
|
114
117
|
const openClients = new Map<string, Set<string>>();
|
|
115
118
|
// Keyed by the URI as given, not canonicalized: the stub has no URI policy,
|
|
@@ -226,6 +229,11 @@ export function makeStubAstDocumentManager<TAst extends AstNode, TDiagnostic ext
|
|
|
226
229
|
// that need real symlink canonicalization wire the real AstDocumentManager).
|
|
227
230
|
getDocument(uri: string): LangiumDocument | undefined {
|
|
228
231
|
return documents?.getDocument(UriUtils.toUri(uri));
|
|
232
|
+
},
|
|
233
|
+
toAstDocument(document: LangiumDocument): AstDocument<TAst, TDiagnostic> {
|
|
234
|
+
const uri = document.textDocument.uri;
|
|
235
|
+
const root = document.parseResult.value as TAst;
|
|
236
|
+
return AstDocument.create<TAst, TDiagnostic>(uri, ledger.versionOf(root), root, document.diagnostics as TDiagnostic[] | undefined);
|
|
229
237
|
}
|
|
230
238
|
};
|
|
231
239
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import type
|
|
10
|
+
import { type CanonicalUri, textHash } from '@hydranium/protocol';
|
|
11
11
|
import type { ApplyWorkspaceEditResult } from 'vscode-languageserver';
|
|
12
12
|
import { Disposable, Emitter } from 'vscode-languageserver';
|
|
13
13
|
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
@@ -36,7 +36,7 @@ export interface StubTextDocumentEntry {
|
|
|
36
36
|
* `isOpenInAnyClient` / `isOpenInClient` / `isRevertPending` /
|
|
37
37
|
* `openDocuments`), the push channel
|
|
38
38
|
* to the language client (`applyEditToLanguageClient` / `stagePendingContent`),
|
|
39
|
-
* the save / close notifications, the dirty state (`isDirty` /
|
|
39
|
+
* the save / close notifications, the dirty state (`isDirty` / `textState` /
|
|
40
40
|
* `onDidChangeDirty` / `updateDiskBaseline`, against a baseline {@link seedOpen}
|
|
41
41
|
* sets and a save that carries its text moves), and the client-session table
|
|
42
42
|
* (`registerSession` / `closeSession` / `onDidCloseSession`), which is a real
|
|
@@ -88,6 +88,7 @@ export interface StubHydraniumTextDocuments extends Pick<
|
|
|
88
88
|
| 'onDidCloseSession'
|
|
89
89
|
| 'onDidCloseLastOpen'
|
|
90
90
|
| 'isDirty'
|
|
91
|
+
| 'textState'
|
|
91
92
|
| 'onDidChangeDirty'
|
|
92
93
|
| 'updateDiskBaseline'
|
|
93
94
|
> {
|
|
@@ -176,7 +177,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
176
177
|
} else {
|
|
177
178
|
dirty.delete(uri);
|
|
178
179
|
}
|
|
179
|
-
dirtyChanged.fire({ uri: key(uri), dirty: now });
|
|
180
|
+
dirtyChanged.fire({ uri: key(uri), text: { version: held?.version ?? 0, hash: textHash(held?.text ?? ''), dirty: now } });
|
|
180
181
|
}
|
|
181
182
|
};
|
|
182
183
|
|
|
@@ -200,6 +201,10 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
200
201
|
version(uri) {
|
|
201
202
|
return docs.get(uri)?.version ?? 0;
|
|
202
203
|
},
|
|
204
|
+
textState(uri) {
|
|
205
|
+
const held = docs.get(uri);
|
|
206
|
+
return held && { version: held.version, hash: textHash(held.text), dirty: dirty.has(uri) };
|
|
207
|
+
},
|
|
203
208
|
notifyDidChangeTextDocument(event, clientId = LANGUAGE_CLIENT_ID) {
|
|
204
209
|
const existing = docs.get(event.textDocument.uri);
|
|
205
210
|
const next: StubTextDocumentEntry = {
|