@likec4/language-server 1.44.0 → 1.46.0
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/dist/LikeC4LanguageServices.d.ts +4 -15
- package/dist/LikeC4LanguageServices.js +4 -32
- package/dist/Rpc.js +44 -21
- package/dist/ast.d.ts +10 -0
- package/dist/ast.js +13 -2
- package/dist/browser.js +2 -2
- package/dist/bundled.js +2 -0
- package/dist/bundled.mjs +3838 -4059
- package/dist/filesystem/ChokidarWatcher.d.ts +2 -0
- package/dist/filesystem/ChokidarWatcher.js +29 -18
- package/dist/filesystem/LikeC4FileSystem.js +4 -0
- package/dist/filesystem/index.d.ts +2 -0
- package/dist/generated/ast.d.ts +46 -9
- package/dist/generated/ast.js +56 -4
- package/dist/generated/grammar.js +1 -1
- package/dist/generated-lib/icons.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -3
- package/dist/lsp/DocumentSymbolProvider.js +12 -1
- package/dist/mcp/server/StdioLikeC4MCPServer.js +10 -6
- package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -97
- package/dist/mcp/server/WithMCPServer.js +4 -6
- package/dist/mcp/tools/read-deployment.js +18 -0
- package/dist/mcp/tools/read-element.js +24 -0
- package/dist/mcp/tools/search-element.js +5 -5
- package/dist/mcp/utils.js +1 -1
- package/dist/model/builder/buildModel.js +70 -1
- package/dist/model/deployments-index.js +2 -2
- package/dist/model/fqn-index.d.ts +1 -2
- package/dist/model/fqn-index.js +21 -18
- package/dist/model/model-builder.js +0 -2
- package/dist/model/model-parser.d.ts +3 -0
- package/dist/model/model-parser.js +41 -27
- package/dist/model/parser/Base.js +8 -3
- package/dist/model/parser/GlobalsParser.d.ts +1 -0
- package/dist/model/parser/ModelParser.d.ts +2 -1
- package/dist/model/parser/ModelParser.js +45 -1
- package/dist/model/parser/SpecificationParser.js +4 -0
- package/dist/model/parser/ViewsParser.d.ts +1 -0
- package/dist/model/parser/ViewsParser.js +16 -1
- package/dist/model-change/ModelChanges.d.ts +2 -2
- package/dist/model-change/ModelChanges.js +41 -11
- package/dist/protocol.d.ts +33 -10
- package/dist/protocol.js +13 -4
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +11 -1
- package/dist/validation/relation.d.ts +1 -0
- package/dist/validation/relation.js +87 -1
- package/dist/validation/view-checks.d.ts +4 -0
- package/dist/validation/view-checks.js +46 -0
- package/dist/view-utils/manual-layout.js +2 -4
- package/dist/views/LikeC4ManualLayouts.d.ts +16 -2
- package/dist/views/LikeC4ManualLayouts.js +100 -23
- package/dist/views/LikeC4Views.d.ts +26 -5
- package/dist/views/LikeC4Views.js +49 -33
- package/dist/workspace/AstNodeDescriptionProvider.js +6 -3
- package/dist/workspace/IndexManager.js +1 -1
- package/dist/workspace/LangiumDocuments.d.ts +3 -2
- package/dist/workspace/LangiumDocuments.js +29 -15
- package/dist/workspace/ProjectsManager.d.ts +45 -16
- package/dist/workspace/ProjectsManager.js +227 -45
- package/dist/workspace/WorkspaceManager.js +43 -0
- package/package.json +22 -21
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { isLikeC4Config, validateProjectConfig, } from '@likec4/config';
|
|
1
|
+
import { isLikeC4Config, normalizeIncludeConfig, validateProjectConfig, } from '@likec4/config';
|
|
2
2
|
import { BiMap, delay, invariant, memoizeProp, nonNullable } from '@likec4/core/utils';
|
|
3
|
+
import { wrapError } from '@likec4/log';
|
|
3
4
|
import { deepEqual } from 'fast-equals';
|
|
4
5
|
import { interruptAndCheck, URI, WorkspaceCache, } from 'langium';
|
|
5
6
|
import picomatch from 'picomatch';
|
|
6
7
|
import { hasAtLeast, isNullish, map, pipe, prop, sortBy } from 'remeda';
|
|
7
8
|
import { joinRelativeURL, parseFilename, withoutProtocol, withTrailingSlash, } from 'ufo';
|
|
9
|
+
import { isLikeC4Builtin } from '../likec4lib';
|
|
8
10
|
import { logger as mainLogger } from '../logger';
|
|
9
11
|
const logger = mainLogger.getChild('ProjectsManager');
|
|
10
12
|
function normalizeUri(uri) {
|
|
@@ -30,6 +32,7 @@ const DefaultProject = {
|
|
|
30
32
|
exclude: ['**/node_modules/**'],
|
|
31
33
|
},
|
|
32
34
|
exclude: picomatch('**/node_modules/**', { dot: true }),
|
|
35
|
+
includeConfig: { paths: [], maxDepth: 3, fileThreshold: 30 },
|
|
33
36
|
};
|
|
34
37
|
export class ProjectsManager {
|
|
35
38
|
services;
|
|
@@ -43,6 +46,10 @@ export class ProjectsManager {
|
|
|
43
46
|
* (it is used in CLI and Vite plugin)
|
|
44
47
|
*/
|
|
45
48
|
#defaultProjectId = undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Cached default project.
|
|
51
|
+
*/
|
|
52
|
+
#defaultProject = undefined;
|
|
46
53
|
/**
|
|
47
54
|
* The mapping between project config files and project IDs.
|
|
48
55
|
*/
|
|
@@ -78,6 +85,7 @@ export class ProjectsManager {
|
|
|
78
85
|
if (id === this.#defaultProjectId) {
|
|
79
86
|
return;
|
|
80
87
|
}
|
|
88
|
+
this.#defaultProject = undefined;
|
|
81
89
|
if (!id || id === ProjectsManager.DefaultProjectId) {
|
|
82
90
|
logger.debug `reset default project ID`;
|
|
83
91
|
this.#defaultProjectId = undefined;
|
|
@@ -87,6 +95,25 @@ export class ProjectsManager {
|
|
|
87
95
|
logger.debug `set default project ID to ${id}`;
|
|
88
96
|
this.#defaultProjectId = id;
|
|
89
97
|
}
|
|
98
|
+
get default() {
|
|
99
|
+
if (!this.#defaultProject) {
|
|
100
|
+
const id = this.defaultProjectId ?? ProjectsManager.DefaultProjectId;
|
|
101
|
+
let project = this.#projects.find(p => p.id === id);
|
|
102
|
+
if (!project) {
|
|
103
|
+
const folderUri = this.getWorkspaceFolder();
|
|
104
|
+
project = {
|
|
105
|
+
id,
|
|
106
|
+
config: DefaultProject.config,
|
|
107
|
+
folder: ProjectFolder(folderUri),
|
|
108
|
+
folderUri,
|
|
109
|
+
exclude: DefaultProject.exclude,
|
|
110
|
+
includeConfig: DefaultProject.includeConfig,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
this.#defaultProject = project;
|
|
114
|
+
}
|
|
115
|
+
return this.#defaultProject;
|
|
116
|
+
}
|
|
90
117
|
get all() {
|
|
91
118
|
if (hasAtLeast(this.#projects, 1)) {
|
|
92
119
|
const ids = [
|
|
@@ -114,7 +141,7 @@ export class ProjectsManager {
|
|
|
114
141
|
}
|
|
115
142
|
catch (error) {
|
|
116
143
|
logger.warn('Failed to get workspace URI, using default folder', { error });
|
|
117
|
-
folderUri = URI.file('');
|
|
144
|
+
folderUri = URI.file('/');
|
|
118
145
|
// ignore - workspace not initialized
|
|
119
146
|
}
|
|
120
147
|
return {
|
|
@@ -123,11 +150,12 @@ export class ProjectsManager {
|
|
|
123
150
|
folderUri,
|
|
124
151
|
};
|
|
125
152
|
}
|
|
126
|
-
const
|
|
153
|
+
const project = nonNullable(this.#projects.find(p => p.id === id), `Project "${id}" not found`);
|
|
127
154
|
return {
|
|
128
155
|
id,
|
|
129
|
-
folderUri,
|
|
130
|
-
config,
|
|
156
|
+
folderUri: project.folderUri,
|
|
157
|
+
config: project.config,
|
|
158
|
+
...(project.includePaths && { includePaths: project.includePaths.map(p => p.uri) }),
|
|
131
159
|
};
|
|
132
160
|
}
|
|
133
161
|
/**
|
|
@@ -188,42 +216,31 @@ export class ProjectsManager {
|
|
|
188
216
|
return isConfigFile;
|
|
189
217
|
}
|
|
190
218
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* @param entry The file system entry to check
|
|
219
|
+
* Registers likec4 project by config file.
|
|
194
220
|
*/
|
|
195
221
|
async registerConfigFile(configFile) {
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
return await this.registerProject(configFile);
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
this.services.lsp.Connection?.window.showErrorMessage(`LikeC4: Failed to register project at ${configFile.fsPath}`);
|
|
202
|
-
logger.warn('Failed to register project at {uri}', { uri: configFile.fsPath, error });
|
|
203
|
-
return undefined;
|
|
204
|
-
}
|
|
222
|
+
if (DefaultProject.exclude(configFile.path)) {
|
|
223
|
+
throw new Error(`Path to ${configFile.fsPath} is excluded by: ${DefaultProject.config.exclude.join(', ')}`);
|
|
205
224
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* If there is some project registered at same folder, it will be reloaded.
|
|
211
|
-
*/
|
|
212
|
-
async registerProject(opts) {
|
|
213
|
-
if (URI.isUri(opts)) {
|
|
214
|
-
const configFile = opts;
|
|
225
|
+
if (!this.isConfigFile(configFile)) {
|
|
226
|
+
throw new Error(`${configFile.fsPath} is not a valid LikeC4 config filename.`);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
215
229
|
const config = await this.services.workspace.FileSystemProvider.loadProjectConfig(configFile);
|
|
216
230
|
const path = joinRelativeURL(configFile.path, '..');
|
|
217
231
|
const folderUri = configFile.with({ path });
|
|
218
|
-
return this.
|
|
232
|
+
return await this.registerProject({ config, folderUri });
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
this.services.lsp.Connection?.window.showErrorMessage(`LikeC4: Failed to register project at ${configFile.fsPath}`);
|
|
236
|
+
throw wrapError(error, `Failed to register project config ${configFile.fsPath}:\n`);
|
|
219
237
|
}
|
|
220
|
-
return this._registerProject(opts);
|
|
221
238
|
}
|
|
222
239
|
/**
|
|
223
240
|
* Registers (or reloads) likec4 project by config file or config object.
|
|
224
241
|
* If there is some project registered at same folder, it will be reloaded.
|
|
225
242
|
*/
|
|
226
|
-
|
|
243
|
+
async registerProject(opts) {
|
|
227
244
|
const config = validateProjectConfig(opts.config);
|
|
228
245
|
const folder = ProjectFolder(opts.folderUri);
|
|
229
246
|
let project = this.#projects.find(p => p.folder === folder);
|
|
@@ -237,11 +254,13 @@ export class ProjectsManager {
|
|
|
237
254
|
logger.warn `Project "${config.name}" already exists, generating unique ID`;
|
|
238
255
|
}
|
|
239
256
|
id = this.uniqueProjectId(config.name);
|
|
257
|
+
const includeConfig = normalizeIncludeConfig(config.include);
|
|
240
258
|
project = {
|
|
241
259
|
id,
|
|
242
260
|
config,
|
|
243
261
|
folder,
|
|
244
262
|
folderUri: URI.parse(folder),
|
|
263
|
+
includeConfig,
|
|
245
264
|
};
|
|
246
265
|
// if there is any project within subfolder or parent folder
|
|
247
266
|
// we need to reset assigned to documents project IDs
|
|
@@ -264,22 +283,83 @@ export class ProjectsManager {
|
|
|
264
283
|
logger.info `update project ${project.id} on config change`;
|
|
265
284
|
}
|
|
266
285
|
project.config = config;
|
|
286
|
+
const includeConfig = normalizeIncludeConfig(config.include);
|
|
287
|
+
project.includeConfig = includeConfig;
|
|
267
288
|
}
|
|
289
|
+
// Reset cached default project
|
|
290
|
+
this.#defaultProject = undefined;
|
|
268
291
|
if (isNullish(config.exclude)) {
|
|
269
292
|
project.exclude = DefaultProject.exclude;
|
|
270
293
|
}
|
|
271
294
|
else if (hasAtLeast(config.exclude, 1)) {
|
|
272
295
|
project.exclude = picomatch(config.exclude, { dot: true });
|
|
273
296
|
}
|
|
297
|
+
// Resolve include paths relative to project folder
|
|
298
|
+
if (project.includeConfig.paths && hasAtLeast(project.includeConfig.paths, 1)) {
|
|
299
|
+
project.includePaths = project.includeConfig.paths.map(includePath => {
|
|
300
|
+
const resolvedPath = joinRelativeURL(project.folderUri.path, includePath);
|
|
301
|
+
const uri = project.folderUri.with({ path: resolvedPath });
|
|
302
|
+
return {
|
|
303
|
+
uri,
|
|
304
|
+
folder: ProjectFolder(uri),
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
logger.debug `project ${project.id} include paths: ${project.includePaths.map(p => p.uri.fsPath).join(', ')}`;
|
|
308
|
+
// Check for overlapping include paths with other projects
|
|
309
|
+
for (const includePath of project.includePaths) {
|
|
310
|
+
// Check if this include path overlaps with another project's folder
|
|
311
|
+
for (const otherProject of this.#projects) {
|
|
312
|
+
if (otherProject.id === project.id)
|
|
313
|
+
continue;
|
|
314
|
+
if (includePath.folder.startsWith(otherProject.folder) || otherProject.folder.startsWith(includePath.folder)) {
|
|
315
|
+
logger.warn('Project "{projectId}" include path "{includePath}" overlaps with project "{otherProjectId}" folder. ' +
|
|
316
|
+
'Files in overlapping areas will only belong to one project.', { projectId: project.id, includePath: includePath.folder, otherProjectId: otherProject.id });
|
|
317
|
+
}
|
|
318
|
+
// Check if this include path overlaps with another project's include paths
|
|
319
|
+
if (otherProject.includePaths) {
|
|
320
|
+
for (const otherIncludePath of otherProject.includePaths) {
|
|
321
|
+
if (includePath.folder.startsWith(otherIncludePath.folder)
|
|
322
|
+
|| otherIncludePath.folder.startsWith(includePath.folder)) {
|
|
323
|
+
logger.warn('Project "{projectId}" include path "{includePath}" overlaps with project "{otherProjectId}" ' +
|
|
324
|
+
'include path "{otherIncludePath}". Files in overlapping areas will only belong to one project.', {
|
|
325
|
+
projectId: project.id,
|
|
326
|
+
includePath: includePath.folder,
|
|
327
|
+
otherProjectId: otherProject.id,
|
|
328
|
+
otherIncludePath: otherIncludePath.folder,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
delete project.includePaths;
|
|
338
|
+
}
|
|
274
339
|
this.#projectIdToFolder.set(project.id, folder);
|
|
275
|
-
if
|
|
276
|
-
|
|
340
|
+
// Reset assigned project IDs if no projects reload is active
|
|
341
|
+
if (mustReset && !this.#activeReload) {
|
|
342
|
+
await this.rebuidProject(project.id).catch(error => {
|
|
343
|
+
logger.warn('Failed to rebuild project {projectId} after config change', {
|
|
344
|
+
projectId: project.id,
|
|
345
|
+
error,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
277
348
|
}
|
|
278
349
|
return project;
|
|
279
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Determines which project the given document belongs to.
|
|
353
|
+
* If the document does not belong to any project, returns the default project ID.
|
|
354
|
+
*/
|
|
280
355
|
belongsTo(document) {
|
|
281
|
-
|
|
282
|
-
|
|
356
|
+
if (URI.isUri(document) || typeof document === 'string') {
|
|
357
|
+
const documentUri = normalizeUri(document);
|
|
358
|
+
return this.findProjectForDocument(documentUri).id;
|
|
359
|
+
}
|
|
360
|
+
return this.documentBelongsTo.get(document, () => {
|
|
361
|
+
return this.findProjectForDocument(normalizeUri(document.uri));
|
|
362
|
+
}).id;
|
|
283
363
|
}
|
|
284
364
|
#activeReload = null;
|
|
285
365
|
async reloadProjects() {
|
|
@@ -306,13 +386,16 @@ export class ProjectsManager {
|
|
|
306
386
|
return;
|
|
307
387
|
}
|
|
308
388
|
await this.services.workspace.WorkspaceLock.write(async (cancelToken) => {
|
|
389
|
+
logger.debug `start reload projects`;
|
|
309
390
|
const configFiles = [];
|
|
310
391
|
for (const folder of folders) {
|
|
311
392
|
try {
|
|
393
|
+
logger.debug `scan projects in folder ${folder.uri}`;
|
|
312
394
|
const files = await this.services.workspace.FileSystemProvider.scanProjectFiles(URI.parse(folder.uri));
|
|
313
395
|
for (const file of files) {
|
|
314
396
|
if (file.isFile && this.isConfigFile(file.uri)) {
|
|
315
|
-
|
|
397
|
+
logger.debug `found config ${file.uri.fsPath}`;
|
|
398
|
+
configFiles.push(file.uri);
|
|
316
399
|
}
|
|
317
400
|
}
|
|
318
401
|
}
|
|
@@ -324,19 +407,18 @@ export class ProjectsManager {
|
|
|
324
407
|
logger.warning('No config files found, but some projects were registered before');
|
|
325
408
|
}
|
|
326
409
|
await interruptAndCheck(cancelToken);
|
|
327
|
-
logger.debug `start reload projects`;
|
|
328
410
|
this.#projects = [];
|
|
329
411
|
this.#projectIdToFolder.clear();
|
|
330
|
-
for (const
|
|
412
|
+
for (const uri of configFiles) {
|
|
331
413
|
try {
|
|
332
|
-
await this.registerConfigFile(
|
|
414
|
+
await this.registerConfigFile(uri);
|
|
333
415
|
}
|
|
334
416
|
catch (error) {
|
|
335
|
-
logger.error('Failed to load config file {uri}', { uri:
|
|
417
|
+
logger.error('Failed to load config file {uri}', { uri: uri.fsPath, error });
|
|
336
418
|
}
|
|
337
419
|
}
|
|
338
|
-
this.
|
|
339
|
-
await this.
|
|
420
|
+
this.reset();
|
|
421
|
+
await this.services.workspace.WorkspaceManager.rebuildAll(cancelToken);
|
|
340
422
|
});
|
|
341
423
|
}
|
|
342
424
|
uniqueProjectId(name) {
|
|
@@ -347,22 +429,86 @@ export class ProjectsManager {
|
|
|
347
429
|
}
|
|
348
430
|
return id;
|
|
349
431
|
}
|
|
350
|
-
|
|
432
|
+
reset() {
|
|
433
|
+
this.#defaultProject = undefined;
|
|
351
434
|
if (this.#defaultProjectId && !this.#projectIdToFolder.has(this.#defaultProjectId)) {
|
|
352
435
|
this.#defaultProjectId = undefined;
|
|
353
436
|
}
|
|
437
|
+
this.services.workspace.LangiumDocuments.all.forEach(doc => {
|
|
438
|
+
if (isLikeC4Builtin(doc.uri)) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
// Remove assigned project ID to force re-calculation
|
|
442
|
+
delete doc.likec4ProjectId;
|
|
443
|
+
});
|
|
444
|
+
this.documentBelongsTo.clear();
|
|
354
445
|
this.mappingsToProject.clear();
|
|
355
446
|
this.#excludedDocuments = new WeakMap();
|
|
356
|
-
this.services.workspace.LangiumDocuments.resetProjectIds();
|
|
357
447
|
}
|
|
358
|
-
async
|
|
359
|
-
|
|
448
|
+
async rebuidProject(projectId, cancelToken) {
|
|
449
|
+
if (!cancelToken) {
|
|
450
|
+
return await this.services.workspace.WorkspaceLock.write(async (ct) => {
|
|
451
|
+
await this.rebuidProject(projectId, ct);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
logger.info `rebuild project ${projectId}`;
|
|
455
|
+
// reset default project cache
|
|
456
|
+
this.#defaultProject = undefined;
|
|
457
|
+
const project = this.#projects.find(p => p.id === projectId) ?? this.default;
|
|
458
|
+
if (project.id !== projectId) {
|
|
459
|
+
logger.warn `Project ${projectId} not found, rebuilding default project ${project.id}`;
|
|
460
|
+
}
|
|
461
|
+
const log = logger.getChild(project.id);
|
|
462
|
+
const folder = project.folder;
|
|
463
|
+
const includePathStrings = project.includePaths?.map(p => p.folder) ?? [];
|
|
464
|
+
const docs = this.services.workspace.LangiumDocuments
|
|
465
|
+
.all
|
|
466
|
+
.filter(doc => {
|
|
467
|
+
if (project.exclude?.(doc.uri.path)) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
const docUriStr = doc.uri.toString();
|
|
471
|
+
if (docUriStr.startsWith(folder)) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
if (includePathStrings.some(includePath => docUriStr.startsWith(includePath))) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
const docdir = withTrailingSlash(joinRelativeURL(docUriStr, '..'));
|
|
478
|
+
return docdir.startsWith(folder) || folder.startsWith(docdir);
|
|
479
|
+
})
|
|
480
|
+
.map(d => d.uri)
|
|
481
|
+
.toArray();
|
|
482
|
+
if (docs.length > 0) {
|
|
483
|
+
this.reset();
|
|
484
|
+
log.info('rebuild documents: {docs}', {
|
|
485
|
+
docs: docs.length,
|
|
486
|
+
});
|
|
487
|
+
await this.services.workspace.DocumentBuilder
|
|
488
|
+
.update(docs, [], cancelToken)
|
|
489
|
+
.catch(error => {
|
|
490
|
+
log.warn('Failed to rebuild project', {
|
|
491
|
+
error,
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
}
|
|
360
495
|
}
|
|
361
496
|
findProjectForDocument(documentUri) {
|
|
362
497
|
return this.mappingsToProject.get(documentUri, () => {
|
|
363
498
|
const project = this.#projects.find(({ folder }) => documentUri.startsWith(folder));
|
|
499
|
+
if (project) {
|
|
500
|
+
return project;
|
|
501
|
+
}
|
|
502
|
+
const projectWithInclude = this.#projects.find(({ includePaths }) => {
|
|
503
|
+
if (!includePaths)
|
|
504
|
+
return false;
|
|
505
|
+
return includePaths.some(includePath => documentUri.startsWith(includePath.folder));
|
|
506
|
+
});
|
|
507
|
+
if (projectWithInclude) {
|
|
508
|
+
return projectWithInclude;
|
|
509
|
+
}
|
|
364
510
|
// If the document is not part of any project, assign it to the global project ID
|
|
365
|
-
return
|
|
511
|
+
return this.default;
|
|
366
512
|
});
|
|
367
513
|
}
|
|
368
514
|
// The mapping between document URIs and their corresponding project ID
|
|
@@ -370,4 +516,40 @@ export class ProjectsManager {
|
|
|
370
516
|
get mappingsToProject() {
|
|
371
517
|
return memoizeProp(this, '_mappingsToProject', () => new WorkspaceCache(this.services));
|
|
372
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* The mapping between documents and projects they belong to.
|
|
521
|
+
* Lazy-created due to initialization order of the LanguageServer
|
|
522
|
+
*/
|
|
523
|
+
get documentBelongsTo() {
|
|
524
|
+
return memoizeProp(this, '_documentBelongsTo', () => new WorkspaceCache(this.services));
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Returns all include paths from all projects.
|
|
528
|
+
* Used by WorkspaceManager to scan additional directories for C4 files.
|
|
529
|
+
*/
|
|
530
|
+
getAllIncludePaths() {
|
|
531
|
+
const result = [];
|
|
532
|
+
for (const project of this.#projects) {
|
|
533
|
+
if (project.includePaths) {
|
|
534
|
+
for (const includePath of project.includePaths) {
|
|
535
|
+
result.push({
|
|
536
|
+
projectId: project.id,
|
|
537
|
+
includePath: includePath.uri,
|
|
538
|
+
includeConfig: project.includeConfig,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
getWorkspaceFolder() {
|
|
546
|
+
try {
|
|
547
|
+
return this.services.workspace.WorkspaceManager.workspaceUri;
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
logger.warn('Failed to get workspace URI, using default folder', { error });
|
|
551
|
+
return URI.file('/');
|
|
552
|
+
// ignore - workspace not initialized
|
|
553
|
+
}
|
|
554
|
+
}
|
|
373
555
|
}
|
|
@@ -54,6 +54,44 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
|
54
54
|
*/
|
|
55
55
|
async loadAdditionalDocuments(folders, collector) {
|
|
56
56
|
collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)));
|
|
57
|
+
// Load documents from project include paths
|
|
58
|
+
const includePaths = this.services.workspace.ProjectsManager.getAllIncludePaths();
|
|
59
|
+
let totalFilesLoaded = 0;
|
|
60
|
+
for (const { projectId, includePath, includeConfig } of includePaths) {
|
|
61
|
+
try {
|
|
62
|
+
logger.debug `scanning include path ${includePath.fsPath} for project ${projectId}`;
|
|
63
|
+
const files = await this.fileSystemProvider.readDirectory(includePath, {
|
|
64
|
+
recursive: true,
|
|
65
|
+
maxDepth: includeConfig.maxDepth,
|
|
66
|
+
});
|
|
67
|
+
let filesLoadedFromPath = 0;
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (file.isFile && !this.services.workspace.ProjectsManager.isExcluded(file.uri)) {
|
|
70
|
+
const doc = await this.documentFactory.fromUri(file.uri);
|
|
71
|
+
collector(doc);
|
|
72
|
+
filesLoadedFromPath++;
|
|
73
|
+
totalFilesLoaded++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
logger.debug `loaded ${filesLoadedFromPath} files from include path ${includePath.fsPath}`;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
logWarnError(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Warn if total files loaded exceeds threshold across all include paths
|
|
83
|
+
if (includePaths.length > 0 && totalFilesLoaded > 0) {
|
|
84
|
+
// Get the minimum threshold from all projects
|
|
85
|
+
const minThreshold = Math.min(...includePaths.map(p => p.includeConfig.fileThreshold));
|
|
86
|
+
if (totalFilesLoaded > minThreshold) {
|
|
87
|
+
logger.warn(`Loaded ${totalFilesLoaded} files from include paths (threshold: ${minThreshold}). ` +
|
|
88
|
+
'Large include directories may slow workspace initialization. ' +
|
|
89
|
+
'Consider adjusting "include.fileThreshold" or "include.maxDepth" in your project configuration.');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
logger.info `loaded ${totalFilesLoaded} total files from ${includePaths.length} include paths`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
57
95
|
await super.loadAdditionalDocuments(folders, collector);
|
|
58
96
|
}
|
|
59
97
|
/**
|
|
@@ -75,6 +113,11 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
|
75
113
|
return null;
|
|
76
114
|
}
|
|
77
115
|
async rebuildAll(cancelToken) {
|
|
116
|
+
if (!cancelToken) {
|
|
117
|
+
return await this.services.workspace.WorkspaceLock.write(async (ct) => {
|
|
118
|
+
await this.rebuildAll(ct);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
78
121
|
const docs = this.services.workspace.LangiumDocuments.all.map(d => d.uri).toArray();
|
|
79
122
|
logger.info('invalidate and rebuild all {docs} documents', { docs: docs.length });
|
|
80
123
|
this.services.workspace.Cache.clear();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/language-server",
|
|
3
3
|
"description": "LikeC4 Language Server",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.46.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -76,48 +76,49 @@
|
|
|
76
76
|
"access": "public"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@hpcc-js/wasm-graphviz": "1.
|
|
79
|
+
"@hpcc-js/wasm-graphviz": "1.16.0",
|
|
80
80
|
"bundle-require": "^5.1.0",
|
|
81
|
-
"esbuild": "0.
|
|
81
|
+
"esbuild": "0.27.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@hono/node-server": "^1.19.
|
|
85
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
84
|
+
"@hono/node-server": "^1.19.6",
|
|
85
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
86
86
|
"@msgpack/msgpack": "^3.1.2",
|
|
87
87
|
"@smithy/util-base64": "^4.3.0",
|
|
88
88
|
"@types/natural-compare-lite": "^1.4.2",
|
|
89
|
-
"@types/node": "~
|
|
89
|
+
"@types/node": "~22.19.1",
|
|
90
90
|
"@types/picomatch": "^4.0.2",
|
|
91
|
-
"@types/vscode": "^1.
|
|
91
|
+
"@types/vscode": "^1.106.1",
|
|
92
92
|
"@types/which": "^3.0.4",
|
|
93
93
|
"chokidar": "^4.0.3",
|
|
94
94
|
"defu": "^6.1.4",
|
|
95
95
|
"esm-env": "^1.2.2",
|
|
96
|
-
"fast-equals": "^5.3.
|
|
96
|
+
"fast-equals": "^5.3.3",
|
|
97
97
|
"fdir": "6.4.0",
|
|
98
98
|
"fetch-to-node": "^2.1.0",
|
|
99
|
-
"hono": "^4.10.
|
|
99
|
+
"hono": "^4.10.7",
|
|
100
|
+
"immer": "^10.2.0",
|
|
100
101
|
"indent-string": "^5.0.0",
|
|
101
102
|
"json5": "^2.2.3",
|
|
102
103
|
"langium": "3.5.0",
|
|
103
104
|
"langium-cli": "3.5.2",
|
|
104
105
|
"nano-spawn": "^1.0.3",
|
|
105
106
|
"natural-compare-lite": "^1.4.0",
|
|
106
|
-
"oxlint": "1.
|
|
107
|
+
"oxlint": "1.31.0",
|
|
107
108
|
"p-debounce": "4.0.0",
|
|
108
109
|
"p-queue": "8.1.1",
|
|
109
110
|
"p-timeout": "6.1.4",
|
|
110
111
|
"picomatch": "^4.0.3",
|
|
111
|
-
"pretty-ms": "^9.
|
|
112
|
+
"pretty-ms": "^9.3.0",
|
|
112
113
|
"remeda": "^2.32.0",
|
|
113
114
|
"strip-indent": "^4.1.1",
|
|
114
|
-
"tsx": "4.
|
|
115
|
-
"turbo": "2.6.
|
|
115
|
+
"tsx": "4.21.0",
|
|
116
|
+
"turbo": "2.6.3",
|
|
116
117
|
"type-fest": "^4.41.0",
|
|
117
118
|
"typescript": "5.9.3",
|
|
118
119
|
"ufo": "1.6.1",
|
|
119
120
|
"unbuild": "3.5.0",
|
|
120
|
-
"vitest": "4.0.
|
|
121
|
+
"vitest": "4.0.15",
|
|
121
122
|
"vscode-jsonrpc": "8.2.1",
|
|
122
123
|
"vscode-languageserver": "9.0.1",
|
|
123
124
|
"vscode-languageserver-protocol": "3.17.5",
|
|
@@ -125,13 +126,13 @@
|
|
|
125
126
|
"vscode-uri": "3.1.0",
|
|
126
127
|
"which": "^5.0.0",
|
|
127
128
|
"zod": "^3.25.76",
|
|
128
|
-
"@likec4/
|
|
129
|
-
"@likec4/
|
|
130
|
-
"@likec4/
|
|
131
|
-
"@likec4/
|
|
132
|
-
"@likec4/
|
|
133
|
-
"@likec4/
|
|
134
|
-
"@likec4/
|
|
129
|
+
"@likec4/config": "1.46.0",
|
|
130
|
+
"@likec4/core": "1.46.0",
|
|
131
|
+
"@likec4/devops": "1.42.0",
|
|
132
|
+
"@likec4/icons": "1.46.0",
|
|
133
|
+
"@likec4/layouts": "1.46.0",
|
|
134
|
+
"@likec4/log": "1.46.0",
|
|
135
|
+
"@likec4/tsconfig": "1.46.0"
|
|
135
136
|
},
|
|
136
137
|
"scripts": {
|
|
137
138
|
"typecheck": "tsc -b --verbose",
|