From 1f3894eb30b0c691fb960c4baefc009ec5298f45 Mon Sep 17 00:00:00 2001 From: JG Date: Thu, 6 Apr 2023 23:35:07 +0100 Subject: [PATCH 1/5] fix: Load plugin models in vscode extension --- .../zmodel-workspace-manager.ts | 104 +++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/packages/schema/src/language-server/zmodel-workspace-manager.ts b/packages/schema/src/language-server/zmodel-workspace-manager.ts index b4146cfd0..3ee6254b8 100644 --- a/packages/schema/src/language-server/zmodel-workspace-manager.ts +++ b/packages/schema/src/language-server/zmodel-workspace-manager.ts @@ -1,21 +1,115 @@ -import { DefaultWorkspaceManager, LangiumDocument } from 'langium'; -import path from 'path'; -import { WorkspaceFolder } from 'vscode-languageserver'; -import { URI } from 'vscode-uri'; -import { STD_LIB_MODULE_NAME } from './constants'; +import { isPlugin, Model } from '@zenstackhq/language/ast'; +import { getLiteral } from '@zenstackhq/sdk'; +import { DefaultWorkspaceManager, interruptAndCheck, LangiumDocument } from 'langium'; +import path, { resolve } from 'path'; +import { CancellationToken, WorkspaceFolder } from 'vscode-languageserver'; +import { URI, Utils } from 'vscode-uri'; +import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants'; /** * Custom Langium WorkspaceManager implementation which automatically loads stdlib.zmodel */ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { + public pluginModels = new Set(); + protected async loadAdditionalDocuments( _folders: WorkspaceFolder[], _collector: (document: LangiumDocument) => void ): Promise { await super.loadAdditionalDocuments(_folders, _collector); const stdLibUri = URI.file(path.join(__dirname, '../res', STD_LIB_MODULE_NAME)); + + resolve(__dirname, '../res', STD_LIB_MODULE_NAME); console.log(`Adding stdlib document from ${stdLibUri}`); const stdlib = this.langiumDocuments.getOrCreateDocument(stdLibUri); _collector(stdlib); } + + override async initializeWorkspace( + folders: WorkspaceFolder[], + cancelToken = CancellationToken.None + ): Promise { + const fileExtensions = this.serviceRegistry.all.flatMap((e) => e.LanguageMetaData.fileExtensions); + const documents: LangiumDocument[] = []; + const collector = (document: LangiumDocument) => { + documents.push(document); + if (!this.langiumDocuments.hasDocument(document.uri)) { + this.langiumDocuments.addDocument(document); + } + }; + // Even though we don't await the initialization of the workspace manager, + // we can still assume that all library documents and file documents are loaded by the time we start building documents. + // The mutex prevents anything from performing a workspace build until we check the cancellation token + await this.loadAdditionalDocuments(folders, collector); + await Promise.all( + folders + .map((wf) => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI]) + .map(async (entry) => this.traverseFolder(...entry, fileExtensions, collector)) + ); + + // find plugin models + documents.forEach((doc) => { + const parsed = doc.parseResult.value as Model; + parsed.declarations.forEach((decl) => { + if (isPlugin(decl)) { + const providerField = decl.fields.find((f) => f.name === 'provider'); + if (providerField) { + const provider = getLiteral(providerField.value); + if (provider) { + this.pluginModels.add(provider); + } + } + } + }); + }); + + console.log(`Used plugin documents: ${Array.from(this.pluginModels)}`); + + await Promise.all( + folders + .map((wf) => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI]) + .map(async (entry) => this.loadPluginModels(...entry, new Set(this.pluginModels), collector)) + ); + + // Only after creating all documents do we check whether we need to cancel the initialization + // The document builder will later pick up on all unprocessed documents + await interruptAndCheck(cancelToken); + await this.documentBuilder.build(documents, undefined, cancelToken); + } + + protected async loadPluginModels( + workspaceFolder: WorkspaceFolder, + folderPath: URI, + pluginModels: Set, + collector: (document: LangiumDocument) => void + ): Promise { + const content = await this.fileSystemProvider.readDirectory(folderPath); + + for (const entry of content) { + if (entry.isDirectory) { + const name = Utils.basename(entry.uri); + if (name === 'node_modules') { + pluginModels.forEach(async (plugin) => { + const path = Utils.joinPath(entry.uri, plugin, PLUGIN_MODULE_NAME); + try { + this.fileSystemProvider.readFileSync(path); + const document = this.langiumDocuments.getOrCreateDocument(path); + collector(document); + console.log(`Adding plugin document from ${path}`); + + pluginModels.delete(plugin); + // early exit if all plugins are loaded + if (pluginModels.size === 0) { + return; + } + } catch { + //no-op + } + }); + } else { + await this.loadPluginModels(workspaceFolder, entry.uri, pluginModels, collector); + } + } + } + } } From 7499f8672919d4b33f0fcfd40d6198df7b2269d6 Mon Sep 17 00:00:00 2001 From: JG Date: Thu, 6 Apr 2023 23:57:26 +0100 Subject: [PATCH 2/5] fix: change foreach --- .../schema/src/language-server/zmodel-workspace-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/language-server/zmodel-workspace-manager.ts b/packages/schema/src/language-server/zmodel-workspace-manager.ts index 3ee6254b8..1b370539b 100644 --- a/packages/schema/src/language-server/zmodel-workspace-manager.ts +++ b/packages/schema/src/language-server/zmodel-workspace-manager.ts @@ -89,7 +89,7 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { if (entry.isDirectory) { const name = Utils.basename(entry.uri); if (name === 'node_modules') { - pluginModels.forEach(async (plugin) => { + for (const plugin of pluginModels) { const path = Utils.joinPath(entry.uri, plugin, PLUGIN_MODULE_NAME); try { this.fileSystemProvider.readFileSync(path); @@ -105,7 +105,7 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { } catch { //no-op } - }); + } } else { await this.loadPluginModels(workspaceFolder, entry.uri, pluginModels, collector); } From 264f14daba58b069e5540729b82336558a718961 Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 7 Apr 2023 11:06:37 +0100 Subject: [PATCH 3/5] fix: resolve comments --- .../zmodel-workspace-manager.ts | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/packages/schema/src/language-server/zmodel-workspace-manager.ts b/packages/schema/src/language-server/zmodel-workspace-manager.ts index 1b370539b..633fb9481 100644 --- a/packages/schema/src/language-server/zmodel-workspace-manager.ts +++ b/packages/schema/src/language-server/zmodel-workspace-manager.ts @@ -37,9 +37,7 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { this.langiumDocuments.addDocument(document); } }; - // Even though we don't await the initialization of the workspace manager, - // we can still assume that all library documents and file documents are loaded by the time we start building documents. - // The mutex prevents anything from performing a workspace build until we check the cancellation token + await this.loadAdditionalDocuments(folders, collector); await Promise.all( folders @@ -63,13 +61,22 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { }); }); - console.log(`Used plugin documents: ${Array.from(this.pluginModels)}`); + if (this.pluginModels.size > 0) { + console.log(`Used plugin documents: ${Array.from(this.pluginModels)}`); - await Promise.all( - folders - .map((wf) => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI]) - .map(async (entry) => this.loadPluginModels(...entry, new Set(this.pluginModels), collector)) - ); + // the loaded plugin models would be removed from the set + const unLoadedPluginModels = new Set(this.pluginModels); + + await Promise.all( + folders + .map((wf) => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI]) + .map(async (entry) => this.loadPluginModels(...entry, unLoadedPluginModels, collector)) + ); + + if (unLoadedPluginModels.size > 0) { + console.warn(`The following plugin documents could not be loaded: ${Array.from(unLoadedPluginModels)}`); + } + } // Only after creating all documents do we check whether we need to cancel the initialization // The document builder will later pick up on all unprocessed documents @@ -83,19 +90,34 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { pluginModels: Set, collector: (document: LangiumDocument) => void ): Promise { - const content = await this.fileSystemProvider.readDirectory(folderPath); + const content = await ( + await this.fileSystemProvider.readDirectory(folderPath) + ).sort((a, b) => { + // make sure the node_moudules folder is always the first one to be checked + // so it could be early exited if the plugin is found + if (a.isDirectory && b.isDirectory) { + const aName = Utils.basename(a.uri); + if (aName === 'node_modules') { + return -1; + } else { + return 1; + } + } else { + return 0; + } + }); for (const entry of content) { if (entry.isDirectory) { const name = Utils.basename(entry.uri); if (name === 'node_modules') { - for (const plugin of pluginModels) { + for (const plugin of Array.from(pluginModels)) { const path = Utils.joinPath(entry.uri, plugin, PLUGIN_MODULE_NAME); try { this.fileSystemProvider.readFileSync(path); const document = this.langiumDocuments.getOrCreateDocument(path); collector(document); - console.log(`Adding plugin document from ${path}`); + console.log(`Adding plugin document from ${path.path}`); pluginModels.delete(plugin); // early exit if all plugins are loaded @@ -103,7 +125,8 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { return; } } catch { - //no-op + // no-op. The module might be found in another node_modules folder + // will show the warning message eventually if not found } } } else { From 34636fe0e4df45fa7f1a2e3f65d9cfe85d676899 Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 7 Apr 2023 13:39:15 +0100 Subject: [PATCH 4/5] fix: remove unused code --- packages/schema/src/language-server/zmodel-workspace-manager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/schema/src/language-server/zmodel-workspace-manager.ts b/packages/schema/src/language-server/zmodel-workspace-manager.ts index 633fb9481..52533dc3b 100644 --- a/packages/schema/src/language-server/zmodel-workspace-manager.ts +++ b/packages/schema/src/language-server/zmodel-workspace-manager.ts @@ -18,8 +18,6 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { ): Promise { await super.loadAdditionalDocuments(_folders, _collector); const stdLibUri = URI.file(path.join(__dirname, '../res', STD_LIB_MODULE_NAME)); - - resolve(__dirname, '../res', STD_LIB_MODULE_NAME); console.log(`Adding stdlib document from ${stdLibUri}`); const stdlib = this.langiumDocuments.getOrCreateDocument(stdLibUri); _collector(stdlib); From 2d53465e2eb3d6fd1ae6bcbdfe06614cc9e01b45 Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 7 Apr 2023 14:14:25 +0100 Subject: [PATCH 5/5] fix: fix build error --- packages/schema/src/language-server/zmodel-workspace-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema/src/language-server/zmodel-workspace-manager.ts b/packages/schema/src/language-server/zmodel-workspace-manager.ts index 52533dc3b..79b5bfb5e 100644 --- a/packages/schema/src/language-server/zmodel-workspace-manager.ts +++ b/packages/schema/src/language-server/zmodel-workspace-manager.ts @@ -1,7 +1,7 @@ import { isPlugin, Model } from '@zenstackhq/language/ast'; import { getLiteral } from '@zenstackhq/sdk'; import { DefaultWorkspaceManager, interruptAndCheck, LangiumDocument } from 'langium'; -import path, { resolve } from 'path'; +import path from 'path'; import { CancellationToken, WorkspaceFolder } from 'vscode-languageserver'; import { URI, Utils } from 'vscode-uri'; import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants';