@hydranium/core 1.0.0-next.259 → 1.0.0-next.261
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/langium/bootstrap.d.ts +11 -9
- package/lib/langium/bootstrap.d.ts.map +1 -1
- package/lib/langium/bootstrap.js +11 -9
- package/lib/langium/bootstrap.js.map +1 -1
- package/lib/langium/build-phase-pass/build-phase-pass-service.d.ts +1 -1
- package/lib/langium/build-phase-pass/build-phase-pass.d.ts +1 -1
- package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts +9 -20
- package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts.map +1 -1
- package/lib/langium/diagnostics/hydranium-langium-profiler.js +15 -24
- package/lib/langium/diagnostics/hydranium-langium-profiler.js.map +1 -1
- package/lib/langium/document-builder/document-builder.d.ts +22 -10
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +25 -17
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/integrity/integrity-service.d.ts +1 -3
- package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-service.js +1 -3
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/module.d.ts +1 -1
- package/lib/langium/module.js +1 -1
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/scope/fallback-scope.d.ts +27 -0
- package/lib/langium/scope/fallback-scope.d.ts.map +1 -0
- package/lib/langium/scope/fallback-scope.js +35 -0
- package/lib/langium/scope/fallback-scope.js.map +1 -0
- package/lib/langium/scope/hydranium-scope-provider.d.ts +10 -9
- package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
- package/lib/langium/scope/hydranium-scope-provider.js +15 -13
- package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
- package/lib/langium/scope/index.d.ts +1 -0
- package/lib/langium/scope/index.d.ts.map +1 -1
- package/lib/langium/scope/index.js +1 -0
- package/lib/langium/scope/index.js.map +1 -1
- package/lib/langium/scope/reference-candidate-provider.d.ts +3 -3
- package/lib/langium/scope/reference-candidate-provider.d.ts.map +1 -1
- package/lib/langium/scope/reference-candidate-provider.js.map +1 -1
- package/lib/langium/scope/scope-extension-service.d.ts +41 -37
- package/lib/langium/scope/scope-extension-service.d.ts.map +1 -1
- package/lib/langium/scope/scope-extension-service.js +57 -29
- package/lib/langium/scope/scope-extension-service.js.map +1 -1
- package/lib/langium/shared-services.d.ts +2 -0
- package/lib/langium/shared-services.d.ts.map +1 -1
- package/lib/langium/shared-services.js.map +1 -1
- package/lib/node/rename-over-open-readers.d.ts +2 -0
- package/lib/node/rename-over-open-readers.d.ts.map +1 -1
- package/lib/node/rename-over-open-readers.js +6 -2
- package/lib/node/rename-over-open-readers.js.map +1 -1
- package/lib/testing/playwright/server-log-capture.d.ts +4 -0
- package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
- package/lib/testing/playwright/server-log-capture.js.map +1 -1
- package/lib/testing/stub-document-builder.d.ts +9 -2
- package/lib/testing/stub-document-builder.d.ts.map +1 -1
- package/lib/testing/stub-document-builder.js +25 -0
- package/lib/testing/stub-document-builder.js.map +1 -1
- package/package.json +5 -5
- package/src/langium/bootstrap.ts +11 -9
- package/src/langium/build-phase-pass/build-phase-pass-service.ts +1 -1
- package/src/langium/build-phase-pass/build-phase-pass.ts +1 -1
- package/src/langium/diagnostics/hydranium-langium-profiler.ts +15 -39
- package/src/langium/document-builder/document-builder.ts +37 -19
- package/src/langium/integrity/integrity-service.ts +2 -4
- package/src/langium/module.ts +2 -2
- package/src/langium/scope/fallback-scope.ts +38 -0
- package/src/langium/scope/hydranium-scope-provider.ts +26 -14
- package/src/langium/scope/index.ts +1 -0
- package/src/langium/scope/reference-candidate-provider.ts +3 -3
- package/src/langium/scope/scope-extension-service.ts +74 -59
- package/src/langium/shared-services.ts +5 -0
- package/src/node/rename-over-open-readers.ts +7 -2
- package/src/testing/playwright/server-log-capture.ts +4 -0
- package/src/testing/stub-document-builder.ts +33 -2
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { type AstNode, AstUtils, type LangiumDocument
|
|
10
|
+
import { type AstNode, AstUtils, type LangiumDocument } from '@hydranium/langium';
|
|
11
11
|
import { type Disposable } from 'vscode-languageserver';
|
|
12
|
-
import { type Tracer } from '@hydranium/protocol';
|
|
12
|
+
import { Format, Logger, type ProfileRecord, type ProfileSession, type Tracer } from '@hydranium/protocol';
|
|
13
13
|
import { type LogNameOptions } from '../diagnostics/logger.js';
|
|
14
14
|
import { type ServerLanguageServices } from '../language-module.js';
|
|
15
15
|
import { Registry, type RegistryItem } from '../../util/registry.js';
|
|
@@ -49,8 +49,7 @@ export interface ScopeDescriptionAcceptor {
|
|
|
49
49
|
* descriptions are constructed ahead of time, to avoid per-query
|
|
50
50
|
* allocation. The pushed description's `tier` must be `'local'` or
|
|
51
51
|
* `'universal'`; a description of any other tier is silently dropped,
|
|
52
|
-
* because
|
|
53
|
-
* the tier they were asked for.
|
|
52
|
+
* because {@link ScopeExtensionDescriptions} holds only those two.
|
|
54
53
|
*/
|
|
55
54
|
push(description: TieredAstNodeDescription): void;
|
|
56
55
|
}
|
|
@@ -87,38 +86,32 @@ export interface ScopeExtension extends RegistryItem {
|
|
|
87
86
|
addDescriptions(context: AstNode, referenceType: string, document: LangiumDocument, accept: ScopeDescriptionAcceptor): void;
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
/** The scope-extension descriptions for one reference, split by tier. */
|
|
90
|
+
export interface ScopeExtensionDescriptions {
|
|
91
|
+
readonly local: readonly TieredAstNodeDescription[];
|
|
92
|
+
readonly universal: readonly TieredAstNodeDescription[];
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
/**
|
|
91
96
|
* Public contract for the per-language scope-extension service. Extends
|
|
92
97
|
* the {@link ScopeExtensionRegistry} (the imperative `register` surface
|
|
93
|
-
* contributions use) with the
|
|
94
|
-
*
|
|
95
|
-
* `layerUniversalExtension` helpers, which layer scope-extension
|
|
96
|
-
* contributions around the cached project chain.
|
|
97
|
-
*
|
|
98
|
-
* Adopter overrides go through {@link DefaultScopeExtensionService}; the
|
|
99
|
-
* interface keeps the public API stable while internals
|
|
100
|
-
* (`collectExtensionDescriptions` walk) stay `protected` on the default
|
|
101
|
-
* class.
|
|
98
|
+
* contributions use) with the query `HydraniumScopeProvider` builds its
|
|
99
|
+
* extension tiers from.
|
|
102
100
|
*/
|
|
103
101
|
export interface ScopeExtensionService extends ScopeExtensionRegistry {
|
|
104
102
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
103
|
+
* Run every extension registered for `referenceType` once against
|
|
104
|
+
* `context`, and answer its descriptions by tier. A caller that needs
|
|
105
|
+
* both tiers takes them from one call: a call per tier runs every
|
|
106
|
+
* extension twice.
|
|
109
107
|
*/
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Layer this language's `universal`-tier scope-extension descriptions
|
|
113
|
-
* BELOW `outerScope` for the given reference type. Returns `outerScope`
|
|
114
|
-
* unchanged when no extension contributes a `universal`-tier
|
|
115
|
-
* description for the type.
|
|
116
|
-
*/
|
|
117
|
-
getUniversalExtensionScope(referenceType: string, context: AstNode, outerScope: Scope): Scope;
|
|
108
|
+
getDescriptions(referenceType: string, context: AstNode): ScopeExtensionDescriptions;
|
|
118
109
|
}
|
|
119
110
|
|
|
120
111
|
export type ScopeExtensionServiceOptions = LogNameOptions;
|
|
121
112
|
|
|
113
|
+
const NO_DESCRIPTIONS: ScopeExtensionDescriptions = { local: [], universal: [] };
|
|
114
|
+
|
|
122
115
|
/**
|
|
123
116
|
* Default {@link ScopeExtensionService} implementation. Per-language
|
|
124
117
|
* registry for dynamic scope contributions: extra resolvable descriptions
|
|
@@ -128,13 +121,19 @@ export type ScopeExtensionServiceOptions = LogNameOptions;
|
|
|
128
121
|
* separate from the query-time scope-resolution concern (which only reads).
|
|
129
122
|
*
|
|
130
123
|
* Lives in the per-language `references` group alongside `ScopeProvider`,
|
|
131
|
-
* which consumes
|
|
132
|
-
* / {@link getUniversalExtensionScope}). Designed as a base class; adopters
|
|
124
|
+
* which consumes {@link getDescriptions}. Designed as a base class; adopters
|
|
133
125
|
* extend it and call `register` in their constructor.
|
|
126
|
+
*
|
|
127
|
+
* At `debug`, each extension's calls and self-time go into one profile
|
|
128
|
+
* session, reported when a build starts and when it ends. A report thus
|
|
129
|
+
* covers one build, validation included, or the calls made between two
|
|
130
|
+
* builds, such as a reference picker's.
|
|
134
131
|
*/
|
|
135
132
|
export class DefaultScopeExtensionService implements ScopeExtensionService {
|
|
136
133
|
protected readonly scopeExtensions = new Registry<ScopeExtension>();
|
|
137
134
|
protected readonly tracer: Tracer;
|
|
135
|
+
/** Open from the first profiled call until {@link reportProfile}. */
|
|
136
|
+
protected profileSession?: ProfileSession;
|
|
138
137
|
|
|
139
138
|
constructor(
|
|
140
139
|
protected readonly services: ServerLanguageServices,
|
|
@@ -150,6 +149,10 @@ export class DefaultScopeExtensionService implements ScopeExtensionService {
|
|
|
150
149
|
for (const contribution of Object.values(contributions)) {
|
|
151
150
|
contribution.registerScopeExtensions(this);
|
|
152
151
|
}
|
|
152
|
+
// Optional for the same reason: a stub tree may carry no builder.
|
|
153
|
+
const builder = services.shared.workspace.DocumentBuilder;
|
|
154
|
+
builder?.onUpdate(() => this.reportProfile());
|
|
155
|
+
builder?.onBuildEnded(() => this.reportProfile());
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
/**
|
|
@@ -162,46 +165,58 @@ export class DefaultScopeExtensionService implements ScopeExtensionService {
|
|
|
162
165
|
return this.scopeExtensions.register(extension);
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
getLocalExtensionScope(referenceType: string, context: AstNode, outerScope: Scope): Scope {
|
|
166
|
-
const local = this.collectExtensionDescriptions(referenceType, context, 'local');
|
|
167
|
-
if (local.length === 0) {
|
|
168
|
-
return outerScope;
|
|
169
|
-
}
|
|
170
|
-
return new StreamScope(stream(local), outerScope);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
getUniversalExtensionScope(referenceType: string, context: AstNode, outerScope: Scope): Scope {
|
|
174
|
-
const universal = this.collectExtensionDescriptions(referenceType, context, 'universal');
|
|
175
|
-
if (universal.length === 0) {
|
|
176
|
-
return outerScope;
|
|
177
|
-
}
|
|
178
|
-
return new StreamScope(outerScope.getAllElements(), new MapScope(universal));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
168
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* `push`-ed descriptions of other tiers are silently dropped here —
|
|
185
|
-
* the per-tier query methods only return descriptions matching their
|
|
186
|
-
* tier. (`local` and `universal` are the only acceptor entry points;
|
|
187
|
-
* `push` is the escape hatch for pre-built descriptions and is the
|
|
188
|
-
* only path that could carry other tiers.)
|
|
169
|
+
* Holds the profile wrap, which an override would have to copy; to change
|
|
170
|
+
* which extensions run, override {@link extensionsFor}.
|
|
189
171
|
*/
|
|
190
|
-
|
|
191
|
-
referenceType
|
|
192
|
-
context: AstNode,
|
|
193
|
-
tier: 'local' | 'universal'
|
|
194
|
-
): TieredAstNodeDescription[] {
|
|
195
|
-
const extensionsForType = this.scopeExtensions.all().filter(extension => extension.referenceTypes.includes(referenceType));
|
|
172
|
+
getDescriptions(referenceType: string, context: AstNode): ScopeExtensionDescriptions {
|
|
173
|
+
const extensionsForType = this.extensionsFor(referenceType, context);
|
|
196
174
|
if (extensionsForType.length === 0) {
|
|
197
|
-
return
|
|
175
|
+
return NO_DESCRIPTIONS;
|
|
198
176
|
}
|
|
199
177
|
const document = AstUtils.getDocument(context);
|
|
200
178
|
const collected: TieredAstNodeDescription[] = [];
|
|
201
179
|
const accept = createScopeDescriptionAcceptor(collected, this.services.workspace.AstNodeDescriptionProvider);
|
|
180
|
+
const session = Logger.isLevelEnabled('debug') ? (this.profileSession ??= this.tracer.profile('scope-extension')) : undefined;
|
|
202
181
|
for (const extension of extensionsForType) {
|
|
203
|
-
|
|
182
|
+
if (session) {
|
|
183
|
+
session.scope(extension.id, () => extension.addDescriptions(context, referenceType, document, accept));
|
|
184
|
+
} else {
|
|
185
|
+
extension.addDescriptions(context, referenceType, document, accept);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
local: collected.filter(description => description.tier === 'local'),
|
|
190
|
+
universal: collected.filter(description => description.tier === 'universal')
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The extensions that run for one reference: by default, those registered for `referenceType`. */
|
|
195
|
+
protected extensionsFor(referenceType: string, _context: AstNode): ScopeExtension[] {
|
|
196
|
+
return this.scopeExtensions.all().filter(extension => extension.referenceTypes.includes(referenceType));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Report the profile session, if one is open, and start the next one fresh:
|
|
201
|
+
* one line per extension, its calls and self-time. Not a share of the
|
|
202
|
+
* session's time, which runs from the first call to the report and so takes
|
|
203
|
+
* in whatever happened between the calls.
|
|
204
|
+
*/
|
|
205
|
+
protected reportProfile(): void {
|
|
206
|
+
const session = this.profileSession;
|
|
207
|
+
this.profileSession = undefined;
|
|
208
|
+
for (const record of session?.records() ?? []) {
|
|
209
|
+
this.tracer.debug(this.formatProfileRecord(record));
|
|
204
210
|
}
|
|
205
|
-
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The log line of one extension's {@link reportProfile} entry. It names the
|
|
215
|
+
* language: each has its own session, and an extension id is unique only
|
|
216
|
+
* within its language.
|
|
217
|
+
*/
|
|
218
|
+
protected formatProfileRecord(record: ProfileRecord): string {
|
|
219
|
+
const language = this.services.LanguageMetaData.languageId;
|
|
220
|
+
return `[profile scope-extension ${language}] ${record.id} ×${record.count} ${Format.elapsed(record.selfMs)}`;
|
|
206
221
|
}
|
|
207
222
|
}
|
|
@@ -23,6 +23,7 @@ import type { ProjectManager } from './project/project-manager.js';
|
|
|
23
23
|
import type { ExtendedServiceRegistry } from './service-registry.js';
|
|
24
24
|
import type { DocumentUriPolicy } from './workspace/document-uri-policy.js';
|
|
25
25
|
import type { HydraniumWorkspaceManager } from './workspace/hydranium-workspace-manager.js';
|
|
26
|
+
import type { HydraniumDocumentBuilder } from './document-builder/document-builder.js';
|
|
26
27
|
import type { AdditionalDocumentContribution } from './workspace/additional-document-contribution.js';
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -94,6 +95,10 @@ export interface ServerSharedServicesMinimal<TProject extends Project = Project>
|
|
|
94
95
|
// only through this narrowing — a scope provider querying a URI before
|
|
95
96
|
// the file exists is the caller that needs it.
|
|
96
97
|
/* override */ LangiumDocuments: HydraniumDocumentRegistry;
|
|
98
|
+
// Same, for the builder: only the framework's has `onBuildEnded`, which a
|
|
99
|
+
// per-build report needs because Langium's phases stay silent for a
|
|
100
|
+
// build that validates nothing.
|
|
101
|
+
/* override */ DocumentBuilder: HydraniumDocumentBuilder;
|
|
97
102
|
ProjectManager: ProjectManager<TProject>;
|
|
98
103
|
SelfSaveRegistry: SelfSaveRegistry;
|
|
99
104
|
BuildPipelineIntegration: BuildPipelineIntegration;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
+
import { type Clock, SystemClock } from '@hydranium/protocol';
|
|
10
11
|
import * as fsp from 'node:fs/promises';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -37,6 +38,7 @@ export const RENAME_BUDGET_MS = 10_000;
|
|
|
37
38
|
export interface RenameOverOpenReadersOptions {
|
|
38
39
|
readonly rename?: (from: string, to: string) => Promise<void>;
|
|
39
40
|
readonly budgetMs?: number;
|
|
41
|
+
readonly clock?: Clock;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -60,18 +62,21 @@ export async function renameOverOpenReaders(
|
|
|
60
62
|
options: RenameOverOpenReadersOptions = {}
|
|
61
63
|
): Promise<void> {
|
|
62
64
|
const rename = options.rename ?? fsp.rename;
|
|
63
|
-
const
|
|
65
|
+
const clock = options.clock ?? new SystemClock();
|
|
66
|
+
const deadline = clock.now() + (options.budgetMs ?? RENAME_BUDGET_MS);
|
|
64
67
|
for (let attempt = 0; ; attempt++) {
|
|
65
68
|
try {
|
|
66
69
|
await rename(staging, destination);
|
|
67
70
|
return;
|
|
68
71
|
} catch (err: unknown) {
|
|
69
72
|
const code = err instanceof Error && 'code' in err ? String((err as NodeJS.ErrnoException).code) : undefined;
|
|
70
|
-
if (code === undefined || !RENAME_CONTENTION_CODES.has(code) ||
|
|
73
|
+
if (code === undefined || !RENAME_CONTENTION_CODES.has(code) || clock.now() >= deadline) {
|
|
71
74
|
throw err;
|
|
72
75
|
}
|
|
73
76
|
// Backs off to keep a long contention window from spinning, capped so
|
|
74
77
|
// a late attempt still lands promptly once the handle is released.
|
|
78
|
+
// Not `clock.setTimer`: it unrefs its timer, so a process with only
|
|
79
|
+
// this write pending could exit mid-backoff.
|
|
75
80
|
await new Promise(resolve => setTimeout(resolve, Math.min(2 ** attempt, 50)));
|
|
76
81
|
}
|
|
77
82
|
}
|
|
@@ -83,6 +83,10 @@ export interface CaptureServerLogOptions {
|
|
|
83
83
|
*
|
|
84
84
|
* The server's own threshold is left alone: it governs what the client sees,
|
|
85
85
|
* not the file, and the setting replaces it within moments of startup.
|
|
86
|
+
*
|
|
87
|
+
* The server runs every path guarded on a level this admits, its debug
|
|
88
|
+
* profiling included, so a timing run captured at a level that admits debug
|
|
89
|
+
* measures that work too.
|
|
86
90
|
*/
|
|
87
91
|
level?: LogThreshold;
|
|
88
92
|
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
type WorkspaceLock
|
|
20
20
|
} from '@hydranium/langium';
|
|
21
21
|
import { type CancellationToken, Disposable } from 'vscode-languageserver';
|
|
22
|
-
import { HydraniumDocumentBuilder } from '../langium/document-builder/document-builder.js';
|
|
22
|
+
import { type BuildEndedEvent, HydraniumDocumentBuilder } from '../langium/document-builder/document-builder.js';
|
|
23
23
|
|
|
24
24
|
/** Recorded call to a stubbed {@link DocumentBuilder} method. */
|
|
25
25
|
export interface RecordedBuilderCall<TArgs extends unknown[]> {
|
|
@@ -77,7 +77,7 @@ export interface StubWaitUntilGate {
|
|
|
77
77
|
export interface StubDocumentBuilder
|
|
78
78
|
extends
|
|
79
79
|
Pick<DocumentBuilder, 'update' | 'onDocumentPhase' | 'onUpdate' | 'build' | 'onBuildPhase' | 'resetToState' | 'updateBuildOptions'>,
|
|
80
|
-
Pick<HydraniumDocumentBuilder, 'onDocumentPhaseDelivered' | 'scheduleUpdate' | 'finalBuildState'> {
|
|
80
|
+
Pick<HydraniumDocumentBuilder, 'onDocumentPhaseDelivered' | 'scheduleUpdate' | 'finalBuildState' | 'onBuildEnded'> {
|
|
81
81
|
/**
|
|
82
82
|
* Single-overload stub of {@link DocumentBuilder.waitUntil}. Real has
|
|
83
83
|
* two overloads (`(state, cancelToken?): Promise<void>` and
|
|
@@ -107,6 +107,13 @@ export interface StubDocumentBuilder
|
|
|
107
107
|
* looking wired.
|
|
108
108
|
*/
|
|
109
109
|
fireBuildPhase(state: DocumentState, built: LangiumDocument[], cancelToken?: CancellationToken): void;
|
|
110
|
+
/**
|
|
111
|
+
* Synchronously fire every {@link HydraniumDocumentBuilder.onBuildEnded}
|
|
112
|
+
* listener with `event`. As in the real builder, one that throws does not
|
|
113
|
+
* stop the others; the real builder logs the error, and this stub, having
|
|
114
|
+
* no logger, rethrows the first one once all have run.
|
|
115
|
+
*/
|
|
116
|
+
fireBuildEnded(event: BuildEndedEvent): void;
|
|
110
117
|
/**
|
|
111
118
|
* Hold the next {@link waitUntil} call. The returned handle releases it;
|
|
112
119
|
* the call's return value resolves on the next tick after `resolve` runs.
|
|
@@ -152,6 +159,7 @@ export function makeStubDocumentBuilder(workspaceLock?: WorkspaceLock): StubDocu
|
|
|
152
159
|
const deliveredListeners = new Map<DocumentState, Array<(document: LangiumDocument, version: number) => void>>();
|
|
153
160
|
const buildPhaseListeners = new Map<DocumentState, DocumentBuildListener[]>();
|
|
154
161
|
const onUpdateListeners: DocumentUpdateListener[] = [];
|
|
162
|
+
const buildEndedListeners: Array<(event: BuildEndedEvent) => void> = [];
|
|
155
163
|
const gates: Array<{ take(release: () => void): void }> = [];
|
|
156
164
|
const updateCalls: RecordedBuilderCall<[URI[], URI[]]>[] = [];
|
|
157
165
|
const waitUntilCalls: RecordedBuilderCall<[DocumentState, URI | undefined]>[] = [];
|
|
@@ -258,6 +266,28 @@ export function makeStubDocumentBuilder(workspaceLock?: WorkspaceLock): StubDocu
|
|
|
258
266
|
reraise(listener(changed, deleted));
|
|
259
267
|
}
|
|
260
268
|
},
|
|
269
|
+
onBuildEnded(listener: (event: BuildEndedEvent) => void) {
|
|
270
|
+
buildEndedListeners.push(listener);
|
|
271
|
+
return Disposable.create(() => {
|
|
272
|
+
const idx = buildEndedListeners.indexOf(listener);
|
|
273
|
+
if (idx >= 0) {
|
|
274
|
+
buildEndedListeners.splice(idx, 1);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
fireBuildEnded(event: BuildEndedEvent) {
|
|
279
|
+
const failures: unknown[] = [];
|
|
280
|
+
for (const listener of buildEndedListeners.slice()) {
|
|
281
|
+
try {
|
|
282
|
+
listener(event);
|
|
283
|
+
} catch (error: unknown) {
|
|
284
|
+
failures.push(error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (failures.length > 0) {
|
|
288
|
+
throw failures[0];
|
|
289
|
+
}
|
|
290
|
+
},
|
|
261
291
|
onBuildPhase(state: DocumentState, listener: DocumentBuildListener) {
|
|
262
292
|
const list = buildPhaseListeners.get(state) ?? [];
|
|
263
293
|
list.push(listener);
|
|
@@ -317,6 +347,7 @@ export function makeStubDocumentBuilder(workspaceLock?: WorkspaceLock): StubDocu
|
|
|
317
347
|
phaseListeners.clear();
|
|
318
348
|
deliveredListeners.clear();
|
|
319
349
|
onUpdateListeners.length = 0;
|
|
350
|
+
buildEndedListeners.length = 0;
|
|
320
351
|
gates.length = 0;
|
|
321
352
|
updateCalls.length = 0;
|
|
322
353
|
waitUntilCalls.length = 0;
|