@hydranium/core 1.0.0-next.6 → 1.0.0-next.60
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 +18 -13
- package/lib/documents/ast-document-manager.js.map +1 -1
- package/lib/documents/hydranium-text-documents.d.ts +42 -17
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +118 -39
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/documents/language-client-text-shadow.d.ts +41 -9
- package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
- package/lib/documents/language-client-text-shadow.js +56 -11
- 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 +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -1
- package/lib/langium/ast-extension/ast-node-builder.d.ts +32 -9
- package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
- package/lib/langium/ast-extension/ast-node-builder.js +60 -22
- package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
- package/lib/langium/bootstrap.d.ts.map +1 -1
- package/lib/langium/bootstrap.js +16 -9
- package/lib/langium/bootstrap.js.map +1 -1
- package/lib/langium/config/configuration-provider.d.ts +61 -0
- package/lib/langium/config/configuration-provider.d.ts.map +1 -0
- package/lib/langium/config/configuration-provider.js +89 -0
- package/lib/langium/config/configuration-provider.js.map +1 -0
- package/lib/langium/config/index.d.ts +1 -0
- package/lib/langium/config/index.d.ts.map +1 -1
- package/lib/langium/config/index.js +1 -0
- package/lib/langium/config/index.js.map +1 -1
- package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
- package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
- package/lib/langium/diagnostics/lsp-logger.js +40 -6
- package/lib/langium/diagnostics/lsp-logger.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/build-session.d.ts +93 -0
- package/lib/langium/document-builder/build-session.d.ts.map +1 -0
- package/lib/langium/document-builder/build-session.js +72 -0
- package/lib/langium/document-builder/build-session.js.map +1 -0
- package/lib/langium/document-builder/document-builder.d.ts +150 -7
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +298 -11
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/document-builder/index.d.ts +1 -0
- package/lib/langium/document-builder/index.d.ts.map +1 -1
- package/lib/langium/document-builder/index.js +1 -0
- package/lib/langium/document-builder/index.js.map +1 -1
- package/lib/langium/integration-services.d.ts +45 -1
- package/lib/langium/integration-services.d.ts.map +1 -1
- package/lib/langium/integration-services.js +10 -5
- package/lib/langium/integration-services.js.map +1 -1
- package/lib/langium/integrity/integrity-service.d.ts +5 -0
- package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-service.js +34 -1
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/keys/containment.d.ts +64 -0
- package/lib/langium/keys/containment.d.ts.map +1 -0
- package/lib/langium/keys/containment.js +53 -0
- package/lib/langium/keys/containment.js.map +1 -0
- package/lib/langium/keys/index.d.ts +1 -0
- package/lib/langium/keys/index.d.ts.map +1 -1
- package/lib/langium/keys/index.js +1 -0
- package/lib/langium/keys/index.js.map +1 -1
- package/lib/langium/keys/name-based-key-provider.d.ts +4 -0
- package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
- package/lib/langium/keys/name-based-key-provider.js +4 -0
- package/lib/langium/keys/name-based-key-provider.js.map +1 -1
- package/lib/langium/model-service/model-service.d.ts +103 -7
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +70 -89
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/module.d.ts +82 -13
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js +26 -12
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/naming/name-provider.d.ts +10 -0
- package/lib/langium/naming/name-provider.d.ts.map +1 -1
- package/lib/langium/naming/name-provider.js.map +1 -1
- package/lib/langium/naming/name-separator-validation.d.ts +23 -0
- package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
- package/lib/langium/naming/name-separator-validation.js +30 -1
- package/lib/langium/naming/name-separator-validation.js.map +1 -1
- package/lib/langium/residency/cst-residency-service.d.ts +31 -9
- 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/scope/hydranium-scope-provider.d.ts +32 -13
- package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
- package/lib/langium/scope/hydranium-scope-provider.js +37 -20
- package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
- package/lib/langium/shared-services.d.ts +15 -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 +33 -2
- package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
- package/lib/langium/transfer/transfer-encoder.js +14 -3
- package/lib/langium/transfer/transfer-encoder.js.map +1 -1
- package/lib/langium/validation/document-validator.d.ts +145 -6
- package/lib/langium/validation/document-validator.d.ts.map +1 -1
- package/lib/langium/validation/document-validator.js +211 -4
- package/lib/langium/validation/document-validator.js.map +1 -1
- package/lib/langium/workspace/document-uri-policy.d.ts +3 -4
- package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
- package/lib/langium/workspace/document-uri-policy.js +3 -4
- package/lib/langium/workspace/document-uri-policy.js.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +25 -0
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.js +25 -0
- package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-manager.d.ts +21 -1
- package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-manager.js +28 -0
- package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
- package/lib/langium/workspace/in-memory-file-system-provider.d.ts +8 -0
- package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
- package/lib/langium/workspace/in-memory-file-system-provider.js +11 -2
- package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
- package/lib/langium/workspace/initialize-workspace.d.ts +22 -2
- package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
- package/lib/langium/workspace/initialize-workspace.js +13 -5
- package/lib/langium/workspace/initialize-workspace.js.map +1 -1
- package/lib/langium/workspace/langium-documents.d.ts +79 -17
- package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
- package/lib/langium/workspace/langium-documents.js +83 -28
- package/lib/langium/workspace/langium-documents.js.map +1 -1
- package/lib/locale/index.d.ts +10 -0
- package/lib/locale/index.d.ts.map +1 -0
- package/lib/locale/index.js +10 -0
- package/lib/locale/index.js.map +1 -0
- package/lib/locale/server-locale.d.ts +73 -0
- package/lib/locale/server-locale.d.ts.map +1 -0
- package/lib/locale/server-locale.js +61 -0
- package/lib/locale/server-locale.js.map +1 -0
- package/lib/lsp/hydranium-document-update-handler.js +1 -1
- package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
- package/lib/messages/carriers.d.ts +54 -0
- package/lib/messages/carriers.d.ts.map +1 -0
- package/lib/messages/carriers.js +62 -0
- package/lib/messages/carriers.js.map +1 -0
- package/lib/messages/index.d.ts +25 -0
- package/lib/messages/index.d.ts.map +1 -0
- package/lib/messages/index.js +25 -0
- package/lib/messages/index.js.map +1 -0
- package/lib/messages/renderer.d.ts +136 -0
- package/lib/messages/renderer.d.ts.map +1 -0
- package/lib/messages/renderer.js +159 -0
- package/lib/messages/renderer.js.map +1 -0
- package/lib/node/node-file-system-provider.d.ts.map +1 -1
- package/lib/node/node-file-system-provider.js +1 -40
- package/lib/node/node-file-system-provider.js.map +1 -1
- package/lib/node/profiling-run.js +1 -1
- package/lib/node/profiling-run.js.map +1 -1
- package/lib/node/rename-over-open-readers.d.ts +52 -0
- package/lib/node/rename-over-open-readers.d.ts.map +1 -0
- package/lib/node/rename-over-open-readers.js +66 -0
- package/lib/node/rename-over-open-readers.js.map +1 -0
- package/lib/testing/fake-document.d.ts +14 -3
- package/lib/testing/fake-document.d.ts.map +1 -1
- package/lib/testing/fake-document.js +18 -3
- package/lib/testing/fake-document.js.map +1 -1
- package/lib/testing/make-noop-shared-services.d.ts +14 -0
- package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
- package/lib/testing/make-noop-shared-services.js +28 -2
- package/lib/testing/make-noop-shared-services.js.map +1 -1
- package/lib/testing/make-test-services.d.ts +31 -4
- package/lib/testing/make-test-services.d.ts.map +1 -1
- package/lib/testing/make-test-services.js +19 -3
- package/lib/testing/make-test-services.js.map +1 -1
- package/lib/testing/node/spawned-server.d.ts +14 -0
- package/lib/testing/node/spawned-server.d.ts.map +1 -1
- package/lib/testing/node/spawned-server.js +33 -1
- package/lib/testing/node/spawned-server.js.map +1 -1
- package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
- package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
- package/lib/testing/playwright/flaky-network-proxy.js +164 -0
- package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
- package/lib/testing/playwright/index.d.ts +1 -0
- package/lib/testing/playwright/index.d.ts.map +1 -1
- package/lib/testing/playwright/index.js +1 -0
- package/lib/testing/playwright/index.js.map +1 -1
- package/lib/testing/playwright/server-log-capture.d.ts +14 -4
- package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
- package/lib/testing/playwright/server-log-capture.js +21 -4
- package/lib/testing/playwright/server-log-capture.js.map +1 -1
- package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
- package/lib/testing/playwright/server-log-rename-reporter.js +10 -1
- package/lib/testing/playwright/server-log-rename-reporter.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-document-builder.d.ts +8 -0
- package/lib/testing/stub-document-builder.d.ts.map +1 -1
- package/lib/testing/stub-document-builder.js +22 -3
- package/lib/testing/stub-document-builder.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 +17 -8
- package/src/documents/ast-document-manager.ts +63 -15
- package/src/documents/hydranium-text-documents.ts +135 -54
- package/src/documents/language-client-text-shadow.ts +59 -11
- package/src/documents/self-save-registry.ts +52 -13
- package/src/index.ts +5 -0
- package/src/langium/ast-extension/ast-node-builder.ts +66 -22
- package/src/langium/bootstrap.ts +16 -9
- package/src/langium/config/configuration-provider.ts +96 -0
- package/src/langium/config/index.ts +1 -0
- package/src/langium/diagnostics/lsp-logger.ts +41 -6
- package/src/langium/document-builder/build-pipeline-integration.ts +14 -1
- package/src/langium/document-builder/build-session.ts +87 -0
- package/src/langium/document-builder/document-builder.ts +339 -11
- package/src/langium/document-builder/index.ts +1 -0
- package/src/langium/integration-services.ts +67 -6
- package/src/langium/integrity/integrity-service.ts +35 -1
- package/src/langium/keys/containment.ts +87 -0
- package/src/langium/keys/index.ts +1 -0
- package/src/langium/keys/name-based-key-provider.ts +4 -0
- package/src/langium/model-service/model-service.ts +145 -22
- package/src/langium/module.ts +104 -19
- package/src/langium/naming/name-provider.ts +10 -0
- package/src/langium/naming/name-separator-validation.ts +34 -5
- package/src/langium/residency/cst-residency-service.ts +36 -12
- package/src/langium/scope/hydranium-scope-provider.ts +40 -22
- package/src/langium/shared-services.ts +19 -2
- package/src/langium/transfer/transfer-encoder.ts +45 -3
- package/src/langium/validation/document-validator.ts +255 -8
- package/src/langium/workspace/document-uri-policy.ts +3 -4
- package/src/langium/workspace/hydranium-langium-document-factory.ts +34 -0
- package/src/langium/workspace/hydranium-workspace-manager.ts +30 -1
- package/src/langium/workspace/in-memory-file-system-provider.ts +13 -3
- package/src/langium/workspace/initialize-workspace.ts +34 -5
- package/src/langium/workspace/langium-documents.ts +106 -33
- package/src/locale/index.ts +10 -0
- package/src/locale/server-locale.ts +86 -0
- package/src/lsp/hydranium-document-update-handler.ts +1 -1
- package/src/messages/carriers.ts +78 -0
- package/src/messages/index.ts +27 -0
- package/src/messages/renderer.ts +204 -0
- package/src/node/node-file-system-provider.ts +1 -41
- package/src/node/profiling-run.ts +1 -1
- package/src/node/rename-over-open-readers.ts +78 -0
- package/src/testing/fake-document.ts +22 -4
- package/src/testing/make-noop-shared-services.ts +50 -2
- package/src/testing/make-test-services.ts +49 -7
- package/src/testing/node/spawned-server.ts +35 -1
- package/src/testing/playwright/flaky-network-proxy.ts +240 -0
- package/src/testing/playwright/index.ts +1 -0
- package/src/testing/playwright/server-log-capture.ts +24 -5
- package/src/testing/playwright/server-log-rename-reporter.ts +12 -1
- package/src/testing/stub-ast-document-manager.ts +5 -10
- package/src/testing/stub-document-builder.ts +32 -3
- package/src/testing/stub-model-service.ts +2 -2
|
@@ -57,12 +57,23 @@ export function isFullReplace(edits: readonly TextEdit[]): boolean {
|
|
|
57
57
|
* - {@link computeEdits} after each outgoing server→client sync (updates the shadow optimistically).
|
|
58
58
|
* - {@link set} when the server receives a `didChange` from the language client (the client now
|
|
59
59
|
* holds that text; we shouldn't resend it).
|
|
60
|
+
* - {@link setOpenedText} when the client opens a document the server did not baseline for it —
|
|
61
|
+
* an equality-only baseline, since that text is a snapshot rather than a tracked one.
|
|
60
62
|
* - {@link invalidate} when the client rejects an edit (`applied=false`) or a doc is closed —
|
|
61
63
|
* the shadow is now stale and the next sync must fall back to a full replace.
|
|
62
64
|
*/
|
|
63
65
|
export class LanguageClientTextShadow {
|
|
64
66
|
protected shadow = new Map<string, string>();
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Per URI, the text the client declared it opened the document with, held
|
|
70
|
+
* only until a real baseline exists. Consulted by {@link computeEdits} when
|
|
71
|
+
* there is no shadow, and dropped on {@link invalidate}: a snapshot that
|
|
72
|
+
* outlived what the client holds would let the equality check skip an edit
|
|
73
|
+
* the client still needs.
|
|
74
|
+
*/
|
|
75
|
+
protected opened = new Map<string, string>();
|
|
76
|
+
|
|
66
77
|
/**
|
|
67
78
|
* @param onFallback invoked when {@link computeEdits} falls back to a full-range replace due to
|
|
68
79
|
* apply-verify mismatch. Useful for logging regressions.
|
|
@@ -85,23 +96,54 @@ export class LanguageClientTextShadow {
|
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
99
|
+
* Record the buffer the language client declared at `didOpen`, for a URI
|
|
100
|
+
* with no baseline yet.
|
|
101
|
+
*
|
|
102
|
+
* Deliberately NOT a {@link set}: this text is only ever compared for
|
|
103
|
+
* EQUALITY, never diffed against. A line-keyed diff is position-dependent,
|
|
104
|
+
* so keying one to a `didOpen` snapshot the client may have moved past
|
|
105
|
+
* splices its buffer — the failure {@link computeEdits}'s full-replace
|
|
106
|
+
* fallback exists to avoid.
|
|
107
|
+
*/
|
|
108
|
+
setOpenedText(uri: string, text: string): void {
|
|
109
|
+
this.opened.set(uri, text);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The text the language client is believed to hold: the tracked text, else
|
|
114
|
+
* the buffer it declared at `didOpen`.
|
|
90
115
|
*
|
|
91
116
|
* Read at the `workspace/applyEdit` egress BEFORE {@link computeEdits},
|
|
92
|
-
* which overwrites
|
|
93
|
-
*
|
|
94
|
-
* it
|
|
95
|
-
*
|
|
96
|
-
*
|
|
117
|
+
* which overwrites the tracked text and drops the opened snapshot. A change
|
|
118
|
+
* the client sends carries ranges addressing this text and nothing else, so
|
|
119
|
+
* losing it leaves that change unreconstructable — and applying its ranges
|
|
120
|
+
* to the already-advanced synced text splices lines they never addressed.
|
|
121
|
+
*
|
|
122
|
+
* Deliberately wider than what {@link computeEdits} may diff from: an
|
|
123
|
+
* opened snapshot is a position-dependent key to a buffer the client may
|
|
124
|
+
* have moved past, so keying a diff to it splices that buffer.
|
|
97
125
|
*/
|
|
98
|
-
|
|
99
|
-
return this.shadow.get(uri);
|
|
126
|
+
clientText(uri: string): string | undefined {
|
|
127
|
+
return this.shadow.get(uri) ?? this.opened.get(uri);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Whether a tracked text exists for `uri`, which is what decides whether a
|
|
132
|
+
* push can be diffed at all. Distinct from {@link clientText}, which also
|
|
133
|
+
* answers from the opened snapshot and so cannot settle this.
|
|
134
|
+
*/
|
|
135
|
+
isTracked(uri: string): boolean {
|
|
136
|
+
return this.shadow.has(uri);
|
|
100
137
|
}
|
|
101
138
|
|
|
102
139
|
/** Forget the shadow for a URI; next {@link computeEdits} falls back to a full-document replace. */
|
|
103
140
|
invalidate(uri: string): void {
|
|
104
141
|
this.shadow.delete(uri);
|
|
142
|
+
// Drops the opened snapshot too, and this is the only place that has to:
|
|
143
|
+
// `computeEdits` clears it whenever it consults it, so the sole way it can
|
|
144
|
+
// outlive what the client holds is surviving under a live shadow until that
|
|
145
|
+
// shadow is dropped here.
|
|
146
|
+
this.opened.delete(uri);
|
|
105
147
|
}
|
|
106
148
|
|
|
107
149
|
/**
|
|
@@ -109,7 +151,9 @@ export class LanguageClientTextShadow {
|
|
|
109
151
|
* the tracked shadow to `newText`. Updates the shadow to `newText` so subsequent calls diff
|
|
110
152
|
* against it.
|
|
111
153
|
*
|
|
112
|
-
* - Empty array when the shadow already equals `newText` (caller should skip the RPC)
|
|
154
|
+
* - Empty array when the shadow already equals `newText` (caller should skip the RPC),
|
|
155
|
+
* or when there is no shadow and the client opened the document with exactly `newText`
|
|
156
|
+
* (see {@link setOpenedText}) — pushing it anyway dirties the client's buffer on open.
|
|
113
157
|
* - Full-range replace when there is no shadow (first sync or post-{@link invalidate}).
|
|
114
158
|
* - Line-level diff otherwise. Before returning, we locally reconstruct `newText` by applying
|
|
115
159
|
* the diff to `old`; if that doesn't match, we fall back to a full-range replace and notify
|
|
@@ -124,7 +168,11 @@ export class LanguageClientTextShadow {
|
|
|
124
168
|
this.shadow.set(uri, newText);
|
|
125
169
|
const fullReplace: TextEdit = { range: FULL_RANGE, newText };
|
|
126
170
|
if (old === undefined) {
|
|
127
|
-
|
|
171
|
+
// The opened snapshot has served its purpose either way: the shadow above
|
|
172
|
+
// is now the baseline, so leaving it would only risk a later stale match.
|
|
173
|
+
const openedText = this.opened.get(uri);
|
|
174
|
+
this.opened.delete(uri);
|
|
175
|
+
return openedText === newText ? [] : [fullReplace];
|
|
128
176
|
}
|
|
129
177
|
const edits = diffToEdits(old, newText);
|
|
130
178
|
const verifyDoc = this.documents.create(uri, 'plaintext', 0, old);
|
|
@@ -22,10 +22,32 @@ export interface SelfSaveRegistryOptions {
|
|
|
22
22
|
/**
|
|
23
23
|
* Tracks `mtime` of files written by the server itself so the echo
|
|
24
24
|
* `didChangeWatchedFiles` event from the OS file watcher can be suppressed,
|
|
25
|
-
* avoiding redundant rebuilds after each save.
|
|
26
|
-
* (not by a time window) and expire after a TTL.
|
|
25
|
+
* avoiding redundant rebuilds after each save.
|
|
27
26
|
*/
|
|
28
|
-
export
|
|
27
|
+
export interface SelfSaveRegistry {
|
|
28
|
+
/**
|
|
29
|
+
* Record a self-write, and drop any registration already past its TTL.
|
|
30
|
+
*
|
|
31
|
+
* Keyed by `fsPath` rather than URI string, because the two sides of a
|
|
32
|
+
* suppression see different URI forms for one file.
|
|
33
|
+
*/
|
|
34
|
+
register(fsPath: string, mtimeMs: number): void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether `fsPath` carries a live registration at exactly `mtimeMs`.
|
|
38
|
+
*
|
|
39
|
+
* A registration expires after the TTL, so a write recorded longer ago than
|
|
40
|
+
* that answers `false` — the caller's decision holds only while the echo it
|
|
41
|
+
* suppresses could still be in flight. A query never mutates the registry.
|
|
42
|
+
*/
|
|
43
|
+
isRegistered(fsPath: string, mtimeMs: number): boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compares entries by mtime rather than by a time window, so the echo of a
|
|
48
|
+
* save whose content is unchanged is still recognised.
|
|
49
|
+
*/
|
|
50
|
+
export class DefaultSelfSaveRegistry implements SelfSaveRegistry {
|
|
29
51
|
protected readonly entries = new Map<string, { mtimeMs: number; registeredAt: number }>();
|
|
30
52
|
/**
|
|
31
53
|
* Read time through the shared {@link Clock} rather than `Date.now()`: the
|
|
@@ -59,23 +81,40 @@ export class SelfSaveRegistry {
|
|
|
59
81
|
return isCaseInsensitiveFileSystem() ? fsPath.toLowerCase() : fsPath;
|
|
60
82
|
}
|
|
61
83
|
|
|
62
|
-
/** Record a self-write. Key by `fsPath` (not URI string) to avoid URI-form mismatches. */
|
|
63
84
|
register(fsPath: string, mtimeMs: number): void {
|
|
85
|
+
this.evictExpired();
|
|
64
86
|
this.entries.set(this.key(fsPath), { mtimeMs, registeredAt: this.clock.now() });
|
|
65
87
|
}
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const entry = this.entries.get(key);
|
|
71
|
-
if (!entry) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
if (this.clock.now() - entry.registeredAt > this.ttlMs) {
|
|
75
|
-
this.entries.delete(key);
|
|
89
|
+
isRegistered(fsPath: string, mtimeMs: number): boolean {
|
|
90
|
+
const entry = this.entries.get(this.key(fsPath));
|
|
91
|
+
if (!entry || this.isExpired(entry.registeredAt)) {
|
|
76
92
|
return false;
|
|
77
93
|
}
|
|
78
94
|
// Accept mtime equality (some filesystems have 1-second precision).
|
|
79
95
|
return entry.mtimeMs === mtimeMs;
|
|
80
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Drop every expired registration.
|
|
100
|
+
*
|
|
101
|
+
* On the WRITE path, so the query stays free of side effects. Evicting from
|
|
102
|
+
* the query instead reaches only the keys something happens to ask about,
|
|
103
|
+
* and a path written once and never queried again is then retained for the
|
|
104
|
+
* life of the process. Bounded by the paths self-written inside one TTL
|
|
105
|
+
* window, so the sweep is over a handful of entries and sits behind a file
|
|
106
|
+
* write either way.
|
|
107
|
+
*/
|
|
108
|
+
protected evictExpired(): void {
|
|
109
|
+
for (const [key, entry] of this.entries) {
|
|
110
|
+
if (this.isExpired(entry.registeredAt)) {
|
|
111
|
+
this.entries.delete(key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Strictly greater, so an entry aged exactly to the TTL is still eligible. */
|
|
117
|
+
protected isExpired(registeredAt: number): boolean {
|
|
118
|
+
return this.clock.now() - registeredAt > this.ttlMs;
|
|
119
|
+
}
|
|
81
120
|
}
|
package/src/index.ts
CHANGED
|
@@ -35,5 +35,10 @@ export * from './langium/workspace/index.js';
|
|
|
35
35
|
export * from './langium/service-registry.js';
|
|
36
36
|
export * from './langium/language-types.js';
|
|
37
37
|
export * from './launcher/index.js';
|
|
38
|
+
export * from './locale/index.js';
|
|
39
|
+
// The renderer only. The `./messages` subpath additionally enumerates this
|
|
40
|
+
// package's own declarations, which the root barrel already re-exports through
|
|
41
|
+
// the modules that raise them.
|
|
42
|
+
export * from './messages/renderer.js';
|
|
38
43
|
export * from './documents/index.js';
|
|
39
44
|
export * from './util/index.js';
|
|
@@ -78,15 +78,13 @@ export type AstNodeBuilder<TMap> = <TType extends string & keyof TMap>(
|
|
|
78
78
|
* AST-interface mapping, so call sites get full inference from the type
|
|
79
79
|
* constant alone — no explicit generic parameter at every call site.
|
|
80
80
|
*
|
|
81
|
-
* Runtime behaviour:
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* TS-augmented fields (declared via `declare module './generated/ast.js'`)
|
|
89
|
-
* are unaffected by the runtime defaulting but remain typed at call sites.
|
|
81
|
+
* Runtime behaviour: pre-fills every grammar-declared default (see
|
|
82
|
+
* {@link withTypeDefaults}) before merging the caller's `init`, so containment
|
|
83
|
+
* lists and `?=`-style boolean flags default automatically. TS-augmented fields
|
|
84
|
+
* (declared via `declare module './generated/ast.js'`) are unaffected by the
|
|
85
|
+
* runtime defaulting but remain typed at call sites.
|
|
86
|
+
*
|
|
87
|
+
* Use {@link buildAstNode} instead when the type name is only known at runtime.
|
|
90
88
|
*
|
|
91
89
|
* The init shape only relaxes array-typed fields to optional (via
|
|
92
90
|
* {@link ArrayKeys}) — there is no equivalent structural signal for "this
|
|
@@ -101,19 +99,7 @@ export function makeAstNodeBuilder<TMap>(reflection: AstReflection): AstNodeBuil
|
|
|
101
99
|
init: AstNodeInit<Extract<TMap[TType], AstNode>>,
|
|
102
100
|
extras?: Record<string, unknown>
|
|
103
101
|
): Extract<TMap[TType], AstNode> {
|
|
104
|
-
const
|
|
105
|
-
const result: Record<string, unknown> = { $type: typeConstant.$type };
|
|
106
|
-
if (meta?.properties) {
|
|
107
|
-
for (const [propName, propInfo] of Object.entries(meta.properties)) {
|
|
108
|
-
const defaultValue = propInfo.defaultValue;
|
|
109
|
-
if (defaultValue === undefined) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
// Arrays are duplicated so each node carries its own mutable instance;
|
|
113
|
-
// scalars are immutable and safe to copy by reference.
|
|
114
|
-
result[propName] = Array.isArray(defaultValue) ? [...defaultValue] : defaultValue;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
102
|
+
const result = withTypeDefaults(reflection, typeConstant.$type);
|
|
117
103
|
Object.assign(result, init);
|
|
118
104
|
if (extras) {
|
|
119
105
|
// Extras win over init: this is the explicit escape hatch, so when a
|
|
@@ -123,3 +109,61 @@ export function makeAstNodeBuilder<TMap>(reflection: AstReflection): AstNodeBuil
|
|
|
123
109
|
return result as unknown as Extract<TMap[TType], AstNode>;
|
|
124
110
|
};
|
|
125
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build a node whose type is only known as a RUNTIME string.
|
|
115
|
+
*
|
|
116
|
+
* The typed {@link makeAstNodeBuilder} cannot express this: its `TMap` generic
|
|
117
|
+
* resolves the AST interface from a `$type` literal, and a type name that
|
|
118
|
+
* arrives over the wire — a protocol-layer reference request naming a node that
|
|
119
|
+
* does not exist yet — has no literal to resolve from. Callers that DO know the
|
|
120
|
+
* type at compile time want the typed builder, which checks their mandatory
|
|
121
|
+
* fields; this one checks nothing beyond the reflection lookup.
|
|
122
|
+
*
|
|
123
|
+
* Applies the same reflection-driven defaulting, so a node built here carries
|
|
124
|
+
* grammar-declared containment arrays rather than leaving them `undefined`. An
|
|
125
|
+
* unknown type name yields a node with `$type` and whatever `init` supplied,
|
|
126
|
+
* because reflection answers no metadata for it and refusing would turn a
|
|
127
|
+
* permissive lookup into a throw at a call site that can only guess.
|
|
128
|
+
*
|
|
129
|
+
* **The `type` argument is authoritative: `init` cannot rebind `$type`.** The
|
|
130
|
+
* typed builder gets this for free — `AstNodeInit` omits `$type`, so a call site
|
|
131
|
+
* cannot reach it — but here `init` is loosely typed, and the case that needs
|
|
132
|
+
* the guarantee is RETYPING: a caller spreading an existing node to produce one
|
|
133
|
+
* of a different type (`{ ...source }`) carries the SOURCE's `$type` in, which
|
|
134
|
+
* would silently win over the type actually requested and hand back a node of
|
|
135
|
+
* the wrong type with no error anywhere.
|
|
136
|
+
*/
|
|
137
|
+
export function buildAstNode<TAst extends AstNode = AstNode>(
|
|
138
|
+
reflection: AstReflection,
|
|
139
|
+
type: string,
|
|
140
|
+
init?: Partial<AstNode> & Record<string, unknown>
|
|
141
|
+
): TAst {
|
|
142
|
+
const result = withTypeDefaults(reflection, type);
|
|
143
|
+
if (init) {
|
|
144
|
+
Object.assign(result, init);
|
|
145
|
+
result.$type = type;
|
|
146
|
+
}
|
|
147
|
+
return result as unknown as TAst;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Seed a node with `$type` plus every property the grammar declares a default
|
|
152
|
+
* for. Array defaults are materialised as a fresh `[]` per call — never shared
|
|
153
|
+
* with the metadata's own array instance, which downstream callers mutate —
|
|
154
|
+
* while scalar defaults are copied as-is since they are immutable.
|
|
155
|
+
*/
|
|
156
|
+
function withTypeDefaults(reflection: AstReflection, type: string): Record<string, unknown> {
|
|
157
|
+
const result: Record<string, unknown> = { $type: type };
|
|
158
|
+
const meta = reflection.getTypeMetaData(type);
|
|
159
|
+
if (meta?.properties) {
|
|
160
|
+
for (const [propName, propInfo] of Object.entries(meta.properties)) {
|
|
161
|
+
const defaultValue = propInfo.defaultValue;
|
|
162
|
+
if (defaultValue === undefined) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
result[propName] = Array.isArray(defaultValue) ? [...defaultValue] : defaultValue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
package/src/langium/bootstrap.ts
CHANGED
|
@@ -12,15 +12,15 @@ import { type LangiumSharedCoreServices } from '@hydranium/langium';
|
|
|
12
12
|
import { type LangiumServices, type LangiumSharedServices } from '@hydranium/langium/lsp';
|
|
13
13
|
import type { ServerSharedServicesMinimal } from './shared-services.js';
|
|
14
14
|
import { collectProducibleTypes } from './language-types.js';
|
|
15
|
-
import {
|
|
15
|
+
import { DefaultAstDocumentManager } from '../documents/ast-document-manager.js';
|
|
16
16
|
import { HydraniumTextDocuments } from '../documents/hydranium-text-documents.js';
|
|
17
|
-
import {
|
|
17
|
+
import { DefaultBuildPipelineIntegration } from './document-builder/build-pipeline-integration.js';
|
|
18
18
|
import { HydraniumDocumentBuilder } from './document-builder/document-builder.js';
|
|
19
|
-
import {
|
|
19
|
+
import { DefaultModelService } from './model-service/model-service.js';
|
|
20
20
|
import { HydraniumScopeComputation } from './scope/hydranium-scope-computation.js';
|
|
21
21
|
import { HydraniumScopeProvider } from './scope/hydranium-scope-provider.js';
|
|
22
22
|
import { processEnv } from '../util/environment.js';
|
|
23
|
-
import {
|
|
23
|
+
import { DefaultTransferEncoder } from './transfer/transfer-encoder.js';
|
|
24
24
|
import { HydraniumWorkspaceManager } from './workspace/hydranium-workspace-manager.js';
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -73,7 +73,14 @@ const EXPECTED_SHARED_SLOTS: readonly ExpectedSlot[] = [
|
|
|
73
73
|
side: 'shared',
|
|
74
74
|
path: 'workspace.BuildPipelineIntegration',
|
|
75
75
|
get: services => nested(services, 'workspace', 'BuildPipelineIntegration')
|
|
76
|
-
}
|
|
76
|
+
},
|
|
77
|
+
// Unbound, this fails where the cause is least visible: the render pass runs
|
|
78
|
+
// inside the `Validated` phase, so a `TypeError` there propagates out of
|
|
79
|
+
// `notifyDocumentPhase` and the document reaches `Validated` with Langium's
|
|
80
|
+
// publisher never invoked — the client receives no diagnostics for the file
|
|
81
|
+
// and the stack names the document builder.
|
|
82
|
+
{ side: 'shared', path: 'MessageRenderer', get: services => nested(services, 'MessageRenderer') },
|
|
83
|
+
{ side: 'shared', path: 'ServerLocale', get: services => nested(services, 'ServerLocale') }
|
|
77
84
|
];
|
|
78
85
|
|
|
79
86
|
/**
|
|
@@ -199,17 +206,17 @@ const STRICT_SHARED_SLOTS: readonly StrictSlot[] = [
|
|
|
199
206
|
{
|
|
200
207
|
side: 'shared',
|
|
201
208
|
path: 'workspace.AstDocumentManager',
|
|
202
|
-
base:
|
|
209
|
+
base: DefaultAstDocumentManager,
|
|
203
210
|
get: s => nested(s, 'workspace', 'AstDocumentManager')
|
|
204
211
|
},
|
|
205
212
|
{
|
|
206
213
|
side: 'shared',
|
|
207
214
|
path: 'workspace.BuildPipelineIntegration',
|
|
208
|
-
base:
|
|
215
|
+
base: DefaultBuildPipelineIntegration,
|
|
209
216
|
get: s => nested(s, 'workspace', 'BuildPipelineIntegration')
|
|
210
217
|
},
|
|
211
|
-
{ side: 'shared', path: 'model.TransferEncoder', base:
|
|
212
|
-
{ side: 'shared', path: 'model.ModelService', base:
|
|
218
|
+
{ side: 'shared', path: 'model.TransferEncoder', base: DefaultTransferEncoder, get: s => nested(s, 'model', 'TransferEncoder') },
|
|
219
|
+
{ side: 'shared', path: 'model.ModelService', base: DefaultModelService, get: s => nested(s, 'model', 'ModelService') }
|
|
213
220
|
];
|
|
214
221
|
|
|
215
222
|
const STRICT_LANGUAGE_SLOTS: readonly StrictSlot[] = [
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { type ConfigurationInitializedParams, DefaultConfigurationProvider } from '@hydranium/langium';
|
|
11
|
+
import type { ConfigurationItem, DidChangeConfigurationRegistrationOptions } from 'vscode-languageserver';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Configuration provider that asks the client about a section the first time
|
|
15
|
+
* something reads it.
|
|
16
|
+
*
|
|
17
|
+
* Langium's base requests one section per registered language, named after the
|
|
18
|
+
* language id, and never asks again. A read of any other section — including
|
|
19
|
+
* whatever `lsp.configurationRoot` is bound to — then resolves `undefined` with
|
|
20
|
+
* no request on the wire, which is indistinguishable from a setting the user has
|
|
21
|
+
* not set.
|
|
22
|
+
*
|
|
23
|
+
* Asking on first read rather than from a list collected up front: a reader
|
|
24
|
+
* names its section during DI, after `initialized` has taken its one-shot pass,
|
|
25
|
+
* so a list would have to be complete before the server initialized.
|
|
26
|
+
*/
|
|
27
|
+
export class HydraniumConfigurationProvider extends DefaultConfigurationProvider {
|
|
28
|
+
/**
|
|
29
|
+
* The client-facing hooks, captured because they arrive once as arguments to
|
|
30
|
+
* {@link initialized} and are needed on every later read. Absent in a
|
|
31
|
+
* headless composition, which has no connection to ask.
|
|
32
|
+
*/
|
|
33
|
+
protected registerForSection?: (params: DidChangeConfigurationRegistrationOptions) => void;
|
|
34
|
+
protected fetchSections?: (items: ConfigurationItem[]) => Promise<unknown>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sections already asked about, holding the in-flight request so concurrent
|
|
38
|
+
* readers share one round trip. Entries stay after they settle: they record
|
|
39
|
+
* that the question was ASKED, and without that an unset section is
|
|
40
|
+
* re-requested on every read, "unset" and "not fetched" being the same
|
|
41
|
+
* absence in the store.
|
|
42
|
+
*/
|
|
43
|
+
protected readonly requestedSections = new Map<string, Promise<void>>();
|
|
44
|
+
|
|
45
|
+
override async initialized(params: ConfigurationInitializedParams): Promise<void> {
|
|
46
|
+
this.registerForSection = params.register;
|
|
47
|
+
this.fetchSections = params.fetchConfiguration;
|
|
48
|
+
await super.initialized(params);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The `ready` await precedes the emptiness check: the base resolves it at the
|
|
53
|
+
* end of its own initial fetch, so testing the store first calls every
|
|
54
|
+
* language section missing and re-requests what is already on its way.
|
|
55
|
+
*/
|
|
56
|
+
override async getConfiguration(language: string, configuration: string): Promise<unknown> {
|
|
57
|
+
await this.ready;
|
|
58
|
+
const section = this.toSectionName(language);
|
|
59
|
+
if (this.settings[section] === undefined) {
|
|
60
|
+
await this.requestSection(section);
|
|
61
|
+
}
|
|
62
|
+
return super.getConfiguration(language, configuration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Ask about `section` once, sharing the request with any concurrent reader. */
|
|
66
|
+
protected requestSection(section: string): Promise<void> {
|
|
67
|
+
const existing = this.requestedSections.get(section);
|
|
68
|
+
if (existing) {
|
|
69
|
+
return existing;
|
|
70
|
+
}
|
|
71
|
+
const request = this.doRequestSection(section);
|
|
72
|
+
this.requestedSections.set(section, request);
|
|
73
|
+
return request;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Registering before fetching, so an edit landing between the two arrives as
|
|
78
|
+
* a notification instead of being lost; the reverse order drops it.
|
|
79
|
+
*
|
|
80
|
+
* A `null` result is left out of the store rather than written, so the
|
|
81
|
+
* section reads as unset, and no `onConfigurationSectionUpdate` fires — the
|
|
82
|
+
* value goes to the caller that asked for it, and an event would announce a
|
|
83
|
+
* change that is only this provider catching up.
|
|
84
|
+
*/
|
|
85
|
+
protected async doRequestSection(section: string): Promise<void> {
|
|
86
|
+
this.registerForSection?.({ section });
|
|
87
|
+
if (!this.fetchSections) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fetched = await this.fetchSections([{ section }]);
|
|
91
|
+
const value = Array.isArray(fetched) ? fetched[0] : fetched;
|
|
92
|
+
if (value !== null && value !== undefined) {
|
|
93
|
+
this.updateSectionConfiguration(section, value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -92,14 +92,17 @@ export class LspLogger extends AbstractLogger implements Logger {
|
|
|
92
92
|
// a Node concern; skip it cleanly there.
|
|
93
93
|
const env = processEnv();
|
|
94
94
|
if (env) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
95
|
+
// The file-tee target FIRST: the other order writes the level
|
|
96
|
+
// announcement before the sink exists, losing the one line that says
|
|
97
|
+
// which threshold the rest of the file was written at.
|
|
99
98
|
const envLogFile = env[DEFAULT_LOG_FILE_ENV];
|
|
100
99
|
if (envLogFile) {
|
|
101
100
|
setLogFilePath(envLogFile);
|
|
102
101
|
}
|
|
102
|
+
const envLevel = parseLogLevel(env[DEFAULT_LOG_LEVEL_ENV]);
|
|
103
|
+
if (envLevel) {
|
|
104
|
+
this.applyLevel(envLevel, DEFAULT_LOG_LEVEL_ENV);
|
|
105
|
+
}
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
// Apply the configured threshold from the root logger only — `derive`
|
|
@@ -109,16 +112,48 @@ export class LspLogger extends AbstractLogger implements Logger {
|
|
|
109
112
|
if (options.logThreshold !== undefined) {
|
|
110
113
|
const level = ObservableValue.from(options.logThreshold);
|
|
111
114
|
if (level.value) {
|
|
112
|
-
|
|
115
|
+
this.applyLevel(level.value, 'setting');
|
|
113
116
|
}
|
|
114
117
|
level.onChange(next => {
|
|
115
118
|
if (next) {
|
|
116
|
-
|
|
119
|
+
this.applyLevel(next, 'setting');
|
|
117
120
|
}
|
|
118
121
|
});
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Set the process-wide threshold and announce the transition under whichever
|
|
127
|
+
* of the two thresholds can carry the notice, so a reader sees every change
|
|
128
|
+
* and its absence means the threshold never moved.
|
|
129
|
+
*
|
|
130
|
+
* `error` and `off` admit no notice of their own — `off` nothing at all, and
|
|
131
|
+
* an error-severity line is read as a fault, which a client that marks its
|
|
132
|
+
* log as having seen an error keeps for the session. Entering either is
|
|
133
|
+
* therefore announced BEFORE the switch, under the outgoing threshold; every
|
|
134
|
+
* other change is announced after it, at the level being entered. Leaving
|
|
135
|
+
* `error` or `off` for one of those two says nothing, both thresholds being
|
|
136
|
+
* unable to carry it.
|
|
137
|
+
*
|
|
138
|
+
* Only on a real transition: the bound setting re-fires for configuration
|
|
139
|
+
* changes that leave the value alone.
|
|
140
|
+
*/
|
|
141
|
+
protected applyLevel(next: LogThreshold, source: string): void {
|
|
142
|
+
const previous = Logger.getLevel();
|
|
143
|
+
if (previous === next) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const message = `Log level ${previous} → ${next} (${source})`;
|
|
147
|
+
const announceFirst = next === 'error' || next === 'off';
|
|
148
|
+
if (announceFirst && previous !== 'error') {
|
|
149
|
+
this.logAt(previous, message);
|
|
150
|
+
}
|
|
151
|
+
Logger.setLevel(next);
|
|
152
|
+
if (!announceFirst) {
|
|
153
|
+
this.logAt(next, message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
122
157
|
/**
|
|
123
158
|
* Route logger timing and timestamps through the injected framework
|
|
124
159
|
* {@link Clock} (the dedicated `shared.Clock` slot) so server-side log time
|
|
@@ -65,6 +65,19 @@ export interface BuildPipelineIntegrationOptions {
|
|
|
65
65
|
readonly integrityPriority?: number;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The build-pipeline slot. Empty because nothing calls this service — it wires
|
|
70
|
+
* itself in its constructor and every seam it offers is `protected`.
|
|
71
|
+
*
|
|
72
|
+
* Typing the slot by the class instead would stop an adopter's subclass
|
|
73
|
+
* satisfying it, since a class carries its `protected` members into the check
|
|
74
|
+
* and they compare by declaration rather than by shape. The cost of the empty
|
|
75
|
+
* shape is that any non-null value satisfies it, so a mis-bound slot compiles
|
|
76
|
+
* and the framework's phase listeners are then never attached.
|
|
77
|
+
*/
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- the slot has no public member to name
|
|
79
|
+
export interface BuildPipelineIntegration {}
|
|
80
|
+
|
|
68
81
|
/**
|
|
69
82
|
* Wires the framework's build-time features (integrity rules, AST
|
|
70
83
|
* enrichment) into Langium's document-build pipeline. The single shared
|
|
@@ -90,7 +103,7 @@ export interface BuildPipelineIntegrationOptions {
|
|
|
90
103
|
* Eagerly constructed (via `DEFAULT_EAGER_SERVICES`) so the listeners are
|
|
91
104
|
* attached before the first build cycle.
|
|
92
105
|
*/
|
|
93
|
-
export class BuildPipelineIntegration {
|
|
106
|
+
export class DefaultBuildPipelineIntegration implements BuildPipelineIntegration {
|
|
94
107
|
constructor(
|
|
95
108
|
protected readonly services: ServerSharedServices,
|
|
96
109
|
options: BuildPipelineIntegrationOptions = {}
|