diff --git a/packages/langium/src/workspace/document-builder.ts b/packages/langium/src/workspace/document-builder.ts index 65611ad8a..6823b99ae 100644 --- a/packages/langium/src/workspace/document-builder.ts +++ b/packages/langium/src/workspace/document-builder.ts @@ -81,7 +81,7 @@ export interface DocumentBuilder { onBuildPhase(targetState: DocumentState, callback: DocumentBuildListener): Disposable; } -export type DocumentUpdateListener = (changed: URI[], deleted: URI[]) => void +export type DocumentUpdateListener = (changed: URI[], deleted: URI[]) => void | Promise export type DocumentBuildListener = (built: LangiumDocument[], cancelToken: CancellationToken) => void | Promise export class DefaultDocumentBuilder implements DocumentBuilder { @@ -144,6 +144,7 @@ export class DefaultDocumentBuilder implements DocumentBuilder { this.buildState.delete(key); } } + await this.emitUpdate(documents.map(e => e.uri), []); await this.buildDocuments(documents, options, cancelToken); } @@ -173,9 +174,7 @@ export class DefaultDocumentBuilder implements DocumentBuilder { doc.diagnostics = undefined; }); // Notify listeners of the update - for (const listener of this.updateListeners) { - listener(changed, deleted); - } + await this.emitUpdate(changed, deleted); // Only allow interrupting the execution after all state changes are done await interruptAndCheck(cancelToken); @@ -191,6 +190,10 @@ export class DefaultDocumentBuilder implements DocumentBuilder { await this.buildDocuments(rebuildDocuments, this.updateBuildOptions, cancelToken); } + protected async emitUpdate(changed: URI[], deleted: URI[]): Promise { + await Promise.all(this.updateListeners.map(listener => listener(changed, deleted))); + } + /** * Check whether the given document should be relinked after changes were found in the given URIs. */ diff --git a/packages/langium/test/grammar/type-system/inferred-types.test.ts b/packages/langium/test/grammar/type-system/inferred-types.test.ts index 42acfac55..de6376113 100644 --- a/packages/langium/test/grammar/type-system/inferred-types.test.ts +++ b/packages/langium/test/grammar/type-system/inferred-types.test.ts @@ -292,7 +292,7 @@ describe('Inferred types', () => { }); test('Should infer data type rules as unions', async () => { - expectTypes(` + await expectTypes(` Strings returns string: 'a' | 'b' | 'c'; MoreStrings returns string: Strings | 'd' | 'e'; Complex returns string: ID ('.' ID)*; @@ -323,7 +323,7 @@ describe('Inferred types', () => { }); test('Infers X as a super interface of Y and Z with property `id`', async () => { - expectTypes(` + await expectTypes(` entry X: id=ID ({infer Y} 'a' | {infer Z} 'b'); terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; `, expandToString` @@ -343,7 +343,7 @@ describe('Inferred types', () => { describe('inferred types that are used by the grammar', () => { test('B is defined and A is not', async () => { - expectTypes(` + await expectTypes(` A infers B: 'a' name=ID (otherA=[B])?; hidden terminal WS: /\\s+/; terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; @@ -359,7 +359,7 @@ describe('inferred types that are used by the grammar', () => { describe('inferred and declared types', () => { test('Declared interfaces should be preserved as interfaces', async () => { - expectTypes(` + await expectTypes(` X returns X: Y | Z; Y: y='y'; Z: z='z'; @@ -881,10 +881,10 @@ describe('generated types from declared types include all of them', () => { // }); const services = createLangiumGrammarServices(EmptyFileSystem).grammar; +const helper = parseHelper(services); async function getTypes(grammar: string): Promise { await clearDocuments(services); - const helper = parseHelper(services); const result = await helper(grammar); const gram = result.parseResult.value; return collectAst(gram); diff --git a/packages/langium/test/workspace/document-builder.test.ts b/packages/langium/test/workspace/document-builder.test.ts index f76d028d7..2e2304b88 100644 --- a/packages/langium/test/workspace/document-builder.test.ts +++ b/packages/langium/test/workspace/document-builder.test.ts @@ -45,6 +45,42 @@ describe('DefaultDocumentBuilder', () => { return services; } + test('emits `onUpdate` on `update` call', async () => { + const services = await createServices(); + const documentFactory = services.shared.workspace.LangiumDocumentFactory; + const documents = services.shared.workspace.LangiumDocuments; + const document = documentFactory.fromString('', URI.parse('file:///test1.txt')); + documents.addDocument(document); + + const builder = services.shared.workspace.DocumentBuilder; + await builder.build([document], {}); + addTextDocument(document, services); + let called = false; + builder.onUpdate(() => { + called = true; + }); + await builder.update([document.uri], []); + expect(called).toBe(true); + }); + + test('emits `onUpdate` on `build` call', async () => { + const services = await createServices(); + const documentFactory = services.shared.workspace.LangiumDocumentFactory; + const documents = services.shared.workspace.LangiumDocuments; + const document = documentFactory.fromString('', URI.parse('file:///test1.txt')); + documents.addDocument(document); + + const builder = services.shared.workspace.DocumentBuilder; + await builder.build([document], {}); + addTextDocument(document, services); + let called = false; + builder.onUpdate(() => { + called = true; + }); + await builder.build([document]); + expect(called).toBe(true); + }); + test('resumes document build after cancellation', async () => { const services = await createServices(); const documentFactory = services.shared.workspace.LangiumDocumentFactory;