@hydranium/core 1.0.0-next.36 → 1.0.0-next.38
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 +42 -5
- package/lib/documents/ast-document-manager.d.ts.map +1 -1
- package/lib/documents/ast-document-manager.js +3 -7
- package/lib/documents/ast-document-manager.js.map +1 -1
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +29 -8
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/documents/language-client-text-shadow.d.ts +24 -1
- package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
- package/lib/documents/language-client-text-shadow.js +36 -2
- package/lib/documents/language-client-text-shadow.js.map +1 -1
- package/lib/documents/self-save-registry.d.ts +37 -6
- package/lib/documents/self-save-registry.d.ts.map +1 -1
- package/lib/documents/self-save-registry.js +28 -15
- package/lib/documents/self-save-registry.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/langium/bootstrap.js +8 -8
- package/lib/langium/bootstrap.js.map +1 -1
- package/lib/langium/document-builder/build-pipeline-integration.d.ts +13 -1
- package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
- package/lib/langium/document-builder/build-pipeline-integration.js +1 -1
- package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
- package/lib/langium/document-builder/document-builder.d.ts +2 -2
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/integration-services.d.ts +1 -1
- package/lib/langium/model-service/model-service.d.ts +64 -5
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +5 -73
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/module.d.ts +58 -16
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js +16 -16
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/residency/cst-residency-service.d.ts +30 -8
- package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
- package/lib/langium/residency/cst-residency-service.js +13 -56
- package/lib/langium/residency/cst-residency-service.js.map +1 -1
- package/lib/langium/shared-services.d.ts +3 -3
- package/lib/langium/shared-services.d.ts.map +1 -1
- package/lib/langium/transfer/transfer-encoder.d.ts +29 -1
- package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
- package/lib/langium/transfer/transfer-encoder.js +1 -1
- package/lib/langium/transfer/transfer-encoder.js.map +1 -1
- package/lib/locale/server-locale.d.ts +15 -1
- package/lib/locale/server-locale.d.ts.map +1 -1
- package/lib/locale/server-locale.js +1 -1
- package/lib/locale/server-locale.js.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.js +1 -1
- package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
- package/lib/messages/renderer.d.ts +23 -4
- package/lib/messages/renderer.d.ts.map +1 -1
- package/lib/messages/renderer.js +1 -1
- package/lib/messages/renderer.js.map +1 -1
- package/lib/testing/make-noop-shared-services.d.ts +4 -4
- package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
- package/lib/testing/make-noop-shared-services.js +4 -4
- package/lib/testing/make-noop-shared-services.js.map +1 -1
- package/lib/testing/make-test-services.d.ts +9 -9
- package/lib/testing/make-test-services.d.ts.map +1 -1
- package/lib/testing/make-test-services.js +6 -6
- package/lib/testing/make-test-services.js.map +1 -1
- package/lib/testing/stub-ast-document-manager.d.ts +3 -4
- package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
- package/lib/testing/stub-ast-document-manager.js +2 -5
- package/lib/testing/stub-ast-document-manager.js.map +1 -1
- package/lib/testing/stub-model-service.d.ts +1 -1
- package/lib/testing/stub-model-service.d.ts.map +1 -1
- package/lib/testing/stub-model-service.js +2 -2
- package/lib/testing/stub-model-service.js.map +1 -1
- package/package.json +5 -5
- package/src/documents/ast-document-manager.ts +49 -9
- package/src/documents/hydranium-text-documents.ts +29 -8
- package/src/documents/language-client-text-shadow.ts +38 -2
- package/src/documents/self-save-registry.ts +52 -13
- package/src/index.ts +4 -0
- package/src/langium/bootstrap.ts +8 -8
- package/src/langium/document-builder/build-pipeline-integration.ts +14 -1
- package/src/langium/document-builder/document-builder.ts +2 -2
- package/src/langium/integration-services.ts +1 -1
- package/src/langium/model-service/model-service.ts +78 -7
- package/src/langium/module.ts +71 -24
- package/src/langium/residency/cst-residency-service.ts +35 -11
- package/src/langium/shared-services.ts +3 -3
- package/src/langium/transfer/transfer-encoder.ts +32 -1
- package/src/locale/server-locale.ts +16 -1
- package/src/lsp/hydranium-document-update-handler.ts +1 -1
- package/src/messages/renderer.ts +24 -4
- package/src/testing/make-noop-shared-services.ts +6 -6
- package/src/testing/make-test-services.ts +15 -15
- package/src/testing/stub-ast-document-manager.ts +5 -10
- package/src/testing/stub-model-service.ts +2 -2
|
@@ -200,7 +200,74 @@ export interface ModelServiceOptions extends LogNameOptions {
|
|
|
200
200
|
* args. Constrained to {@link TransferElement}. Defaults to the
|
|
201
201
|
* structural base.
|
|
202
202
|
*/
|
|
203
|
-
|
|
203
|
+
/**
|
|
204
|
+
* The seam every non-LSP head talks to: the data server, the GLSP head and an
|
|
205
|
+
* adopter's own services reach documents through this slot rather than through
|
|
206
|
+
* the workspace stores.
|
|
207
|
+
*
|
|
208
|
+
* **Two families, and the distinction matters more than the names suggest.**
|
|
209
|
+
* `waitFor*` is a pure wait — it never triggers a build, so a caller waiting on
|
|
210
|
+
* a document no build has touched waits until something else builds it.
|
|
211
|
+
* `ensureDocumentState` and the phase shorthands over it *dispatch*: warm
|
|
212
|
+
* documents are awaited, cold ones are built.
|
|
213
|
+
*
|
|
214
|
+
* **Diagnostics are typed `never` below `Validated`.** Validation is the last
|
|
215
|
+
* phase, so at any earlier landmark the array either is not yet computed or
|
|
216
|
+
* still holds the previous build's, and reading it would take stale results
|
|
217
|
+
* for fresh ones. A caller that needs diagnostics asks for {@link validated}.
|
|
218
|
+
*
|
|
219
|
+
* What the `never` buys, exactly: reading a field off an element is a compile
|
|
220
|
+
* error, and nothing can be appended. It does NOT stop a caller assigning an
|
|
221
|
+
* element to a typed variable, because `never` is assignable to everything — so
|
|
222
|
+
* this is a guard against reaching for diagnostics by accident, not a seal
|
|
223
|
+
* against doing it deliberately.
|
|
224
|
+
*
|
|
225
|
+
* The waits resolve at or ABOVE their target, so an already-validated document
|
|
226
|
+
* does carry usable diagnostics and the `never` over-forbids there. That
|
|
227
|
+
* direction is the safe one: the alternative permits stale reads silently. A
|
|
228
|
+
* member taking a phase as a PARAMETER cannot judge statically and so returns
|
|
229
|
+
* `TDiagnostic`, leaving the choice to the caller.
|
|
230
|
+
*/
|
|
231
|
+
export interface ModelService<TAst extends AstNode, TDiagnostic = TransferDiagnostic, TTransfer extends TransferElement = TransferElement> {
|
|
232
|
+
/**
|
|
233
|
+
* Resolves once the workspace has been initialised and its first build has
|
|
234
|
+
* completed — the gate every read should wait behind, since a document
|
|
235
|
+
* queried before it may be unbuilt and reach no phase.
|
|
236
|
+
*
|
|
237
|
+
* A property rather than a method, matching `ProjectManager.ready` and
|
|
238
|
+
* Langium's `WorkspaceManager.ready`. An implementation needing a stricter
|
|
239
|
+
* gate supplies a Promise that awaits its own concern as well.
|
|
240
|
+
*/
|
|
241
|
+
readonly ready: Promise<void>;
|
|
242
|
+
|
|
243
|
+
// Pure waits — never trigger a build.
|
|
244
|
+
waitForDocumentState(uri: string, state: DocumentState, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
245
|
+
waitForDocumentSettled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
|
|
246
|
+
waitForBuilderState(state: DocumentState, cancelToken?: CancellationToken): Promise<void>;
|
|
247
|
+
|
|
248
|
+
// Wait if warm, build if cold.
|
|
249
|
+
ensureDocumentState(uri: string, state?: DocumentState, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
250
|
+
rebuild(uri: string, state?: DocumentState, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
251
|
+
parsed(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
|
|
252
|
+
linked(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
|
|
253
|
+
settled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
|
|
254
|
+
indexed(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
|
|
255
|
+
validated(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
256
|
+
|
|
257
|
+
update(args: TransferUpdateArgs<TTransfer>, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
258
|
+
save(args: TransferSaveArgs<TTransfer>, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
259
|
+
|
|
260
|
+
open(args: OpenModelArgs): Promise<Disposable>;
|
|
261
|
+
close(args: CloseModelArgs): Promise<void>;
|
|
262
|
+
isOpen(uri: string): boolean;
|
|
263
|
+
getDocument(uri: string): LangiumDocument | undefined;
|
|
264
|
+
|
|
265
|
+
onModelUpdated(uri: string, listener: (event: AstDocumentUpdatedEvent<TAst, TDiagnostic>) => void): Disposable;
|
|
266
|
+
onModelSaved(uri: string, listener: (event: AstDocumentSavedEvent<TAst, TDiagnostic>) => void): Disposable;
|
|
267
|
+
onClientClosed(uri: string, clientId: string, listener: () => void): Disposable;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class DefaultModelService<
|
|
204
271
|
TAst extends AstNode,
|
|
205
272
|
TDiagnostic = TransferDiagnostic,
|
|
206
273
|
/**
|
|
@@ -213,7 +280,7 @@ export class ModelService<
|
|
|
213
280
|
* integrity service) that pass AST roots remain compatible.
|
|
214
281
|
*/
|
|
215
282
|
TTransfer extends TransferElement = TransferElement
|
|
216
|
-
> {
|
|
283
|
+
> implements ModelService<TAst, TDiagnostic, TTransfer> {
|
|
217
284
|
protected readonly tracer: Tracer;
|
|
218
285
|
/**
|
|
219
286
|
* The single document-identity seam (`services.workspace.DocumentUriPolicy`),
|
|
@@ -347,8 +414,12 @@ export class ModelService<
|
|
|
347
414
|
* {@link waitForDocumentState} for the common "wait until content is stable"
|
|
348
415
|
* case (e.g. settling a save). Pure wait — does not trigger a build.
|
|
349
416
|
*/
|
|
350
|
-
async waitForDocumentSettled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst,
|
|
351
|
-
return this.waitForDocumentStateCanonical(
|
|
417
|
+
async waitForDocumentSettled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>> {
|
|
418
|
+
return (await this.waitForDocumentStateCanonical(
|
|
419
|
+
this.uriPolicy.canonicalUri(uri),
|
|
420
|
+
IntegrityService.SettledState,
|
|
421
|
+
cancelToken
|
|
422
|
+
)) as AstDocument<TAst, never>;
|
|
352
423
|
}
|
|
353
424
|
|
|
354
425
|
/**
|
|
@@ -863,17 +934,17 @@ export class ModelService<
|
|
|
863
934
|
*
|
|
864
935
|
* The signal is "a known client other than the language client authored this
|
|
865
936
|
* version **and** the URI was in the last build's changed set
|
|
866
|
-
* (`
|
|
937
|
+
* (`isTriggeringEdit`)". A framework-internal rebuild reports no author
|
|
867
938
|
* (`getAuthor` → `undefined`), so it fails `hasKnownAuthor` without comparing
|
|
868
939
|
* against a sentinel. This is NOT redundant with content/registration — it
|
|
869
940
|
* distinguishes "client edited" from "framework rebuilt", which neither the
|
|
870
|
-
* shadow nor `
|
|
941
|
+
* shadow nor `isTriggeringEdit` alone can.
|
|
871
942
|
*/
|
|
872
943
|
protected isNonLanguageClientEdit(document: LangiumDocument): boolean {
|
|
873
944
|
const documents = this.services.workspace.AstDocumentManager;
|
|
874
945
|
const author = documents.getAuthor(document);
|
|
875
946
|
const hasKnownAuthor = !!author && author !== LANGUAGE_CLIENT_ID;
|
|
876
|
-
return hasKnownAuthor && documents.
|
|
947
|
+
return hasKnownAuthor && documents.isTriggeringEdit(document.textDocument.uri);
|
|
877
948
|
}
|
|
878
949
|
|
|
879
950
|
/**
|
package/src/langium/module.ts
CHANGED
|
@@ -7,23 +7,23 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { type Clock, type Logger, type Project, type Tracer,
|
|
11
|
-
import { ServerLocale } from '../locale/server-locale.js';
|
|
12
|
-
import {
|
|
10
|
+
import { type Clock, type Logger, type Project, type Tracer, NoopLogger, SystemClock } from '@hydranium/protocol';
|
|
11
|
+
import { DefaultServerLocale, type ServerLocale } from '../locale/server-locale.js';
|
|
12
|
+
import { DefaultMessageRenderer, type MessageRenderer } from '../messages/renderer.js';
|
|
13
13
|
import { ServerTracer } from './diagnostics/server-tracer.js';
|
|
14
14
|
import { HydraniumLangiumProfiler } from './diagnostics/hydranium-langium-profiler.js';
|
|
15
15
|
import { type AstNode, type Module } from '@hydranium/langium';
|
|
16
16
|
import { type DefaultSharedModuleContext, type LangiumSharedServices, type PartialLangiumSharedServices } from '@hydranium/langium/lsp';
|
|
17
17
|
import { type TextDocument } from 'vscode-languageserver-textdocument';
|
|
18
|
-
import { ModelService } from './model-service/model-service.js';
|
|
18
|
+
import { DefaultModelService, type ModelService } from './model-service/model-service.js';
|
|
19
19
|
import { type ProjectManager } from './project/project-manager.js';
|
|
20
20
|
import { SingleProjectManager } from './project/single-project-manager.js';
|
|
21
|
-
import { TransferEncoder } from './transfer/transfer-encoder.js';
|
|
21
|
+
import { DefaultTransferEncoder, type TransferEncoder } from './transfer/transfer-encoder.js';
|
|
22
22
|
import { HydraniumDocumentBuilder } from './document-builder/document-builder.js';
|
|
23
|
-
import { BuildPipelineIntegration } from './document-builder/build-pipeline-integration.js';
|
|
23
|
+
import { DefaultBuildPipelineIntegration, type BuildPipelineIntegration } from './document-builder/build-pipeline-integration.js';
|
|
24
24
|
import { type BuildPhasePassContribution } from './build-phase-pass/build-phase-pass.js';
|
|
25
25
|
import { type BuildPhasePassService, DefaultBuildPhasePassService } from './build-phase-pass/build-phase-pass-service.js';
|
|
26
|
-
import { CstResidencyService } from './residency/index.js';
|
|
26
|
+
import { DefaultCstResidencyService, type CstResidencyService } from './residency/index.js';
|
|
27
27
|
import { HydraniumIndexManager } from './workspace/index-manager.js';
|
|
28
28
|
import { HydraniumWorkspaceManager } from './workspace/hydranium-workspace-manager.js';
|
|
29
29
|
import { HydraniumWorkspaceLock } from './workspace/hydranium-workspace-lock.js';
|
|
@@ -32,8 +32,8 @@ import { type HydraniumDocumentRegistry, HydraniumLangiumDocuments } from './wor
|
|
|
32
32
|
import { type AdditionalDocumentContribution } from './workspace/additional-document-contribution.js';
|
|
33
33
|
import { DefaultDocumentUriPolicy, type DocumentUriPolicy } from './workspace/document-uri-policy.js';
|
|
34
34
|
import { HydraniumTextDocuments } from '../documents/hydranium-text-documents.js';
|
|
35
|
-
import { AstDocumentManager, type WritableFileSystemProvider } from '../documents/ast-document-manager.js';
|
|
36
|
-
import { SelfSaveRegistry } from '../documents/self-save-registry.js';
|
|
35
|
+
import { DefaultAstDocumentManager, type AstDocumentManager, type WritableFileSystemProvider } from '../documents/ast-document-manager.js';
|
|
36
|
+
import { DefaultSelfSaveRegistry, type SelfSaveRegistry } from '../documents/self-save-registry.js';
|
|
37
37
|
import { DefaultEmptyFileSystemProvider } from './workspace/file-system-provider.js';
|
|
38
38
|
import { type ServerLanguageServices } from './language-module.js';
|
|
39
39
|
import { ExtendedServiceRegistry } from './service-registry.js';
|
|
@@ -105,7 +105,7 @@ export interface ServerAddedSharedServices<TProject extends Project = Project> {
|
|
|
105
105
|
* returns every sentence unchanged. Adopters subclass and override
|
|
106
106
|
* `translationsFor`.
|
|
107
107
|
*/
|
|
108
|
-
MessageRenderer:
|
|
108
|
+
MessageRenderer: MessageRenderer;
|
|
109
109
|
/**
|
|
110
110
|
* The locale an init handed the server, for whoever renders in it. Held
|
|
111
111
|
* apart from the renderer so replacing the renderer cannot drop locale
|
|
@@ -255,14 +255,22 @@ export interface ServerAddedSharedServices<TProject extends Project = Project> {
|
|
|
255
255
|
* heads read both slots from shared services rather than constructor
|
|
256
256
|
* arguments.
|
|
257
257
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
258
|
+
* `TransferEncoder` is the INTERFACE, not `DefaultTransferEncoder` — an
|
|
259
|
+
* adopter can therefore REPLACE this declaration (see
|
|
260
|
+
* {@link WithServiceOverrides}) rather than intersect with it, which is what
|
|
261
|
+
* keeps slot resolution independent of the order a services type is written
|
|
262
|
+
* in. `ModelService` is still a class and does not yet have that property.
|
|
263
|
+
*
|
|
264
|
+
* A class in a slot costs two things, both measured. Its `protected` members
|
|
265
|
+
* join every assignability check and are compared NOMINALLY, so a subclass
|
|
266
|
+
* declared against a second physical copy of this package cannot satisfy it.
|
|
267
|
+
* And where the class is generic over a map reached through `keyof`, that
|
|
268
|
+
* parameter is measured INVARIANT — instantiations then relate only when
|
|
269
|
+
* their arguments are mutually assignable, which no adopter map is with the
|
|
270
|
+
* framework's. Neither survives on an interface.
|
|
263
271
|
*/
|
|
264
272
|
model: {
|
|
265
|
-
TransferEncoder: TransferEncoder
|
|
273
|
+
TransferEncoder: TransferEncoder;
|
|
266
274
|
ModelService: ModelService<AstNode, unknown>;
|
|
267
275
|
};
|
|
268
276
|
/**
|
|
@@ -306,6 +314,45 @@ export type ServerSharedServices<TProject extends Project = Project> = Omit<Lang
|
|
|
306
314
|
workspace: Omit<LangiumSharedServices['workspace'], 'TextDocuments' | 'DocumentBuilder'>;
|
|
307
315
|
} & ServerAddedSharedServices<TProject>;
|
|
308
316
|
|
|
317
|
+
/**
|
|
318
|
+
* The service-tree namespaces — the keys whose value groups further slots
|
|
319
|
+
* rather than being a slot itself.
|
|
320
|
+
*
|
|
321
|
+
* Consumed by {@link WithServiceOverrides} to know where to merge one level
|
|
322
|
+
* deeper. **Add a namespace to {@link ServerAddedSharedServices} and you must
|
|
323
|
+
* add it here**, and the failure of forgetting is silent in the direction that
|
|
324
|
+
* matters: an unlisted namespace is treated as a leaf, so an adopter overriding
|
|
325
|
+
* one slot inside it replaces the WHOLE namespace and loses the sibling slots
|
|
326
|
+
* with no diagnostic. The reverse mistake — listing a leaf — fails loudly.
|
|
327
|
+
*/
|
|
328
|
+
type ServiceNamespace = 'lsp' | 'workspace' | 'model';
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Compose an adopter's service tree so its declarations REPLACE the framework's
|
|
332
|
+
* rather than intersecting with them.
|
|
333
|
+
*
|
|
334
|
+
* Langium composes services by intersection, which accumulates: two
|
|
335
|
+
* declarations of one slot survive as an overload set, and which one a call
|
|
336
|
+
* resolves to depends on the order the intersection was written in — silently,
|
|
337
|
+
* with no diagnostic at the point a reorder changes it. There is no override
|
|
338
|
+
* operator for intersections, so the framework's declaration has to be removed
|
|
339
|
+
* before the adopter's is added. This does that, one level deep for each
|
|
340
|
+
* {@link ServiceNamespace}, so a narrowed slot replaces its framework twin
|
|
341
|
+
* while its siblings survive.
|
|
342
|
+
*
|
|
343
|
+
* Only slots typed as INTERFACES can be replaced this way. A class-typed slot
|
|
344
|
+
* drags its `protected` members into the assignability check — compared
|
|
345
|
+
* nominally — so an adopter subclass does not satisfy it and the framework's
|
|
346
|
+
* declaration cannot be dropped.
|
|
347
|
+
*/
|
|
348
|
+
export type WithServiceOverrides<TBase, TOverrides> = Omit<TBase, keyof TOverrides> & {
|
|
349
|
+
[K in keyof TOverrides]: K extends ServiceNamespace
|
|
350
|
+
? K extends keyof TBase
|
|
351
|
+
? Omit<TBase[K], keyof TOverrides[K]> & TOverrides[K]
|
|
352
|
+
: TOverrides[K]
|
|
353
|
+
: TOverrides[K];
|
|
354
|
+
};
|
|
355
|
+
|
|
309
356
|
/**
|
|
310
357
|
* Construction context for {@link createServerSharedModule}.
|
|
311
358
|
*
|
|
@@ -354,8 +401,8 @@ export function createServerSharedModule(
|
|
|
354
401
|
ServiceRegistry: services => new ExtendedServiceRegistry<ServerLanguageServices>(services),
|
|
355
402
|
Logger: () => new NoopLogger(),
|
|
356
403
|
Tracer: services => new ServerTracer(services.Logger, services.Clock),
|
|
357
|
-
ServerLocale: services => new
|
|
358
|
-
MessageRenderer: services => new
|
|
404
|
+
ServerLocale: services => new DefaultServerLocale(services),
|
|
405
|
+
MessageRenderer: services => new DefaultMessageRenderer(services),
|
|
359
406
|
// Bind Langium's per-grammar-rule / per-`$type` parse/link/validate
|
|
360
407
|
// profiler (the data the framework's own `Tracer`/`ProfileSession` passes
|
|
361
408
|
// cannot produce), routed through our `Logger` and debug-gated. At the
|
|
@@ -430,7 +477,7 @@ export function createServerSharedModule(
|
|
|
430
477
|
// scope tracker — `@hydranium/core/node` does.
|
|
431
478
|
WorkspaceLock: () => new HydraniumWorkspaceLock(),
|
|
432
479
|
ProjectManager: services => new SingleProjectManager(services),
|
|
433
|
-
SelfSaveRegistry: services => new
|
|
480
|
+
SelfSaveRegistry: services => new DefaultSelfSaveRegistry(services),
|
|
434
481
|
DocumentUriPolicy: () => new DefaultDocumentUriPolicy(),
|
|
435
482
|
// Writable filesystem with two paths:
|
|
436
483
|
// - Adopter passed `context.fileSystemProvider` and it returned a
|
|
@@ -454,20 +501,20 @@ export function createServerSharedModule(
|
|
|
454
501
|
}
|
|
455
502
|
return new DefaultEmptyFileSystemProvider(services);
|
|
456
503
|
},
|
|
457
|
-
AstDocumentManager: services => new
|
|
504
|
+
AstDocumentManager: services => new DefaultAstDocumentManager(services),
|
|
458
505
|
// Eagerly constructed, so its build-phase listeners attach before the
|
|
459
506
|
// first build.
|
|
460
|
-
BuildPipelineIntegration: services => new
|
|
507
|
+
BuildPipelineIntegration: services => new DefaultBuildPipelineIntegration(services),
|
|
461
508
|
BuildPhasePassService: services => new DefaultBuildPhasePassService(services),
|
|
462
509
|
// Eagerly constructed, so its `Validated` pass registers before the
|
|
463
510
|
// first build.
|
|
464
|
-
CstResidencyService: services => new
|
|
511
|
+
CstResidencyService: services => new DefaultCstResidencyService(services)
|
|
465
512
|
},
|
|
466
513
|
model: {
|
|
467
514
|
// Generic walker — adopters with a typed `$type → wire shape` overlay
|
|
468
515
|
// rebind this slot with a subclass.
|
|
469
|
-
TransferEncoder: services => new
|
|
470
|
-
ModelService: services => new
|
|
516
|
+
TransferEncoder: services => new DefaultTransferEncoder(services),
|
|
517
|
+
ModelService: services => new DefaultModelService(services)
|
|
471
518
|
},
|
|
472
519
|
// Empty default so `services.buildPhasePasses` always resolves (Langium
|
|
473
520
|
// throws on access to an unbound slot). The framework's own integrity /
|
|
@@ -24,9 +24,13 @@ import { type ServerSharedServicesMinimal } from '../shared-services.js';
|
|
|
24
24
|
/**
|
|
25
25
|
* Default priority of the CST-residency pass. Deliberately very high so it runs
|
|
26
26
|
* *last* among `Validated` build-phase passes (the registry iterates priority
|
|
27
|
-
* ascending)
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* ascending).
|
|
28
|
+
*
|
|
29
|
+
* The ordering is defensive, not load-bearing: the pass only arms or cancels an
|
|
30
|
+
* idle timer, so every pass at `Validated` reads a resident CST whichever order
|
|
31
|
+
* they run in, and lowering it changes nothing observable while that holds. It
|
|
32
|
+
* becomes load-bearing the moment the shed stops being deferred — a pass that
|
|
33
|
+
* frees the CST synchronously has to run after every reader of it.
|
|
30
34
|
*/
|
|
31
35
|
export const CST_RESIDENCY_PASS_PRIORITY = 1_000_000;
|
|
32
36
|
|
|
@@ -227,7 +231,27 @@ function rehydrateCst(document: LangiumDocument, factory: LangiumDocumentFactory
|
|
|
227
231
|
* `NameProvider.getNameNode` chokepoint, the comment provider, and
|
|
228
232
|
* `HydraniumLangiumDocuments.getOrCreateDocument`.
|
|
229
233
|
*/
|
|
230
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Keeps a document's CST available to the readers that need one, shedding it
|
|
236
|
+
* for documents that have gone idle and re-grafting on demand.
|
|
237
|
+
*/
|
|
238
|
+
export interface CstResidencyService {
|
|
239
|
+
/**
|
|
240
|
+
* Ensure `document` has a CST, re-parsing its retained text if it was shed.
|
|
241
|
+
* A resident document is left exactly as it is, so this is safe to call on
|
|
242
|
+
* a hot path and safe to call twice.
|
|
243
|
+
*/
|
|
244
|
+
rehydrate(document: LangiumDocument): void;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Node-level convenience over {@link rehydrate}, cheap enough for an
|
|
248
|
+
* unconditional call: a node whose `$cstNode` is already set returns
|
|
249
|
+
* without resolving its document.
|
|
250
|
+
*/
|
|
251
|
+
rehydrateNode(node: AstNode): void;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export class DefaultCstResidencyService implements CstResidencyService {
|
|
231
255
|
protected readonly strategy: CstResidencyStrategy;
|
|
232
256
|
/** Bytes-per-node for the optional reclaim estimate in traces; `0` disables it. */
|
|
233
257
|
protected readonly estimatedBytesPerShedNode: number;
|
|
@@ -277,8 +301,8 @@ export class CstResidencyService {
|
|
|
277
301
|
* until navigation latency on shed files is actually observed to hurt. This
|
|
278
302
|
* method is where that hook goes.
|
|
279
303
|
*/
|
|
280
|
-
rehydrate(document: LangiumDocument):
|
|
281
|
-
|
|
304
|
+
rehydrate(document: LangiumDocument): void {
|
|
305
|
+
rehydrateCst(document, this.services.workspace.LangiumDocumentFactory);
|
|
282
306
|
}
|
|
283
307
|
|
|
284
308
|
/**
|
|
@@ -287,11 +311,11 @@ export class CstResidencyService {
|
|
|
287
311
|
* `true` without even resolving the document, so hot read paths
|
|
288
312
|
* (`getNameNode`, comment lookup) call this unconditionally.
|
|
289
313
|
*/
|
|
290
|
-
rehydrateNode(node: AstNode):
|
|
314
|
+
rehydrateNode(node: AstNode): void {
|
|
291
315
|
if (node.$cstNode !== undefined) {
|
|
292
|
-
return
|
|
316
|
+
return;
|
|
293
317
|
}
|
|
294
|
-
|
|
318
|
+
this.rehydrate(AstUtils.getDocument(node));
|
|
295
319
|
}
|
|
296
320
|
|
|
297
321
|
/**
|
|
@@ -353,8 +377,8 @@ export class CstResidencyService {
|
|
|
353
377
|
this.shedTotal += this.shed(document);
|
|
354
378
|
}
|
|
355
379
|
|
|
356
|
-
/** Cancel
|
|
357
|
-
|
|
380
|
+
/** Cancel every armed shed, so a document that has gone idle keeps its CST. */
|
|
381
|
+
cancelPendingShed(): void {
|
|
358
382
|
for (const timer of this.idleTimers.values()) {
|
|
359
383
|
timer.dispose();
|
|
360
384
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { Clock, Logger, Project, Tracer } from '@hydranium/protocol';
|
|
11
11
|
import type { LangiumSharedCoreServices } from '@hydranium/langium';
|
|
12
12
|
import type { ServerLocale } from '../locale/server-locale.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type { MessageRenderer } from '../messages/renderer.js';
|
|
14
14
|
import type { SelfSaveRegistry } from '../documents/self-save-registry.js';
|
|
15
15
|
import type { WritableFileSystemProvider } from '../documents/ast-document-manager.js';
|
|
16
16
|
import type { HydraniumDocumentRegistry } from './workspace/langium-documents.js';
|
|
@@ -31,7 +31,7 @@ import type { AdditionalDocumentContribution } from './workspace/additional-docu
|
|
|
31
31
|
* {@link Clock}, {@link Logger} and {@link Tracer} top-level slots (injectable
|
|
32
32
|
* time source, emission-only logger, and the measure-and-emit tracer composed
|
|
33
33
|
* from the other two), the {@link AdditionalDocumentContribution} group, the
|
|
34
|
-
* {@link
|
|
34
|
+
* {@link MessageRenderer} and the {@link ServerLocale} it renders in,
|
|
35
35
|
* an {@link ExtendedServiceRegistry}, and on `workspace` a writable file-system
|
|
36
36
|
* provider plus {@link HydraniumWorkspaceManager}, {@link ProjectManager},
|
|
37
37
|
* {@link SelfSaveRegistry}, {@link BuildPipelineIntegration},
|
|
@@ -66,7 +66,7 @@ export interface ServerSharedServicesMinimal<TProject extends Project = Project>
|
|
|
66
66
|
* shared tier with no workspace dependency, because all three heads'
|
|
67
67
|
* messages pass through it.
|
|
68
68
|
*/
|
|
69
|
-
MessageRenderer:
|
|
69
|
+
MessageRenderer: MessageRenderer;
|
|
70
70
|
/** The locale an init handed the server, for whoever renders in it. */
|
|
71
71
|
ServerLocale: ServerLocale;
|
|
72
72
|
/**
|
|
@@ -80,6 +80,37 @@ export interface TransferEnvelopeSource<TDiagnostic> {
|
|
|
80
80
|
readonly diagnostics: readonly (TDiagnostic | Diagnostic)[];
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The AST→transfer contract a DI slot holds, implemented by
|
|
85
|
+
* {@link DefaultTransferEncoder}.
|
|
86
|
+
*
|
|
87
|
+
* **This is an interface and not the class, and that is load-bearing.** A class
|
|
88
|
+
* in a slot drags its `protected` members into every assignability check, which
|
|
89
|
+
* TypeScript compares NOMINALLY — so the slot type could not be satisfied by a
|
|
90
|
+
* subclass declared against a second physical copy of this package, and an
|
|
91
|
+
* adopter narrowing the slot had to intersect with the framework's declaration
|
|
92
|
+
* rather than replace it, leaving method resolution dependent on declaration
|
|
93
|
+
* order. An interface has no protected members and is compared structurally, so
|
|
94
|
+
* neither applies.
|
|
95
|
+
*
|
|
96
|
+
* **The transfer returns are the structural base ({@link TransferElement}), not
|
|
97
|
+
* the per-grammar overlay.** The overlay cannot parameterise this contract:
|
|
98
|
+
* `TransferTypeFor` reaches `keyof TTransferMap`, which makes that parameter
|
|
99
|
+
* measured-INVARIANT, and invariant parameters relate only when their arguments
|
|
100
|
+
* are mutually assignable — so a map-parameterised contract admits no adopter
|
|
101
|
+
* instantiation at all. Heads read this contract and do not know adopter maps;
|
|
102
|
+
* an adopter recovers the precise type by declaring its own slot, which it can
|
|
103
|
+
* now do by replacement.
|
|
104
|
+
*/
|
|
105
|
+
export interface TransferEncoder<TDiagnostic extends TransferDiagnostic = TransferDiagnostic> {
|
|
106
|
+
toTransfer<T extends AstNode>(ast: T, mode?: TransferMode): TransferElement;
|
|
107
|
+
toTransferDocument(langiumDocument: LangiumDocument): TransferDocument<TransferElement, TDiagnostic>;
|
|
108
|
+
astDocumentToTransferDocument<TAst extends AstNode>(
|
|
109
|
+
document: AstDocument<TAst, TDiagnostic | TransferLspDiagnostic>
|
|
110
|
+
): TransferDocument<TransferElement, TDiagnostic>;
|
|
111
|
+
toTransferDiagnostic(diagnostic: TransferLspDiagnostic): TDiagnostic;
|
|
112
|
+
}
|
|
113
|
+
|
|
83
114
|
/**
|
|
84
115
|
* One-directional structural mapper from a Langium AST into the wire
|
|
85
116
|
* transfer-model shape ({@link TransferElement}-based JSON).
|
|
@@ -124,7 +155,7 @@ export interface TransferEnvelopeSource<TDiagnostic> {
|
|
|
124
155
|
* - {@link toTransferDiagnostic} — project a {@link TransferLspDiagnostic}
|
|
125
156
|
* (the framework validator's output) to {@link TDiagnostic}.
|
|
126
157
|
*/
|
|
127
|
-
export class
|
|
158
|
+
export class DefaultTransferEncoder<
|
|
128
159
|
// The map is intentionally unconstrained — adopters supply typed
|
|
129
160
|
// overlays whose specific-key shapes don't structurally satisfy a
|
|
130
161
|
// `Record<string, TransferElement>` index signature. `TransferTypeFor`
|
|
@@ -14,6 +14,21 @@ import type { ServerSharedServicesMinimal } from '../langium/shared-services.js'
|
|
|
14
14
|
/** Construction options for {@link ServerLocale}. */
|
|
15
15
|
export type ServerLocaleOptions = LogNameOptions;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The locale contract the `ServerLocale` slot holds, implemented by
|
|
19
|
+
* {@link DefaultServerLocale}.
|
|
20
|
+
*
|
|
21
|
+
* An interface rather than the class, so the slot is compared STRUCTURALLY —
|
|
22
|
+
* a class-typed slot carries its `protected` members into every assignability
|
|
23
|
+
* check, compared nominally, which makes it unsatisfiable across two physical
|
|
24
|
+
* copies of this package and unreplaceable by an adopter's own declaration.
|
|
25
|
+
*/
|
|
26
|
+
export interface ServerLocale {
|
|
27
|
+
/** The locale, or `undefined` when no init supplied one — the framework's English. */
|
|
28
|
+
readonly value: string | undefined;
|
|
29
|
+
accept(locale: string): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
/**
|
|
18
33
|
* The locale the server was handed at init, for whoever needs to render in the
|
|
19
34
|
* reading user's language. Held apart from the message renderer that reads it,
|
|
@@ -23,7 +38,7 @@ export type ServerLocaleOptions = LogNameOptions;
|
|
|
23
38
|
* read the current value wraps this itself — nothing does today, and both real
|
|
24
39
|
* hosts respawn the server on a display-language switch.
|
|
25
40
|
*/
|
|
26
|
-
export class ServerLocale {
|
|
41
|
+
export class DefaultServerLocale implements ServerLocale {
|
|
27
42
|
protected readonly tracer: Tracer;
|
|
28
43
|
protected current: string | undefined;
|
|
29
44
|
|
|
@@ -257,7 +257,7 @@ export class HydraniumDocumentUpdateHandler extends DefaultDocumentUpdateHandler
|
|
|
257
257
|
}
|
|
258
258
|
const uri = UriUtils.toUri(change.uri);
|
|
259
259
|
const mtimeMs = await this.fileSystemProvider.mtimeMs?.(uri);
|
|
260
|
-
if (mtimeMs !== undefined && this.selfSaveRegistry.
|
|
260
|
+
if (mtimeMs !== undefined && this.selfSaveRegistry.isRegistered(uri.fsPath, mtimeMs)) {
|
|
261
261
|
return undefined;
|
|
262
262
|
}
|
|
263
263
|
return change;
|
package/src/messages/renderer.ts
CHANGED
|
@@ -25,8 +25,28 @@ import type { ServerSharedServicesMinimal } from '../langium/shared-services.js'
|
|
|
25
25
|
import type { ServerLocale } from '../locale/server-locale.js';
|
|
26
26
|
import { resolvedFromDiagnostic } from './carriers.js';
|
|
27
27
|
|
|
28
|
-
/** Construction options for {@link
|
|
29
|
-
export type
|
|
28
|
+
/** Construction options for {@link DefaultMessageRenderer}. */
|
|
29
|
+
export type MessageRendererOptions = LogNameOptions;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The render contract the `MessageRenderer` slot holds, implemented by
|
|
33
|
+
* {@link DefaultMessageRenderer}.
|
|
34
|
+
*
|
|
35
|
+
* An interface rather than the class, so the slot is compared STRUCTURALLY. A
|
|
36
|
+
* class-typed slot carries its `protected` members into every assignability
|
|
37
|
+
* check and TypeScript compares those nominally — which makes the slot
|
|
38
|
+
* unsatisfiable by a subclass declared against a second physical copy of this
|
|
39
|
+
* package, the state a pre-publish `yalc` / `file:` install produces. It also
|
|
40
|
+
* lets an adopter REPLACE this declaration rather than intersect with it, so
|
|
41
|
+
* slot resolution does not depend on the order a services type is written in.
|
|
42
|
+
*
|
|
43
|
+
* Every method carries a no-throw contract; see the implementation.
|
|
44
|
+
*/
|
|
45
|
+
export interface MessageRenderer {
|
|
46
|
+
renderDiagnostic(diagnostic: Diagnostic): string;
|
|
47
|
+
renderError(error: ResponseError<unknown>): string;
|
|
48
|
+
renderMessage<S extends string>(message: MessageDefinition<S>, ...params: ParamsArg<S>): string;
|
|
49
|
+
}
|
|
30
50
|
|
|
31
51
|
/**
|
|
32
52
|
* Renders every user-facing message the server sends, in the locale the server
|
|
@@ -41,7 +61,7 @@ export type ServerMessageRendererOptions = LogNameOptions;
|
|
|
41
61
|
* single `render(text)` would force an adopter to match English prose, which
|
|
42
62
|
* breaks on the first Langium reword.
|
|
43
63
|
*/
|
|
44
|
-
export class
|
|
64
|
+
export class DefaultMessageRenderer implements MessageRenderer {
|
|
45
65
|
protected readonly tracer: Tracer;
|
|
46
66
|
protected readonly serverLocale: ServerLocale;
|
|
47
67
|
/**
|
|
@@ -58,7 +78,7 @@ export class ServerMessageRenderer {
|
|
|
58
78
|
*/
|
|
59
79
|
protected readonly catalogues = new SimpleCache<string | undefined, Record<string, string> | undefined>();
|
|
60
80
|
|
|
61
|
-
constructor(services: ServerSharedServicesMinimal, options:
|
|
81
|
+
constructor(services: ServerSharedServicesMinimal, options: MessageRendererOptions = {}) {
|
|
62
82
|
this.serverLocale = services.ServerLocale;
|
|
63
83
|
this.tracer = services.Tracer.for(options.logName ?? 'MessageRenderer').trace('instantiated');
|
|
64
84
|
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { type Clock, DefaultTracer, type Logger, NoopLogger, SystemClock, type Tracer } from '@hydranium/protocol';
|
|
11
11
|
import type { ServerSharedServicesMinimal } from '../langium/shared-services.js';
|
|
12
|
-
import { ServerLocale } from '../locale/server-locale.js';
|
|
13
|
-
import {
|
|
12
|
+
import { DefaultServerLocale, type ServerLocale } from '../locale/server-locale.js';
|
|
13
|
+
import { DefaultMessageRenderer, type MessageRenderer } from '../messages/renderer.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Overrides for {@link makeNoopSharedServices}. The three observability slots
|
|
@@ -35,14 +35,14 @@ export interface NoopSharedServicesOverrides {
|
|
|
35
35
|
ServerLocale?: (services: ServerSharedServicesMinimal) => ServerLocale;
|
|
36
36
|
/**
|
|
37
37
|
* Factory for the `MessageRenderer` slot. Default: the real
|
|
38
|
-
* {@link
|
|
38
|
+
* {@link DefaultMessageRenderer}, whose no-catalogue behaviour is a
|
|
39
39
|
* pass-through — so the default is the framework's own behaviour, not a stub.
|
|
40
40
|
*
|
|
41
41
|
* A factory rather than an instance, because a renderer reads the tree it is
|
|
42
42
|
* bound into. Both slots are resolved lazily, so an override is honoured
|
|
43
43
|
* however late the caller reads them.
|
|
44
44
|
*/
|
|
45
|
-
MessageRenderer?: (services: ServerSharedServicesMinimal) =>
|
|
45
|
+
MessageRenderer?: (services: ServerSharedServicesMinimal) => MessageRenderer;
|
|
46
46
|
/** Per-slot `workspace` overrides. Slots left out resolve to `undefined`. */
|
|
47
47
|
workspace?: Record<string, unknown>;
|
|
48
48
|
/** Any other minimal slot (`ServiceRegistry`, `AstReflection`, `additionalDocuments`, …). */
|
|
@@ -103,8 +103,8 @@ export function makeNoopSharedServices<T extends ServerSharedServicesMinimal = S
|
|
|
103
103
|
// both services emit an `instantiated` trace, so building them eagerly puts
|
|
104
104
|
// two lines into the capture of every test that passes a capturing logger
|
|
105
105
|
// and asserts on emptiness.
|
|
106
|
-
defineLazySlot(services, 'ServerLocale', () => serverLocale?.(services) ?? new
|
|
107
|
-
defineLazySlot(services, 'MessageRenderer', () => messageRenderer?.(services) ?? new
|
|
106
|
+
defineLazySlot(services, 'ServerLocale', () => serverLocale?.(services) ?? new DefaultServerLocale(services));
|
|
107
|
+
defineLazySlot(services, 'MessageRenderer', () => messageRenderer?.(services) ?? new DefaultMessageRenderer(services));
|
|
108
108
|
return services;
|
|
109
109
|
}
|
|
110
110
|
|