|
1 | 1 | import { isPlugin, Model } from '@zenstackhq/language/ast'; |
2 | 2 | import { getLiteral } from '@zenstackhq/sdk'; |
3 | | -import { DefaultWorkspaceManager, interruptAndCheck, LangiumDocument } from 'langium'; |
| 3 | +import { |
| 4 | + ConfigurationProvider, |
| 5 | + DefaultWorkspaceManager, |
| 6 | + interruptAndCheck, |
| 7 | + LangiumDocument, |
| 8 | + LangiumSharedServices, |
| 9 | +} from 'langium'; |
4 | 10 | import fs from 'fs'; |
5 | 11 | import path from 'path'; |
6 | 12 | import { CancellationToken, WorkspaceFolder } from 'vscode-languageserver'; |
7 | 13 | import { URI, Utils } from 'vscode-uri'; |
8 | 14 | import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants'; |
| 15 | +import { ZModelLanguageMetaData } from '@zenstackhq/language/generated/module'; |
9 | 16 |
|
10 | 17 | /** |
11 | 18 | * Custom Langium WorkspaceManager implementation which automatically loads stdlib.zmodel |
12 | 19 | */ |
13 | 20 | export class ZModelWorkspaceManager extends DefaultWorkspaceManager { |
| 21 | + protected readonly configurationProvider: ConfigurationProvider; |
| 22 | + |
| 23 | + constructor(services: LangiumSharedServices) { |
| 24 | + super(services); |
| 25 | + |
| 26 | + this.configurationProvider = services.workspace.ConfigurationProvider; |
| 27 | + } |
14 | 28 | public pluginModels = new Set<string>(); |
| 29 | + private entryDocumentUris: Set<string> = new Set(); |
| 30 | + |
| 31 | + public isEntryDocument(uri: string | URI): boolean { |
| 32 | + const u = typeof uri === 'string' ? uri : uri.toString(); |
| 33 | + return this.entryDocumentUris.has(u); |
| 34 | + } |
| 35 | + |
| 36 | + public getEntryDocumentUris(): Set<string> { |
| 37 | + return new Set(this.entryDocumentUris); |
| 38 | + } |
15 | 39 |
|
16 | 40 | protected async loadAdditionalDocuments( |
17 | 41 | _folders: WorkspaceFolder[], |
18 | 42 | _collector: (document: LangiumDocument) => void |
19 | 43 | ): Promise<void> { |
20 | 44 | await super.loadAdditionalDocuments(_folders, _collector); |
21 | | - |
22 | | - let stdLibPath: string; |
| 45 | + |
| 46 | + let stdLibPath: string; |
23 | 47 | // First, try to find the stdlib from an installed zenstack package |
24 | 48 | // in the project's node_modules |
25 | 49 | let installedStdlibPath: string | undefined; |
26 | 50 | for (const folder of _folders) { |
27 | 51 | const folderPath = this.getRootFolder(folder).fsPath; |
28 | 52 | try { |
29 | 53 | // Try to resolve zenstack from the workspace folder |
30 | | - const languagePackagePath = require.resolve('zenstack/package.json', { |
31 | | - paths: [folderPath] |
| 54 | + const languagePackagePath = require.resolve('zenstack/package.json', { |
| 55 | + paths: [folderPath], |
32 | 56 | }); |
33 | 57 | const languagePackageDir = path.dirname(languagePackagePath); |
34 | 58 | const candidateStdlibPath = path.join(languagePackageDir, 'res', STD_LIB_MODULE_NAME); |
35 | | - |
| 59 | + |
36 | 60 | // Check if the stdlib file exists in the installed package |
37 | 61 | if (fs.existsSync(candidateStdlibPath)) { |
38 | 62 | installedStdlibPath = candidateStdlibPath; |
39 | 63 | console.log(`Found installed zenstack package stdlib at ${installedStdlibPath}`); |
40 | 64 | break; |
41 | | - } |
| 65 | + } |
42 | 66 | } catch (error) { |
43 | 67 | // Package not found or other error, continue to next folder |
44 | 68 | continue; |
45 | 69 | } |
46 | 70 | } |
47 | | - |
| 71 | + |
48 | 72 | if (installedStdlibPath) { |
49 | 73 | stdLibPath = installedStdlibPath; |
50 | 74 | } else { |
51 | 75 | // Fallback to bundled stdlib |
52 | 76 | stdLibPath = path.join(__dirname, '../res', STD_LIB_MODULE_NAME); |
53 | 77 | console.log(`Using bundled stdlib in extension`); |
54 | 78 | } |
55 | | - |
| 79 | + |
56 | 80 | const stdLibUri = URI.file(stdLibPath); |
57 | 81 | console.log(`Adding stdlib document from ${stdLibUri}`); |
58 | 82 | const stdlib = this.langiumDocuments.getOrCreateDocument(stdLibUri); |
@@ -112,6 +136,62 @@ export class ZModelWorkspaceManager extends DefaultWorkspaceManager { |
112 | 136 | } |
113 | 137 | } |
114 | 138 |
|
| 139 | + // Save entry document URIs based on user configuration (simple mode: accept absolute file paths or file:// URIs) |
| 140 | + try { |
| 141 | + const entries = await this.configurationProvider.getConfiguration( |
| 142 | + ZModelLanguageMetaData.languageId, |
| 143 | + 'entries' |
| 144 | + ); |
| 145 | + const entryUris = new Set<string>(); |
| 146 | + if (Array.isArray(entries)) { |
| 147 | + for (const e of entries) { |
| 148 | + if (typeof e !== 'string') continue; |
| 149 | + try { |
| 150 | + if (e.startsWith('file://')) { |
| 151 | + const u = URI.parse(e); |
| 152 | + entryUris.add(u.toString()); |
| 153 | + } else if (path.isAbsolute(e)) { |
| 154 | + const u = URI.file(e); |
| 155 | + entryUris.add(u.toString()); |
| 156 | + } else { |
| 157 | + // Try resolving relative entries against workspace folders |
| 158 | + let resolved = false; |
| 159 | + for (const wf of folders) { |
| 160 | + try { |
| 161 | + const folderPath = this.getRootFolder(wf).fsPath; |
| 162 | + const candidate = path.join(folderPath, e); |
| 163 | + if (fs.existsSync(candidate)) { |
| 164 | + const u = URI.file(candidate); |
| 165 | + entryUris.add(u.toString()); |
| 166 | + resolved = true; |
| 167 | + break; |
| 168 | + } |
| 169 | + } catch { |
| 170 | + // ignore and continue with other folders |
| 171 | + } |
| 172 | + } |
| 173 | + if (!resolved) { |
| 174 | + // ignore relative/glob entries that cannot be resolved in simple mode |
| 175 | + console.warn( |
| 176 | + `zmodel.entries value ignored (not absolute/file URI/resolvable relative to workspace): ${e}` |
| 177 | + ); |
| 178 | + } |
| 179 | + } |
| 180 | + } catch { |
| 181 | + console.warn(`Invalid zmodel.entries entry ignored: ${e}`); |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + this.entryDocumentUris = entryUris; |
| 186 | + if (this.entryDocumentUris.size > 0) { |
| 187 | + console.log( |
| 188 | + `ZModelWorkspaceManager: using configured zmodel.entries: ${Array.from(this.entryDocumentUris)}` |
| 189 | + ); |
| 190 | + } |
| 191 | + } catch (err) { |
| 192 | + console.warn('Failed to read zmodel.entries configuration:', err); |
| 193 | + } |
| 194 | + |
115 | 195 | // Only after creating all documents do we check whether we need to cancel the initialization |
116 | 196 | // The document builder will later pick up on all unprocessed documents |
117 | 197 | await interruptAndCheck(cancelToken); |
|
0 commit comments