@likec4/language-server 1.27.3 → 1.28.1
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.js +6 -7
- package/dist/ast.d.ts +16 -9
- package/dist/ast.js +58 -79
- package/dist/bundled.mjs +2161 -2141
- package/dist/config/schema.d.ts +3 -3
- package/dist/config/schema.js +12 -5
- package/dist/documentation/documentation-provider.js +3 -1
- package/dist/formatting/LikeC4Formatter.d.ts +0 -2
- package/dist/formatting/LikeC4Formatter.js +24 -53
- package/dist/generated/ast.d.ts +128 -233
- package/dist/generated/ast.js +136 -308
- package/dist/generated/grammar.js +1 -1
- package/dist/lsp/CompletionProvider.d.ts +3 -0
- package/dist/lsp/CompletionProvider.js +128 -113
- package/dist/lsp/DocumentLinkProvider.js +6 -3
- package/dist/lsp/HoverProvider.js +3 -1
- package/dist/lsp/SemanticTokenProvider.js +33 -43
- package/dist/model/builder/MergedSpecification.d.ts +5 -3
- package/dist/model/builder/MergedSpecification.js +21 -7
- package/dist/model/builder/buildModel.d.ts +6 -1
- package/dist/model/builder/buildModel.js +20 -15
- package/dist/model/deployments-index.js +4 -2
- package/dist/model/fqn-index.d.ts +4 -2
- package/dist/model/fqn-index.js +28 -5
- package/dist/model/model-builder.d.ts +2 -2
- package/dist/model/model-builder.js +54 -16
- package/dist/model/model-locator.js +7 -4
- package/dist/model/model-parser.d.ts +215 -52
- package/dist/model/model-parser.js +6 -2
- package/dist/model/parser/Base.d.ts +11 -2
- package/dist/model/parser/Base.js +138 -3
- package/dist/model/parser/DeploymentModelParser.d.ts +19 -2
- package/dist/model/parser/DeploymentModelParser.js +19 -29
- package/dist/model/parser/DeploymentViewParser.d.ts +18 -2
- package/dist/model/parser/DeploymentViewParser.js +6 -24
- package/dist/model/parser/FqnRefParser.d.ts +18 -3
- package/dist/model/parser/FqnRefParser.js +264 -40
- package/dist/model/parser/GlobalsParser.d.ts +35 -18
- package/dist/model/parser/ImportsParser.d.ts +32 -0
- package/dist/model/parser/ImportsParser.js +26 -0
- package/dist/model/parser/ModelParser.d.ts +26 -2
- package/dist/model/parser/ModelParser.js +21 -41
- package/dist/model/parser/PredicatesParser.d.ts +35 -12
- package/dist/model/parser/PredicatesParser.js +20 -271
- package/dist/model/parser/SpecificationParser.d.ts +8 -0
- package/dist/model/parser/SpecificationParser.js +5 -9
- package/dist/model/parser/ViewsParser.d.ts +36 -19
- package/dist/model/parser/ViewsParser.js +16 -12
- package/dist/model-change/changeElementStyle.d.ts +2 -2
- package/dist/model-change/changeElementStyle.js +9 -6
- package/dist/references/name-provider.js +8 -2
- package/dist/references/scope-computation.d.ts +1 -1
- package/dist/references/scope-computation.js +33 -3
- package/dist/references/scope-provider.d.ts +7 -8
- package/dist/references/scope-provider.js +59 -41
- package/dist/shared/NodeKindProvider.js +4 -2
- package/dist/test/testServices.d.ts +2 -0
- package/dist/test/testServices.js +4 -1
- package/dist/utils/elementRef.d.ts +1 -1
- package/dist/utils/elementRef.js +6 -1
- package/dist/utils/fqnRef.d.ts +3 -0
- package/dist/utils/fqnRef.js +15 -4
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/projectId.d.ts +2 -1
- package/dist/utils/projectId.js +11 -1
- package/dist/validation/_shared.js +2 -2
- package/dist/validation/deployment-checks.js +24 -10
- package/dist/validation/element-ref.d.ts +4 -0
- package/dist/validation/element-ref.js +12 -0
- package/dist/validation/element.d.ts +1 -1
- package/dist/validation/element.js +1 -1
- package/dist/validation/imports.d.ts +5 -0
- package/dist/validation/imports.js +30 -0
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +47 -45
- package/dist/validation/relation.d.ts +2 -2
- package/dist/validation/relation.js +24 -27
- package/dist/validation/specification.d.ts +9 -9
- package/dist/validation/specification.js +9 -9
- package/dist/validation/view-predicates/{element-with.d.ts → fqn-expr-with.d.ts} +1 -1
- package/dist/validation/view-predicates/fqn-expr-with.js +42 -0
- package/dist/validation/view-predicates/fqn-ref-expr.d.ts +4 -0
- package/dist/validation/view-predicates/fqn-ref-expr.js +53 -0
- package/dist/validation/view-predicates/incoming.d.ts +1 -1
- package/dist/validation/view-predicates/incoming.js +2 -2
- package/dist/validation/view-predicates/index.d.ts +6 -6
- package/dist/validation/view-predicates/index.js +6 -6
- package/dist/validation/view-predicates/outgoing.d.ts +1 -1
- package/dist/validation/view-predicates/outgoing.js +8 -4
- package/dist/validation/view-predicates/{expanded-element.d.ts → relation-expr.d.ts} +1 -1
- package/dist/validation/view-predicates/relation-expr.js +39 -0
- package/dist/validation/view-predicates/relation-with.d.ts +1 -1
- package/dist/validation/view-predicates/relation-with.js +8 -5
- package/dist/views/likec4-views.d.ts +1 -0
- package/dist/views/likec4-views.js +23 -4
- package/dist/workspace/AstNodeDescriptionProvider.d.ts +1 -1
- package/dist/workspace/AstNodeDescriptionProvider.js +2 -3
- package/dist/workspace/IndexManager.d.ts +1 -1
- package/dist/workspace/IndexManager.js +5 -4
- package/dist/workspace/LangiumDocuments.d.ts +1 -1
- package/dist/workspace/LangiumDocuments.js +3 -5
- package/dist/workspace/ProjectsManager.d.ts +25 -7
- package/dist/workspace/ProjectsManager.js +76 -32
- package/dist/workspace/WorkspaceManager.d.ts +4 -5
- package/dist/workspace/WorkspaceManager.js +53 -20
- package/package.json +17 -13
- package/dist/validation/dynamic-view-rule.d.ts +0 -4
- package/dist/validation/dynamic-view-rule.js +0 -17
- package/dist/validation/view-predicates/element-with.js +0 -31
- package/dist/validation/view-predicates/expanded-element.js +0 -12
- package/dist/validation/view-predicates/expression-v2.d.ts +0 -5
- package/dist/validation/view-predicates/expression-v2.js +0 -83
|
@@ -25,6 +25,7 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
|
|
|
25
25
|
private cache;
|
|
26
26
|
private viewsWithReportedErrors;
|
|
27
27
|
private ModelBuilder;
|
|
28
|
+
private queue;
|
|
28
29
|
constructor(services: LikeC4Services);
|
|
29
30
|
get layouter(): GraphvizLayouter;
|
|
30
31
|
computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { loggable } from "@likec4/log";
|
|
2
|
+
import PQueue from "p-queue";
|
|
2
3
|
import prettyMs from "pretty-ms";
|
|
3
4
|
import { values } from "remeda";
|
|
4
5
|
import { CancellationToken } from "vscode-jsonrpc";
|
|
5
6
|
import { logError, logger as rootLogger, logWarnError } from "../logger.js";
|
|
6
|
-
const logger = rootLogger.getChild("Views");
|
|
7
7
|
export class DefaultLikeC4Views {
|
|
8
8
|
constructor(services) {
|
|
9
9
|
this.services = services;
|
|
@@ -12,6 +12,7 @@ export class DefaultLikeC4Views {
|
|
|
12
12
|
cache = /* @__PURE__ */ new WeakMap();
|
|
13
13
|
viewsWithReportedErrors = /* @__PURE__ */ new Set();
|
|
14
14
|
ModelBuilder;
|
|
15
|
+
queue = new PQueue({ concurrency: 4, timeout: 1e4, throwOnTimeout: true });
|
|
15
16
|
get layouter() {
|
|
16
17
|
return this.services.likec4.Layouter;
|
|
17
18
|
}
|
|
@@ -24,19 +25,28 @@ export class DefaultLikeC4Views {
|
|
|
24
25
|
if (views.length === 0) {
|
|
25
26
|
return [];
|
|
26
27
|
}
|
|
28
|
+
const logger = rootLogger.getChild(["views", projectId ?? ""]);
|
|
27
29
|
logger.debug`layoutAll: ${views.length} views`;
|
|
28
30
|
const results = [];
|
|
29
31
|
const tasks = [];
|
|
30
32
|
for (const view of views) {
|
|
31
33
|
this.viewsWithReportedErrors.delete(view.id);
|
|
32
34
|
tasks.push(
|
|
33
|
-
|
|
35
|
+
Promise.resolve().then(async () => {
|
|
36
|
+
const result = await this.queue.add(async () => {
|
|
37
|
+
logger.debug`layouting view ${view.id}...`;
|
|
38
|
+
return await this.layouter.layout(view);
|
|
39
|
+
});
|
|
40
|
+
if (!result) {
|
|
41
|
+
return Promise.reject(new Error(`Failed to layout view ${view.id}`));
|
|
42
|
+
}
|
|
43
|
+
logger.debug`done layout view ${view.id}`;
|
|
34
44
|
this.viewsWithReportedErrors.delete(view.id);
|
|
35
45
|
this.cache.set(view, result);
|
|
36
46
|
return result;
|
|
37
47
|
}).catch((e) => {
|
|
48
|
+
logger.warn(`fail layout view ${view.id}`, { e });
|
|
38
49
|
this.cache.delete(view);
|
|
39
|
-
logWarnError(e);
|
|
40
50
|
return Promise.reject(e);
|
|
41
51
|
})
|
|
42
52
|
);
|
|
@@ -44,6 +54,8 @@ export class DefaultLikeC4Views {
|
|
|
44
54
|
for (const task of await Promise.allSettled(tasks)) {
|
|
45
55
|
if (task.status === "fulfilled") {
|
|
46
56
|
results.push(task.value);
|
|
57
|
+
} else {
|
|
58
|
+
logger.error(loggable(task.reason));
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
if (results.length !== views.length) {
|
|
@@ -56,6 +68,7 @@ export class DefaultLikeC4Views {
|
|
|
56
68
|
async layoutView(viewId, projectId, cancelToken = CancellationToken.None) {
|
|
57
69
|
const model = await this.ModelBuilder.buildLikeC4Model(projectId, cancelToken);
|
|
58
70
|
const view = model.findView(viewId)?.$view;
|
|
71
|
+
const logger = rootLogger.getChild(["views", projectId ?? ""]);
|
|
59
72
|
if (!view) {
|
|
60
73
|
logger.warn`layoutView ${viewId} not found`;
|
|
61
74
|
return null;
|
|
@@ -67,7 +80,13 @@ export class DefaultLikeC4Views {
|
|
|
67
80
|
}
|
|
68
81
|
try {
|
|
69
82
|
const start = performance.now();
|
|
70
|
-
const result = await this.
|
|
83
|
+
const result = await this.queue.add(async () => {
|
|
84
|
+
logger.debug`layouting view ${view.id}...`;
|
|
85
|
+
return await this.layouter.layout(view);
|
|
86
|
+
});
|
|
87
|
+
if (!result) {
|
|
88
|
+
throw new Error(`Failed to layout view ${viewId}`);
|
|
89
|
+
}
|
|
71
90
|
this.viewsWithReportedErrors.delete(viewId);
|
|
72
91
|
this.cache.set(view, result);
|
|
73
92
|
logger.debug(`layout {viewId} ready in ${prettyMs(performance.now() - start)}`, { viewId });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AstNode, type AstNodeDescription, type LangiumDocument, DefaultAstNodeDescriptionProvider } from 'langium';
|
|
2
2
|
import type { LikeC4Services } from '../module';
|
|
3
3
|
export declare class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
|
|
4
|
-
|
|
4
|
+
protected services: LikeC4Services;
|
|
5
5
|
constructor(services: LikeC4Services);
|
|
6
6
|
createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription;
|
|
7
7
|
}
|
|
@@ -3,15 +3,14 @@ import {
|
|
|
3
3
|
DefaultAstNodeDescriptionProvider
|
|
4
4
|
} from "langium";
|
|
5
5
|
export class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
|
|
6
|
-
projects;
|
|
7
6
|
constructor(services) {
|
|
8
7
|
super(services);
|
|
9
|
-
this.
|
|
8
|
+
this.services = services;
|
|
10
9
|
}
|
|
11
10
|
createDescription(node, name, document) {
|
|
12
11
|
const doc = document ?? AstUtils.getDocument(node);
|
|
13
12
|
const description = super.createDescription(node, name, document);
|
|
14
|
-
doc.likec4ProjectId ??= this.
|
|
13
|
+
doc.likec4ProjectId ??= this.services.shared.workspace.ProjectsManager.belongsTo(doc.uri);
|
|
15
14
|
description.likec4ProjectId = doc.likec4ProjectId;
|
|
16
15
|
return description;
|
|
17
16
|
}
|
|
@@ -3,7 +3,7 @@ import { type AstNodeDescription, type LangiumDocument, type Stream, DefaultInde
|
|
|
3
3
|
import { CancellationToken } from 'vscode-jsonrpc';
|
|
4
4
|
import type { LikeC4SharedServices } from '../module';
|
|
5
5
|
export declare class IndexManager extends DefaultIndexManager {
|
|
6
|
-
|
|
6
|
+
protected services: LikeC4SharedServices;
|
|
7
7
|
constructor(services: LikeC4SharedServices);
|
|
8
8
|
updateContent(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void>;
|
|
9
9
|
projectElements(projectId: ProjectId, nodeType?: string, uris?: Set<string>): Stream<AstNodeDescription>;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { DefaultIndexManager, stream } from "langium";
|
|
2
2
|
import { CancellationToken } from "vscode-jsonrpc";
|
|
3
3
|
export class IndexManager extends DefaultIndexManager {
|
|
4
|
-
projects;
|
|
5
4
|
constructor(services) {
|
|
6
5
|
super(services);
|
|
7
|
-
this.
|
|
6
|
+
this.services = services;
|
|
8
7
|
}
|
|
9
8
|
async updateContent(document, cancelToken = CancellationToken.None) {
|
|
10
|
-
|
|
9
|
+
const projects = this.services.workspace.ProjectsManager;
|
|
10
|
+
document.likec4ProjectId = projects.belongsTo(document.uri);
|
|
11
11
|
await super.updateContent(document, cancelToken);
|
|
12
12
|
}
|
|
13
13
|
projectElements(projectId, nodeType, uris) {
|
|
14
|
+
const projects = this.services.workspace.ProjectsManager;
|
|
14
15
|
let documentUris = stream(this.symbolIndex.keys());
|
|
15
|
-
return documentUris.filter((uri) =>
|
|
16
|
+
return documentUris.filter((uri) => projects.belongsTo(uri) === projectId && (!uris || uris.has(uri))).flatMap((uri) => this.getFileDescriptions(uri, nodeType));
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -3,7 +3,7 @@ import { type Stream, DefaultLangiumDocuments } from 'langium';
|
|
|
3
3
|
import { type LikeC4LangiumDocument } from '../ast';
|
|
4
4
|
import type { LikeC4SharedServices } from '../module';
|
|
5
5
|
export declare class LangiumDocuments extends DefaultLangiumDocuments {
|
|
6
|
-
|
|
6
|
+
protected services: LikeC4SharedServices;
|
|
7
7
|
constructor(services: LikeC4SharedServices);
|
|
8
8
|
/**
|
|
9
9
|
* Returns all user documents, excluding built-in documents.
|
|
@@ -3,22 +3,20 @@ import { groupBy, prop } from "remeda";
|
|
|
3
3
|
import { isLikeC4LangiumDocument } from "../ast.js";
|
|
4
4
|
import { isLikeC4Builtin } from "../likec4lib.js";
|
|
5
5
|
export class LangiumDocuments extends DefaultLangiumDocuments {
|
|
6
|
-
projects;
|
|
7
6
|
constructor(services) {
|
|
8
7
|
super(services);
|
|
9
|
-
this.
|
|
8
|
+
this.services = services;
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
11
|
* Returns all user documents, excluding built-in documents.
|
|
13
12
|
*/
|
|
14
13
|
get allExcludingBuiltin() {
|
|
14
|
+
const projects = this.services.workspace.ProjectsManager;
|
|
15
15
|
return super.all.filter((doc) => {
|
|
16
16
|
if (!isLikeC4LangiumDocument(doc) || isLikeC4Builtin(doc.uri)) {
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
doc.likec4ProjectId = this.projects.belongsTo(doc.uri);
|
|
21
|
-
}
|
|
19
|
+
doc.likec4ProjectId ??= projects.belongsTo(doc.uri);
|
|
22
20
|
return true;
|
|
23
21
|
});
|
|
24
22
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { NonEmptyReadonlyArray, ProjectId } from '@likec4/core';
|
|
2
2
|
import { type FileSystemNode, type LangiumDocument, URI, WorkspaceCache } from 'langium';
|
|
3
|
+
import picomatch from 'picomatch/posix';
|
|
3
4
|
import { ProjectConfig } from '../config';
|
|
4
5
|
import type { LikeC4SharedServices } from '../module';
|
|
6
|
+
interface Project {
|
|
7
|
+
id: ProjectId;
|
|
8
|
+
config: ProjectConfig;
|
|
9
|
+
folder: string;
|
|
10
|
+
exclude?: picomatch.Matcher;
|
|
11
|
+
}
|
|
5
12
|
export declare class ProjectsManager {
|
|
6
13
|
protected services: LikeC4SharedServices;
|
|
7
14
|
/**
|
|
@@ -14,35 +21,46 @@ export declare class ProjectsManager {
|
|
|
14
21
|
* The mapping between project config files and project IDs.
|
|
15
22
|
*/
|
|
16
23
|
private projectIdToFolder;
|
|
17
|
-
private _mappingsToProject;
|
|
18
24
|
/**
|
|
19
25
|
* Registered projects.
|
|
20
26
|
* Sorted descending by the number of segments in the folder path.
|
|
21
27
|
* This ensures that the most specific project is used for a document.
|
|
22
28
|
*/
|
|
23
29
|
private _projects;
|
|
30
|
+
private defaultGlobalProject;
|
|
24
31
|
constructor(services: LikeC4SharedServices);
|
|
32
|
+
/**
|
|
33
|
+
* Returns:
|
|
34
|
+
* - the default project ID if there are no projects.
|
|
35
|
+
* - the ID of the only project
|
|
36
|
+
* - undefined if there are multiple projects.
|
|
37
|
+
*/
|
|
25
38
|
get defaultProjectId(): ProjectId | undefined;
|
|
26
39
|
get all(): NonEmptyReadonlyArray<ProjectId>;
|
|
27
|
-
getProject(
|
|
40
|
+
getProject(arg: ProjectId | LangiumDocument): {
|
|
41
|
+
id: ProjectId;
|
|
28
42
|
folder: URI;
|
|
29
43
|
config: Readonly<ProjectConfig>;
|
|
30
44
|
};
|
|
31
45
|
ensureProjectId(projectId?: ProjectId | undefined): ProjectId;
|
|
32
46
|
hasMultipleProjects(): boolean;
|
|
47
|
+
checkIfExcluded(documentUri: URI): boolean;
|
|
48
|
+
isConfigFile(entry: FileSystemNode): boolean;
|
|
33
49
|
/**
|
|
34
50
|
* Checks if the provided file system entry is a valid project config file.
|
|
35
51
|
*
|
|
36
52
|
* @param entry The file system entry to check
|
|
37
53
|
* @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
|
|
38
54
|
*/
|
|
39
|
-
loadConfigFile(entry: FileSystemNode): Promise<
|
|
40
|
-
registerProject(configFile: URI): Promise<
|
|
55
|
+
loadConfigFile(entry: FileSystemNode): Promise<Project | undefined>;
|
|
56
|
+
registerProject(configFile: URI): Promise<Project>;
|
|
41
57
|
registerProject(opts: {
|
|
42
58
|
config: ProjectConfig;
|
|
43
59
|
folderUri: URI | string;
|
|
44
|
-
}): Promise<
|
|
60
|
+
}): Promise<Project>;
|
|
45
61
|
belongsTo(document: LangiumDocument | URI | string): ProjectId;
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
protected findProjectForDocument(documentUri: string): Omit<Project, 'folder'>;
|
|
63
|
+
private _mappingsToProject;
|
|
64
|
+
protected get mappingsToProject(): WorkspaceCache<string, Omit<Project, 'folder'>>;
|
|
48
65
|
}
|
|
66
|
+
export {};
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { BiMap, invariant, nonNullable } from "@likec4/core";
|
|
2
2
|
import { URI, WorkspaceCache } from "langium";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import picomatch from "picomatch/posix";
|
|
4
|
+
import { hasAtLeast, isNullish, map, pipe, prop, sortBy } from "remeda";
|
|
5
|
+
import {
|
|
6
|
+
hasProtocol,
|
|
7
|
+
joinRelativeURL,
|
|
8
|
+
parseFilename,
|
|
9
|
+
withoutProtocol,
|
|
10
|
+
withProtocol
|
|
11
|
+
} from "ufo";
|
|
5
12
|
import { parseConfigJson } from "../config/index.js";
|
|
6
13
|
import { logger as mainLogger } from "../logger.js";
|
|
7
14
|
const logger = mainLogger.getChild("ProjectsManager");
|
|
@@ -24,14 +31,26 @@ export class ProjectsManager {
|
|
|
24
31
|
* The mapping between project config files and project IDs.
|
|
25
32
|
*/
|
|
26
33
|
projectIdToFolder = new BiMap();
|
|
27
|
-
// The mapping between document URIs and their corresponding project IDs.
|
|
28
|
-
_mappingsToProject;
|
|
29
34
|
/**
|
|
30
35
|
* Registered projects.
|
|
31
36
|
* Sorted descending by the number of segments in the folder path.
|
|
32
37
|
* This ensures that the most specific project is used for a document.
|
|
33
38
|
*/
|
|
34
39
|
_projects = [];
|
|
40
|
+
defaultGlobalProject = {
|
|
41
|
+
id: ProjectsManager.DefaultProjectId,
|
|
42
|
+
config: {
|
|
43
|
+
name: ProjectsManager.DefaultProjectId,
|
|
44
|
+
exclude: ["**/node_modules/**/*"]
|
|
45
|
+
},
|
|
46
|
+
exclude: picomatch("**/node_modules/**/*")
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Returns:
|
|
50
|
+
* - the default project ID if there are no projects.
|
|
51
|
+
* - the ID of the only project
|
|
52
|
+
* - undefined if there are multiple projects.
|
|
53
|
+
*/
|
|
35
54
|
get defaultProjectId() {
|
|
36
55
|
if (this._projects.length > 1) {
|
|
37
56
|
return void 0;
|
|
@@ -47,20 +66,24 @@ export class ProjectsManager {
|
|
|
47
66
|
}
|
|
48
67
|
return [ProjectsManager.DefaultProjectId];
|
|
49
68
|
}
|
|
50
|
-
getProject(
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
getProject(arg) {
|
|
70
|
+
const id = typeof arg === "string" ? arg : arg.likec4ProjectId || this.belongsTo(arg);
|
|
71
|
+
if (id === ProjectsManager.DefaultProjectId) {
|
|
72
|
+
const folder2 = this.services.workspace.WorkspaceManager.workspaceUri;
|
|
53
73
|
return {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
74
|
+
id,
|
|
75
|
+
folder: folder2,
|
|
76
|
+
config: this.defaultGlobalProject.config
|
|
58
77
|
};
|
|
59
78
|
}
|
|
60
|
-
const
|
|
79
|
+
const {
|
|
80
|
+
config,
|
|
81
|
+
folder
|
|
82
|
+
} = nonNullable(this._projects.find((p) => p.id === id), `Project "${id}" not found`);
|
|
61
83
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
id,
|
|
85
|
+
folder: URI.parse(folder),
|
|
86
|
+
config
|
|
64
87
|
};
|
|
65
88
|
}
|
|
66
89
|
ensureProjectId(projectId) {
|
|
@@ -79,6 +102,15 @@ export class ProjectsManager {
|
|
|
79
102
|
hasMultipleProjects() {
|
|
80
103
|
return this._projects.length > 1;
|
|
81
104
|
}
|
|
105
|
+
checkIfExcluded(documentUri) {
|
|
106
|
+
let docUriAsString = documentUri.toString();
|
|
107
|
+
const project = this.findProjectForDocument(docUriAsString);
|
|
108
|
+
return project.exclude ? project.exclude(withoutProtocol(docUriAsString)) : false;
|
|
109
|
+
}
|
|
110
|
+
isConfigFile(entry) {
|
|
111
|
+
const filename = parseFilename(entry.uri.toString(), { strict: false })?.toLowerCase();
|
|
112
|
+
return !!filename && ProjectsManager.ConfigFileNames.includes(filename);
|
|
113
|
+
}
|
|
82
114
|
/**
|
|
83
115
|
* Checks if the provided file system entry is a valid project config file.
|
|
84
116
|
*
|
|
@@ -87,17 +119,12 @@ export class ProjectsManager {
|
|
|
87
119
|
*/
|
|
88
120
|
async loadConfigFile(entry) {
|
|
89
121
|
if (entry.isDirectory) {
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
const filename = parseFilename(entry.uri.fsPath, { strict: false });
|
|
93
|
-
if (!filename) {
|
|
94
|
-
return false;
|
|
122
|
+
return void 0;
|
|
95
123
|
}
|
|
96
|
-
if (
|
|
97
|
-
await this.registerProject(entry.uri);
|
|
98
|
-
return true;
|
|
124
|
+
if (this.isConfigFile(entry)) {
|
|
125
|
+
return await this.registerProject(entry.uri);
|
|
99
126
|
}
|
|
100
|
-
return
|
|
127
|
+
return void 0;
|
|
101
128
|
}
|
|
102
129
|
async registerProject(opts) {
|
|
103
130
|
if (URI.isUri(opts)) {
|
|
@@ -109,9 +136,10 @@ export class ProjectsManager {
|
|
|
109
136
|
return this.registerProject({ config: config2, folderUri: folderUri2 });
|
|
110
137
|
}
|
|
111
138
|
const { config, folderUri } = opts;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
139
|
+
let id = config.name;
|
|
140
|
+
let i = 1;
|
|
141
|
+
while (this.projectIdToFolder.has(id)) {
|
|
142
|
+
id = `${config.name}-${i++}`;
|
|
115
143
|
}
|
|
116
144
|
let folder;
|
|
117
145
|
if (URI.isUri(folderUri)) {
|
|
@@ -119,14 +147,25 @@ export class ProjectsManager {
|
|
|
119
147
|
} else {
|
|
120
148
|
folder = hasProtocol(folderUri) ? folderUri : withProtocol(folderUri, "file://");
|
|
121
149
|
}
|
|
150
|
+
const project = {
|
|
151
|
+
id,
|
|
152
|
+
config,
|
|
153
|
+
folder
|
|
154
|
+
};
|
|
155
|
+
if (isNullish(config.exclude)) {
|
|
156
|
+
project.exclude = this.defaultGlobalProject.exclude;
|
|
157
|
+
} else if (hasAtLeast(config.exclude, 1)) {
|
|
158
|
+
project.exclude = picomatch(config.exclude);
|
|
159
|
+
}
|
|
122
160
|
this._projects = pipe(
|
|
123
|
-
[...this._projects,
|
|
161
|
+
[...this._projects, project],
|
|
124
162
|
sortBy(
|
|
125
163
|
[({ folder: folder2 }) => withoutProtocol(folder2).split("/").length, "desc"]
|
|
126
164
|
)
|
|
127
165
|
);
|
|
128
166
|
this.projectIdToFolder.set(id, folder);
|
|
129
|
-
logger.
|
|
167
|
+
logger.info`register project ${id} folder: ${folder})`;
|
|
168
|
+
return project;
|
|
130
169
|
}
|
|
131
170
|
belongsTo(document) {
|
|
132
171
|
let documentUri;
|
|
@@ -137,12 +176,17 @@ export class ProjectsManager {
|
|
|
137
176
|
} else {
|
|
138
177
|
documentUri = document.uri.toString();
|
|
139
178
|
}
|
|
140
|
-
return this.
|
|
179
|
+
return this.findProjectForDocument(documentUri).id;
|
|
141
180
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
181
|
+
findProjectForDocument(documentUri) {
|
|
182
|
+
return this.mappingsToProject.get(documentUri, () => {
|
|
183
|
+
const project = this._projects.find(({ folder }) => documentUri.startsWith(folder));
|
|
184
|
+
return project ?? this.defaultGlobalProject;
|
|
185
|
+
});
|
|
145
186
|
}
|
|
187
|
+
// The mapping between document URIs and their corresponding project ID
|
|
188
|
+
// Lazy-created due to initialization order of the LanguageServer
|
|
189
|
+
_mappingsToProject;
|
|
146
190
|
get mappingsToProject() {
|
|
147
191
|
this._mappingsToProject ??= new WorkspaceCache(this.services);
|
|
148
192
|
return this._mappingsToProject;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { LangiumDocument } from 'langium';
|
|
1
|
+
import type { FileSystemNode, LangiumDocument } from 'langium';
|
|
2
2
|
import { DefaultWorkspaceManager } from 'langium';
|
|
3
3
|
import type { WorkspaceFolder } from 'vscode-languageserver';
|
|
4
4
|
import { URI } from 'vscode-uri';
|
|
5
5
|
import type { LikeC4SharedServices } from '../module';
|
|
6
6
|
export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
7
|
+
private services;
|
|
7
8
|
private documentFactory;
|
|
8
|
-
private projects;
|
|
9
9
|
constructor(services: LikeC4SharedServices);
|
|
10
10
|
/**
|
|
11
11
|
* Load all additional documents that shall be visible in the context of the given workspace
|
|
@@ -14,10 +14,9 @@ export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
|
14
14
|
*/
|
|
15
15
|
protected loadAdditionalDocuments(folders: WorkspaceFolder[], collector: (document: LangiumDocument) => void): Promise<void>;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* This is necessary to ensure that the project config files are loaded and processed correctly.
|
|
17
|
+
* Determine whether the given folder entry shall be included while indexing the workspace.
|
|
19
18
|
*/
|
|
20
|
-
protected
|
|
19
|
+
protected includeEntry(_workspaceFolder: WorkspaceFolder, entry: FileSystemNode, fileExtensions: string[]): boolean;
|
|
21
20
|
workspace(): any;
|
|
22
21
|
get workspaceUri(): URI;
|
|
23
22
|
get workspaceURL(): URL;
|
|
@@ -2,42 +2,75 @@ import { hasAtLeast, invariant } from "@likec4/core";
|
|
|
2
2
|
import { DefaultWorkspaceManager } from "langium";
|
|
3
3
|
import { URI } from "vscode-uri";
|
|
4
4
|
import * as BuiltIn from "../likec4lib.js";
|
|
5
|
+
import { logError } from "../logger.js";
|
|
5
6
|
export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
6
|
-
documentFactory;
|
|
7
|
-
projects;
|
|
8
7
|
constructor(services) {
|
|
9
8
|
super(services);
|
|
9
|
+
this.services = services;
|
|
10
10
|
this.documentFactory = services.workspace.LangiumDocumentFactory;
|
|
11
|
-
this.projects = services.workspace.ProjectsManager;
|
|
12
11
|
}
|
|
12
|
+
documentFactory;
|
|
13
13
|
/**
|
|
14
14
|
* Load all additional documents that shall be visible in the context of the given workspace
|
|
15
15
|
* folders and add them to the collector. This can be used to include built-in libraries of
|
|
16
16
|
* your language, which can be either loaded from provided files or constructed in memory.
|
|
17
17
|
*/
|
|
18
18
|
async loadAdditionalDocuments(folders, collector) {
|
|
19
|
+
const projects = this.services.workspace.ProjectsManager;
|
|
20
|
+
for (const folder of folders) {
|
|
21
|
+
try {
|
|
22
|
+
const content = await this.fileSystemProvider.readDirectory(URI.parse(folder.uri));
|
|
23
|
+
for (const entry of content) {
|
|
24
|
+
try {
|
|
25
|
+
await projects.loadConfigFile(entry);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
logError(error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logError(error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
19
34
|
collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)));
|
|
20
35
|
await super.loadAdditionalDocuments(folders, collector);
|
|
21
36
|
}
|
|
37
|
+
// /**
|
|
38
|
+
// * We override the default implementation to process project config files during the traversal.
|
|
39
|
+
// * This is necessary to ensure that the project config files are loaded and processed correctly.
|
|
40
|
+
// */
|
|
41
|
+
// protected override async traverseFolder(
|
|
42
|
+
// workspaceFolder: WorkspaceFolder,
|
|
43
|
+
// folderPath: URI,
|
|
44
|
+
// fileExtensions: string[],
|
|
45
|
+
// collector: (document: LangiumDocument) => void,
|
|
46
|
+
// ): Promise<void> {
|
|
47
|
+
// // Then load other files
|
|
48
|
+
// for (const entry of nonConfigFiles) {
|
|
49
|
+
// try {
|
|
50
|
+
// if (this.includeEntry(workspaceFolder, entry, fileExtensions)) {
|
|
51
|
+
// if (entry.isDirectory) {
|
|
52
|
+
// await this.traverseFolder(workspaceFolder, entry.uri, fileExtensions, collector)
|
|
53
|
+
// } else if (entry.isFile) {
|
|
54
|
+
// const document = await this.langiumDocuments.getOrCreateDocument(entry.uri)
|
|
55
|
+
// collector(document)
|
|
56
|
+
// }
|
|
57
|
+
// }
|
|
58
|
+
// } catch (error) {
|
|
59
|
+
// logError(error)
|
|
60
|
+
// }
|
|
61
|
+
// }
|
|
62
|
+
// }
|
|
22
63
|
/**
|
|
23
|
-
*
|
|
24
|
-
* This is necessary to ensure that the project config files are loaded and processed correctly.
|
|
64
|
+
* Determine whether the given folder entry shall be included while indexing the workspace.
|
|
25
65
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
await this.traverseFolder(workspaceFolder, entry.uri, fileExtensions, collector);
|
|
35
|
-
} else if (entry.isFile) {
|
|
36
|
-
const document = await this.langiumDocuments.getOrCreateDocument(entry.uri);
|
|
37
|
-
collector(document);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}));
|
|
66
|
+
includeEntry(_workspaceFolder, entry, fileExtensions) {
|
|
67
|
+
if (this.services.workspace.ProjectsManager.isConfigFile(entry)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (entry.isFile) {
|
|
71
|
+
return !this.services.workspace.ProjectsManager.checkIfExcluded(entry.uri);
|
|
72
|
+
}
|
|
73
|
+
return super.includeEntry(_workspaceFolder, entry, fileExtensions);
|
|
41
74
|
}
|
|
42
75
|
workspace() {
|
|
43
76
|
if (this.folders && hasAtLeast(this.folders, 1)) {
|
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.28.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@msgpack/msgpack": "^3.1.1",
|
|
98
98
|
"@smithy/util-base64": "^4.0.0",
|
|
99
|
-
"@types/node": "^20.17.
|
|
99
|
+
"@types/node": "^20.17.30",
|
|
100
|
+
"@types/picomatch": "^4.0.0",
|
|
100
101
|
"@types/which": "^3.0.4",
|
|
101
102
|
"esm-env": "^1.2.2",
|
|
102
103
|
"fast-equals": "^5.2.2",
|
|
@@ -107,26 +108,29 @@
|
|
|
107
108
|
"langium-cli": "3.4.0",
|
|
108
109
|
"natural-compare-lite": "^1.4.0",
|
|
109
110
|
"p-debounce": "^4.0.0",
|
|
111
|
+
"p-queue": "^8.1.0",
|
|
112
|
+
"picomatch": "^4.0.2",
|
|
113
|
+
"p-timeout": "^6.1.4",
|
|
110
114
|
"pretty-ms": "^9.2.0",
|
|
111
115
|
"remeda": "^2.21.2",
|
|
112
116
|
"strip-indent": "^4.0.0",
|
|
113
117
|
"tsx": "~4.19.3",
|
|
114
|
-
"turbo": "^2.
|
|
115
|
-
"type-fest": "^4.
|
|
116
|
-
"typescript": "^5.8.
|
|
117
|
-
"ufo": "^1.
|
|
118
|
+
"turbo": "^2.5.0",
|
|
119
|
+
"type-fest": "^4.39.1",
|
|
120
|
+
"typescript": "^5.8.3",
|
|
121
|
+
"ufo": "^1.6.1",
|
|
118
122
|
"unbuild": "^3.5.0",
|
|
119
123
|
"valibot": "^1.0.0",
|
|
120
|
-
"vitest": "^3.
|
|
124
|
+
"vitest": "^3.1.1",
|
|
121
125
|
"vscode-languageserver": "9.0.1",
|
|
122
|
-
"vscode-languageserver-types": "3.17.5",
|
|
123
126
|
"vscode-languageserver-protocol": "3.17.5",
|
|
127
|
+
"vscode-languageserver-types": "3.17.5",
|
|
124
128
|
"which": "^5.0.0",
|
|
125
|
-
"@likec4/core": "1.
|
|
126
|
-
"@likec4/
|
|
127
|
-
"@likec4/
|
|
128
|
-
"@likec4/log": "1.
|
|
129
|
-
"@likec4/
|
|
129
|
+
"@likec4/core": "1.28.1",
|
|
130
|
+
"@likec4/layouts": "1.28.1",
|
|
131
|
+
"@likec4/tsconfig": "1.28.1",
|
|
132
|
+
"@likec4/log": "1.28.1",
|
|
133
|
+
"@likec4/icons": "1.28.1"
|
|
130
134
|
},
|
|
131
135
|
"scripts": {
|
|
132
136
|
"typecheck": "tsc --noEmit",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { ast, elementExpressionFromPredicate } from "../ast.js";
|
|
2
|
-
import { tryOrLog } from "./_shared.js";
|
|
3
|
-
export const dynamicViewRulePredicate = (_services) => {
|
|
4
|
-
return tryOrLog((predicate, accept) => {
|
|
5
|
-
const expr = elementExpressionFromPredicate(predicate.value);
|
|
6
|
-
switch (true) {
|
|
7
|
-
case ast.isElementKindExpression(expr):
|
|
8
|
-
case ast.isElementTagExpression(expr):
|
|
9
|
-
case ast.isWildcardExpression(expr): {
|
|
10
|
-
accept("warning", `Predicate is ignored, as not supported in dynamic views`, {
|
|
11
|
-
node: predicate
|
|
12
|
-
});
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
};
|