@likec4/language-server 1.45.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 +3 -0
- package/dist/LikeC4LanguageServices.js +2 -0
- package/dist/Rpc.js +13 -2
- package/dist/ast.d.ts +10 -0
- package/dist/ast.js +7 -0
- package/dist/bundled.mjs +3819 -4062
- package/dist/filesystem/ChokidarWatcher.js +2 -2
- package/dist/filesystem/LikeC4FileSystem.js +5 -1
- 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/lsp/DocumentSymbolProvider.js +12 -1
- package/dist/mcp/server/WithMCPServer.js +0 -2
- package/dist/mcp/tools/read-deployment.js +18 -0
- package/dist/mcp/tools/read-element.js +24 -0
- package/dist/model/builder/buildModel.js +70 -1
- package/dist/model/fqn-index.js +8 -2
- package/dist/model/model-parser.d.ts +3 -0
- package/dist/model/model-parser.js +7 -0
- 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/ViewsParser.d.ts +1 -0
- package/dist/model/parser/ViewsParser.js +13 -0
- package/dist/model-change/ModelChanges.js +6 -3
- 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/views/LikeC4ManualLayouts.js +2 -2
- package/dist/views/LikeC4Views.js +2 -2
- package/dist/workspace/ProjectsManager.d.ts +26 -1
- package/dist/workspace/ProjectsManager.js +98 -12
- package/dist/workspace/WorkspaceManager.js +38 -0
- package/package.json +17 -17
|
@@ -1,4 +1,4 @@
|
|
|
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
3
|
import { wrapError } from '@likec4/log';
|
|
4
4
|
import { deepEqual } from 'fast-equals';
|
|
@@ -32,6 +32,7 @@ const DefaultProject = {
|
|
|
32
32
|
exclude: ['**/node_modules/**'],
|
|
33
33
|
},
|
|
34
34
|
exclude: picomatch('**/node_modules/**', { dot: true }),
|
|
35
|
+
includeConfig: { paths: [], maxDepth: 3, fileThreshold: 30 },
|
|
35
36
|
};
|
|
36
37
|
export class ProjectsManager {
|
|
37
38
|
services;
|
|
@@ -106,6 +107,7 @@ export class ProjectsManager {
|
|
|
106
107
|
folder: ProjectFolder(folderUri),
|
|
107
108
|
folderUri,
|
|
108
109
|
exclude: DefaultProject.exclude,
|
|
110
|
+
includeConfig: DefaultProject.includeConfig,
|
|
109
111
|
};
|
|
110
112
|
}
|
|
111
113
|
this.#defaultProject = project;
|
|
@@ -148,11 +150,12 @@ export class ProjectsManager {
|
|
|
148
150
|
folderUri,
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
|
-
const
|
|
153
|
+
const project = nonNullable(this.#projects.find(p => p.id === id), `Project "${id}" not found`);
|
|
152
154
|
return {
|
|
153
155
|
id,
|
|
154
|
-
folderUri,
|
|
155
|
-
config,
|
|
156
|
+
folderUri: project.folderUri,
|
|
157
|
+
config: project.config,
|
|
158
|
+
...(project.includePaths && { includePaths: project.includePaths.map(p => p.uri) }),
|
|
156
159
|
};
|
|
157
160
|
}
|
|
158
161
|
/**
|
|
@@ -251,11 +254,13 @@ export class ProjectsManager {
|
|
|
251
254
|
logger.warn `Project "${config.name}" already exists, generating unique ID`;
|
|
252
255
|
}
|
|
253
256
|
id = this.uniqueProjectId(config.name);
|
|
257
|
+
const includeConfig = normalizeIncludeConfig(config.include);
|
|
254
258
|
project = {
|
|
255
259
|
id,
|
|
256
260
|
config,
|
|
257
261
|
folder,
|
|
258
262
|
folderUri: URI.parse(folder),
|
|
263
|
+
includeConfig,
|
|
259
264
|
};
|
|
260
265
|
// if there is any project within subfolder or parent folder
|
|
261
266
|
// we need to reset assigned to documents project IDs
|
|
@@ -278,6 +283,8 @@ export class ProjectsManager {
|
|
|
278
283
|
logger.info `update project ${project.id} on config change`;
|
|
279
284
|
}
|
|
280
285
|
project.config = config;
|
|
286
|
+
const includeConfig = normalizeIncludeConfig(config.include);
|
|
287
|
+
project.includeConfig = includeConfig;
|
|
281
288
|
}
|
|
282
289
|
// Reset cached default project
|
|
283
290
|
this.#defaultProject = undefined;
|
|
@@ -287,6 +294,48 @@ export class ProjectsManager {
|
|
|
287
294
|
else if (hasAtLeast(config.exclude, 1)) {
|
|
288
295
|
project.exclude = picomatch(config.exclude, { dot: true });
|
|
289
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
|
+
}
|
|
290
339
|
this.#projectIdToFolder.set(project.id, folder);
|
|
291
340
|
// Reset assigned project IDs if no projects reload is active
|
|
292
341
|
if (mustReset && !this.#activeReload) {
|
|
@@ -402,36 +451,43 @@ export class ProjectsManager {
|
|
|
402
451
|
await this.rebuidProject(projectId, ct);
|
|
403
452
|
});
|
|
404
453
|
}
|
|
454
|
+
logger.info `rebuild project ${projectId}`;
|
|
405
455
|
// reset default project cache
|
|
406
456
|
this.#defaultProject = undefined;
|
|
407
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);
|
|
408
462
|
const folder = project.folder;
|
|
463
|
+
const includePathStrings = project.includePaths?.map(p => p.folder) ?? [];
|
|
409
464
|
const docs = this.services.workspace.LangiumDocuments
|
|
410
465
|
.all
|
|
411
466
|
.filter(doc => {
|
|
412
467
|
if (project.exclude?.(doc.uri.path)) {
|
|
413
468
|
return false;
|
|
414
469
|
}
|
|
415
|
-
|
|
470
|
+
const docUriStr = doc.uri.toString();
|
|
471
|
+
if (docUriStr.startsWith(folder)) {
|
|
416
472
|
return true;
|
|
417
473
|
}
|
|
418
|
-
|
|
474
|
+
if (includePathStrings.some(includePath => docUriStr.startsWith(includePath))) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
const docdir = withTrailingSlash(joinRelativeURL(docUriStr, '..'));
|
|
419
478
|
return docdir.startsWith(folder) || folder.startsWith(docdir);
|
|
420
479
|
})
|
|
421
480
|
.map(d => d.uri)
|
|
422
481
|
.toArray();
|
|
423
482
|
if (docs.length > 0) {
|
|
424
483
|
this.reset();
|
|
425
|
-
|
|
426
|
-
logger.info('rebuild documents of project {projectId}: {docs}', {
|
|
427
|
-
projectId,
|
|
484
|
+
log.info('rebuild documents: {docs}', {
|
|
428
485
|
docs: docs.length,
|
|
429
486
|
});
|
|
430
487
|
await this.services.workspace.DocumentBuilder
|
|
431
488
|
.update(docs, [], cancelToken)
|
|
432
489
|
.catch(error => {
|
|
433
|
-
|
|
434
|
-
projectId,
|
|
490
|
+
log.warn('Failed to rebuild project', {
|
|
435
491
|
error,
|
|
436
492
|
});
|
|
437
493
|
});
|
|
@@ -440,8 +496,19 @@ export class ProjectsManager {
|
|
|
440
496
|
findProjectForDocument(documentUri) {
|
|
441
497
|
return this.mappingsToProject.get(documentUri, () => {
|
|
442
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
|
+
}
|
|
443
510
|
// If the document is not part of any project, assign it to the global project ID
|
|
444
|
-
return
|
|
511
|
+
return this.default;
|
|
445
512
|
});
|
|
446
513
|
}
|
|
447
514
|
// The mapping between document URIs and their corresponding project ID
|
|
@@ -456,6 +523,25 @@ export class ProjectsManager {
|
|
|
456
523
|
get documentBelongsTo() {
|
|
457
524
|
return memoizeProp(this, '_documentBelongsTo', () => new WorkspaceCache(this.services));
|
|
458
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
|
+
}
|
|
459
545
|
getWorkspaceFolder() {
|
|
460
546
|
try {
|
|
461
547
|
return this.services.workspace.WorkspaceManager.workspaceUri;
|
|
@@ -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
|
/**
|
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,19 +76,19 @@
|
|
|
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
81
|
"esbuild": "0.27.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@hono/node-server": "^1.19.6",
|
|
85
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
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",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
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
100
|
"immer": "^10.2.0",
|
|
101
101
|
"indent-string": "^5.0.0",
|
|
102
102
|
"json5": "^2.2.3",
|
|
@@ -104,21 +104,21 @@
|
|
|
104
104
|
"langium-cli": "3.5.2",
|
|
105
105
|
"nano-spawn": "^1.0.3",
|
|
106
106
|
"natural-compare-lite": "^1.4.0",
|
|
107
|
-
"oxlint": "1.
|
|
107
|
+
"oxlint": "1.31.0",
|
|
108
108
|
"p-debounce": "4.0.0",
|
|
109
109
|
"p-queue": "8.1.1",
|
|
110
110
|
"p-timeout": "6.1.4",
|
|
111
111
|
"picomatch": "^4.0.3",
|
|
112
|
-
"pretty-ms": "^9.
|
|
112
|
+
"pretty-ms": "^9.3.0",
|
|
113
113
|
"remeda": "^2.32.0",
|
|
114
114
|
"strip-indent": "^4.1.1",
|
|
115
|
-
"tsx": "4.
|
|
116
|
-
"turbo": "2.6.
|
|
115
|
+
"tsx": "4.21.0",
|
|
116
|
+
"turbo": "2.6.3",
|
|
117
117
|
"type-fest": "^4.41.0",
|
|
118
118
|
"typescript": "5.9.3",
|
|
119
119
|
"ufo": "1.6.1",
|
|
120
120
|
"unbuild": "3.5.0",
|
|
121
|
-
"vitest": "4.0.
|
|
121
|
+
"vitest": "4.0.15",
|
|
122
122
|
"vscode-jsonrpc": "8.2.1",
|
|
123
123
|
"vscode-languageserver": "9.0.1",
|
|
124
124
|
"vscode-languageserver-protocol": "3.17.5",
|
|
@@ -126,13 +126,13 @@
|
|
|
126
126
|
"vscode-uri": "3.1.0",
|
|
127
127
|
"which": "^5.0.0",
|
|
128
128
|
"zod": "^3.25.76",
|
|
129
|
-
"@likec4/config": "1.
|
|
130
|
-
"@likec4/core": "1.
|
|
129
|
+
"@likec4/config": "1.46.0",
|
|
130
|
+
"@likec4/core": "1.46.0",
|
|
131
131
|
"@likec4/devops": "1.42.0",
|
|
132
|
-
"@likec4/icons": "1.
|
|
133
|
-
"@likec4/layouts": "1.
|
|
134
|
-
"@likec4/log": "1.
|
|
135
|
-
"@likec4/tsconfig": "1.
|
|
132
|
+
"@likec4/icons": "1.46.0",
|
|
133
|
+
"@likec4/layouts": "1.46.0",
|
|
134
|
+
"@likec4/log": "1.46.0",
|
|
135
|
+
"@likec4/tsconfig": "1.46.0"
|
|
136
136
|
},
|
|
137
137
|
"scripts": {
|
|
138
138
|
"typecheck": "tsc -b --verbose",
|