@likec4/language-server 1.25.1 → 1.26.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.
Files changed (60) hide show
  1. package/dist/LikeC4FileSystem.js +6 -1
  2. package/dist/LikeC4LanguageServices.d.ts +77 -0
  3. package/dist/LikeC4LanguageServices.js +118 -0
  4. package/dist/Rpc.js +106 -28
  5. package/dist/ast.d.ts +10 -0
  6. package/dist/bundled.mjs +2352 -2278
  7. package/dist/config/index.d.ts +1 -0
  8. package/dist/config/index.js +1 -0
  9. package/dist/config/schema.d.ts +8 -0
  10. package/dist/config/schema.js +19 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/lsp/CodeLensProvider.js +3 -1
  13. package/dist/model/builder/buildModel.d.ts +8 -1
  14. package/dist/model/deployments-index.js +1 -1
  15. package/dist/model/fqn-index.d.ts +9 -6
  16. package/dist/model/fqn-index.js +24 -14
  17. package/dist/model/model-builder.d.ts +16 -6
  18. package/dist/model/model-builder.js +64 -50
  19. package/dist/model/model-locator.d.ts +11 -10
  20. package/dist/model/model-locator.js +32 -14
  21. package/dist/model/model-parser.d.ts +148 -148
  22. package/dist/model/model-parser.js +2 -2
  23. package/dist/model/parser/ModelParser.js +14 -2
  24. package/dist/model-change/ModelChanges.d.ts +1 -1
  25. package/dist/model-change/ModelChanges.js +2 -2
  26. package/dist/module.d.ts +9 -3
  27. package/dist/module.js +19 -5
  28. package/dist/protocol.d.ts +98 -16
  29. package/dist/protocol.js +21 -6
  30. package/dist/references/scope-provider.d.ts +24 -11
  31. package/dist/references/scope-provider.js +97 -68
  32. package/dist/shared/index.d.ts +0 -1
  33. package/dist/shared/index.js +0 -1
  34. package/dist/test/testServices.d.ts +15 -1
  35. package/dist/test/testServices.js +65 -17
  36. package/dist/utils/index.d.ts +3 -0
  37. package/dist/utils/index.js +3 -0
  38. package/dist/utils/projectId.d.ts +3 -0
  39. package/dist/utils/projectId.js +6 -0
  40. package/dist/validation/deployment-checks.js +5 -2
  41. package/dist/validation/element.js +2 -1
  42. package/dist/validation/specification.js +14 -7
  43. package/dist/validation/view.js +10 -8
  44. package/dist/views/index.d.ts +1 -1
  45. package/dist/views/index.js +1 -1
  46. package/dist/views/likec4-views.d.ts +17 -8
  47. package/dist/views/likec4-views.js +12 -11
  48. package/dist/workspace/AstNodeDescriptionProvider.d.ts +7 -0
  49. package/dist/workspace/AstNodeDescriptionProvider.js +18 -0
  50. package/dist/workspace/IndexManager.d.ts +10 -0
  51. package/dist/workspace/IndexManager.js +17 -0
  52. package/dist/workspace/LangiumDocuments.d.ts +14 -0
  53. package/dist/workspace/LangiumDocuments.js +31 -0
  54. package/dist/workspace/ProjectsManager.d.ts +48 -0
  55. package/dist/workspace/ProjectsManager.js +150 -0
  56. package/dist/{shared → workspace}/WorkspaceManager.d.ts +8 -2
  57. package/dist/{shared → workspace}/WorkspaceManager.js +22 -0
  58. package/dist/workspace/index.d.ts +5 -0
  59. package/dist/workspace/index.js +5 -0
  60. package/package.json +17 -8
@@ -14,11 +14,25 @@ export declare function createTestServices(workspace?: string): {
14
14
  errors: any;
15
15
  warnings: any;
16
16
  }>;
17
- buildModel: () => Promise<ComputedLikeC4Model>;
17
+ buildModel: () => Promise<ComputedLikeC4ModelData>;
18
18
  buildLikeC4Model: () => Promise<any>;
19
19
  resetState: () => Promise<void>;
20
20
  format: (input: string | LikeC4LangiumDocument, uri?: string) => Promise<any>;
21
21
  };
22
+ export declare function createMultiProjectTestServices<const Projects extends Record<string, Record<string, string>>>(data: Projects): Promise<{
23
+ projects: { readonly [K in keyof Projects]: { readonly [L in keyof Projects[K]]: LikeC4LangiumDocument; }; };
24
+ /**
25
+ * Add document outside of projects
26
+ */
27
+ addDocumentOutside: (input: string) => Promise<LikeC4LangiumDocument>;
28
+ validateAll: () => Promise<{
29
+ diagnostics: any;
30
+ errors: any;
31
+ warnings: any;
32
+ }>;
33
+ buildModel: (projectId: keyof Projects) => Promise<ComputedLikeC4ModelData>;
34
+ buildLikeC4Model: (projectId: keyof Projects) => Promise<any>;
35
+ }>;
22
36
  export type TestServices = ReturnType<typeof createTestServices>;
23
37
  export type TestParseFn = TestServices['validate'];
24
38
  export type TestValidateFn = TestServices['validate'];
@@ -1,5 +1,6 @@
1
- import { DocumentState, EmptyFileSystem, TextDocument } from "langium";
1
+ import { DocumentState, EmptyFileSystem, TextDocument, UriUtils } from "langium";
2
2
  import * as assert from "node:assert";
3
+ import { entries } from "remeda";
3
4
  import stripIndent from "strip-indent";
4
5
  import { DiagnosticSeverity } from "vscode-languageserver-types";
5
6
  import { URI, Utils } from "vscode-uri";
@@ -18,19 +19,21 @@ export function createTestServices(workspace = "file:///test/workspace") {
18
19
  };
19
20
  let isInitialized = false;
20
21
  let documentIndex = 1;
21
- const addDocument = async (input, uri) => {
22
- if (!isInitialized) {
23
- isInitialized = true;
24
- await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
25
- services.shared.workspace.WorkspaceManager.initialize({
26
- capabilities: {},
27
- processId: null,
28
- rootUri: null,
29
- workspaceFolders: [workspaceFolder]
30
- });
31
- await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
22
+ async function initialize() {
23
+ if (isInitialized) return;
24
+ isInitialized = true;
25
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
26
+ services.shared.workspace.WorkspaceManager.initialize({
27
+ capabilities: {},
28
+ processId: null,
29
+ rootUri: workspaceFolder.uri,
30
+ workspaceFolders: [workspaceFolder]
32
31
  });
33
- }
32
+ await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
33
+ });
34
+ }
35
+ const addDocument = async (input, uri) => {
36
+ await initialize();
34
37
  const docUri = Utils.resolvePath(
35
38
  workspaceUri,
36
39
  "./src/",
@@ -82,9 +85,7 @@ export function createTestServices(workspace = "file:///test/workspace") {
82
85
  const validateAll = async () => {
83
86
  const docs = langiumDocuments.all.toArray();
84
87
  assert.ok(docs.length > 0, "no documents to validate");
85
- await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
86
- await documentBuilder.build(docs, { validation: true }, cancelToken);
87
- });
88
+ await documentBuilder.build(docs, { validation: true });
88
89
  const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
89
90
  const warnings = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Warning ? d.message : []);
90
91
  const errors = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Error ? d.message : []);
@@ -112,7 +113,7 @@ export function createTestServices(workspace = "file:///test/workspace") {
112
113
  };
113
114
  const resetState = async () => {
114
115
  await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
115
- const docs = langiumDocuments.all.toArray().map((doc) => doc.uri);
116
+ const docs = langiumDocuments.allExcludingBuiltin.toArray().map((doc) => doc.uri);
116
117
  await documentBuilder.update([], docs, cancelToken);
117
118
  });
118
119
  };
@@ -128,3 +129,50 @@ export function createTestServices(workspace = "file:///test/workspace") {
128
129
  format
129
130
  };
130
131
  }
132
+ export async function createMultiProjectTestServices(data) {
133
+ const workspace = "file:///test/workspace";
134
+ const {
135
+ services,
136
+ addDocument,
137
+ validateAll
138
+ } = createTestServices(workspace);
139
+ const projects = {};
140
+ for (const [name, files] of entries(data)) {
141
+ const folderUri = UriUtils.joinPath(URI.parse(workspace), "src", name);
142
+ services.shared.workspace.ProjectsManager.registerProject({
143
+ config: {
144
+ name
145
+ },
146
+ folderUri
147
+ });
148
+ projects[name] = {};
149
+ for (let [docName, content] of entries(files)) {
150
+ const fileName = docName.endsWith(".c4") ? docName : `${docName}.c4`;
151
+ projects[name][docName] = await addDocument(content, `${name}/${fileName}`);
152
+ }
153
+ }
154
+ async function buildLikeC4Model(projectId) {
155
+ if (services.shared.workspace.LangiumDocuments.all.some((doc) => doc.state < DocumentState.Validated)) {
156
+ await validateAll();
157
+ }
158
+ const likec4model = await services.likec4.ModelBuilder.buildLikeC4Model(projectId);
159
+ if (!likec4model) throw new Error("No model found");
160
+ return likec4model;
161
+ }
162
+ async function buildModel(projectId) {
163
+ const model = await buildLikeC4Model(projectId);
164
+ return model.$model;
165
+ }
166
+ return {
167
+ projects,
168
+ /**
169
+ * Add document outside of projects
170
+ */
171
+ addDocumentOutside: async (input) => {
172
+ return await addDocument(input);
173
+ },
174
+ validateAll,
175
+ buildModel,
176
+ buildLikeC4Model
177
+ };
178
+ }
@@ -1,2 +1,5 @@
1
1
  export * from './disposable';
2
+ export * from './elementRef';
3
+ export * from './fqnRef';
4
+ export * from './projectId';
2
5
  export * from './stringHash';
@@ -1,2 +1,5 @@
1
1
  export * from "./disposable.js";
2
+ export * from "./elementRef.js";
3
+ export * from "./fqnRef.js";
4
+ export * from "./projectId.js";
2
5
  export * from "./stringHash.js";
@@ -0,0 +1,3 @@
1
+ import { type ProjectId } from '@likec4/core';
2
+ import { type AstNode, type LangiumDocument } from 'langium';
3
+ export declare function projectIdFrom(value: AstNode | LangiumDocument): ProjectId;
@@ -0,0 +1,6 @@
1
+ import { nonNullable } from "@likec4/core";
2
+ import { AstUtils, isAstNode } from "langium";
3
+ export function projectIdFrom(value) {
4
+ const doc = isAstNode(value) ? AstUtils.getDocument(value) : value;
5
+ return nonNullable(doc.likec4ProjectId, () => `Invalid state, document ${doc.uri} has no project ID assigned`);
6
+ }
@@ -1,6 +1,7 @@
1
1
  import { FqnRef, isSameHierarchy, nonNullable } from "@likec4/core";
2
2
  import { AstUtils } from "langium";
3
3
  import { ast } from "../ast.js";
4
+ import { projectIdFrom } from "../utils/index.js";
4
5
  import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
5
6
  const { getDocument } = AstUtils;
6
7
  export const deploymentNodeChecks = (services) => {
@@ -21,8 +22,9 @@ export const deploymentNodeChecks = (services) => {
21
22
  range
22
23
  });
23
24
  }
25
+ const projectId = projectIdFrom(el);
24
26
  const fqnName = DeploymentsIndex.getFqn(el);
25
- const withSameName = DeploymentsIndex.byFqn(fqnName).limit(2).toArray();
27
+ const withSameName = DeploymentsIndex.byFqn(projectId, fqnName).limit(2).toArray();
26
28
  if (withSameName.length > 1) {
27
29
  accept(
28
30
  "error",
@@ -53,8 +55,9 @@ export const deployedInstanceChecks = (services) => {
53
55
  range
54
56
  });
55
57
  }
58
+ const projectId = projectIdFrom(el);
56
59
  const fqnName = DeploymentsIndex.getFqn(el);
57
- const withSameName = DeploymentsIndex.byFqn(fqnName).limit(2).toArray();
60
+ const withSameName = DeploymentsIndex.byFqn(projectId, fqnName).limit(2).toArray();
58
61
  if (withSameName.length > 1) {
59
62
  accept(
60
63
  "error",
@@ -1,4 +1,5 @@
1
1
  import { AstUtils } from "langium";
2
+ import { projectIdFrom } from "../utils/index.js";
2
3
  import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
3
4
  const { getDocument } = AstUtils;
4
5
  export const elementChecks = (services) => {
@@ -22,7 +23,7 @@ export const elementChecks = (services) => {
22
23
  const doc = getDocument(el);
23
24
  const docUri = doc.uri;
24
25
  const elPath = locator.getAstNodePath(el);
25
- const withSameFqn = fqnIndex.byFqn(fqn).filter((v) => v.documentUri !== docUri || v.path !== elPath).head();
26
+ const withSameFqn = fqnIndex.byFqn(projectIdFrom(doc), fqn).filter((v) => v.documentUri !== docUri || v.path !== elPath).head();
26
27
  if (withSameFqn) {
27
28
  const isAnotherDoc = withSameFqn.documentUri !== docUri;
28
29
  accept(
@@ -1,5 +1,6 @@
1
1
  import { AstUtils } from "langium";
2
2
  import { ast } from "../ast.js";
3
+ import { projectIdFrom } from "../utils/index.js";
3
4
  import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
4
5
  export const specificationRuleChecks = (_) => {
5
6
  return tryOrLog((node, accept) => {
@@ -40,7 +41,8 @@ export const elementKindChecks = (services) => {
40
41
  property: "name"
41
42
  });
42
43
  }
43
- const sameKind = index.allElements(ast.ElementKind).filter((n) => n.name === node.name && n.node !== node).head();
44
+ const projectId = projectIdFrom(node);
45
+ const sameKind = index.projectElements(projectId, ast.ElementKind).filter((n) => n.name === node.name && n.node !== node).head();
44
46
  if (sameKind) {
45
47
  const isAnotherDoc = sameKind.documentUri !== AstUtils.getDocument(node).uri;
46
48
  accept("error", `Duplicate element kind '${node.name}'`, {
@@ -70,7 +72,8 @@ export const deploymentNodeKindChecks = (services) => {
70
72
  property: "name"
71
73
  });
72
74
  }
73
- const sameKind = index.allElements(ast.DeploymentNodeKind).filter((n) => n.name === node.name && n.node !== node).head();
75
+ const projectId = projectIdFrom(node);
76
+ const sameKind = index.projectElements(projectId, ast.DeploymentNodeKind).filter((n) => n.name === node.name && n.node !== node).head();
74
77
  if (sameKind) {
75
78
  const isAnotherDoc = sameKind.documentUri !== AstUtils.getDocument(node).uri;
76
79
  accept("error", `Duplicate deploymentNode kind '${node.name}'`, {
@@ -95,7 +98,8 @@ export const tagChecks = (services) => {
95
98
  const index = services.shared.workspace.IndexManager;
96
99
  return tryOrLog((node, accept) => {
97
100
  const tagname = "#" + node.name;
98
- const sameTag = index.allElements(ast.Tag).filter((n) => n.name === tagname && n.node !== node).head();
101
+ const projectId = projectIdFrom(node);
102
+ const sameTag = index.projectElements(projectId, ast.Tag).filter((n) => n.name === tagname && n.node !== node).head();
99
103
  if (sameTag) {
100
104
  const isAnotherDoc = sameTag.documentUri !== AstUtils.getDocument(node).uri;
101
105
  accept(
@@ -129,7 +133,8 @@ export const relationshipChecks = (services) => {
129
133
  property: "name"
130
134
  });
131
135
  }
132
- const sameKinds = index.allElements(ast.RelationshipKind).filter((n) => n.name === node.name).limit(2).count();
136
+ const projectId = projectIdFrom(node);
137
+ const sameKinds = index.projectElements(projectId, ast.RelationshipKind).filter((n) => n.name === node.name).limit(2).count();
133
138
  if (sameKinds > 1) {
134
139
  accept("error", `Duplicate RelationshipKind '${node.name}'`, {
135
140
  node,
@@ -141,8 +146,9 @@ export const relationshipChecks = (services) => {
141
146
  export const globalPredicateChecks = (services) => {
142
147
  const index = services.shared.workspace.IndexManager;
143
148
  return tryOrLog((node, accept) => {
144
- const predicateGroups = index.allElements(ast.GlobalPredicateGroup);
145
- const dynamicPredicateGroups = index.allElements(ast.GlobalDynamicPredicateGroup);
149
+ const projectId = projectIdFrom(node);
150
+ const predicateGroups = index.projectElements(projectId, ast.GlobalPredicateGroup);
151
+ const dynamicPredicateGroups = index.projectElements(projectId, ast.GlobalDynamicPredicateGroup);
146
152
  const sameName = predicateGroups.concat(dynamicPredicateGroups).filter((s) => s.name === node.name).limit(2).count();
147
153
  if (sameName > 1) {
148
154
  accept("error", `Duplicate GlobalPredicateGroup or GlobalDynamicPredicateGroup name '${node.name}'`, {
@@ -155,7 +161,8 @@ export const globalPredicateChecks = (services) => {
155
161
  export const globalStyleIdChecks = (services) => {
156
162
  const index = services.shared.workspace.IndexManager;
157
163
  return tryOrLog((node, accept) => {
158
- const sameName = index.allElements(ast.GlobalStyleId).filter((s) => s.name === node.name).limit(2).count();
164
+ const projectId = projectIdFrom(node);
165
+ const sameName = index.projectElements(projectId, ast.GlobalStyleId).filter((s) => s.name === node.name).limit(2).count();
159
166
  if (sameName > 1) {
160
167
  accept("error", `Duplicate GlobalStyleId name '${node.name}'`, {
161
168
  node,
@@ -1,21 +1,23 @@
1
1
  import { ast } from "../ast.js";
2
+ import { projectIdFrom } from "../utils/index.js";
2
3
  import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
3
4
  export const viewChecks = (services) => {
4
5
  const index = services.shared.workspace.IndexManager;
5
- return tryOrLog((el, accept) => {
6
- if (!el.name) {
6
+ return tryOrLog((node, accept) => {
7
+ if (!node.name) {
7
8
  return;
8
9
  }
9
- if (RESERVED_WORDS.includes(el.name)) {
10
- accept("error", `Reserved word: ${el.name}`, {
11
- node: el,
10
+ if (RESERVED_WORDS.includes(node.name)) {
11
+ accept("error", `Reserved word: ${node.name}`, {
12
+ node,
12
13
  property: "name"
13
14
  });
14
15
  }
15
- const anotherViews = index.allElements(ast.LikeC4View).filter((n) => n.name === el.name).limit(2).count();
16
+ const projectId = projectIdFrom(node);
17
+ const anotherViews = index.projectElements(projectId, ast.LikeC4View).filter((n) => n.name === node.name).limit(2).count();
16
18
  if (anotherViews > 1) {
17
- accept("error", `Duplicate view '${el.name}'`, {
18
- node: el,
19
+ accept("error", `Duplicate view '${node.name}'`, {
20
+ node,
19
21
  property: "name"
20
22
  });
21
23
  }
@@ -1 +1 @@
1
- export { LikeC4Views } from './likec4-views';
1
+ export { DefaultLikeC4Views, type LikeC4Views } from './likec4-views';
@@ -1 +1 @@
1
- export { LikeC4Views } from "./likec4-views.js";
1
+ export { DefaultLikeC4Views } from "./likec4-views.js";
@@ -1,6 +1,6 @@
1
- import type { ComputedView, DiagramView, OverviewGraph, ViewId } from '@likec4/core';
1
+ import type { ComputedView, DiagramView, OverviewGraph, ProjectId, ViewId } from '@likec4/core';
2
2
  import { GraphvizLayouter } from '@likec4/layouts';
3
- import { type Cancellation } from 'langium';
3
+ import { CancellationToken } from 'vscode-jsonrpc';
4
4
  import type { LikeC4Services } from '../module';
5
5
  export type GraphvizOut = {
6
6
  dot: string;
@@ -11,18 +11,27 @@ type GraphvizSvgOut = {
11
11
  dot: string;
12
12
  svg: string;
13
13
  };
14
- export declare class LikeC4Views {
14
+ export interface LikeC4Views {
15
+ readonly layouter: GraphvizLayouter;
16
+ computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
17
+ layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<Readonly<GraphvizOut>>>;
18
+ layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
19
+ diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<DiagramView>>;
20
+ viewsAsGraphvizOut(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<GraphvizSvgOut>>;
21
+ overviewGraph(): Promise<OverviewGraph>;
22
+ }
23
+ export declare class DefaultLikeC4Views implements LikeC4Views {
15
24
  private services;
16
25
  private cache;
17
26
  private viewsWithReportedErrors;
18
27
  private ModelBuilder;
19
28
  constructor(services: LikeC4Services);
20
29
  get layouter(): GraphvizLayouter;
21
- computedViews(cancelToken?: Cancellation.CancellationToken): Promise<ComputedView[]>;
22
- layoutAllViews(cancelToken?: Cancellation.CancellationToken): Promise<Array<Readonly<GraphvizOut>>>;
23
- layoutView(viewId: ViewId, cancelToken?: Cancellation.CancellationToken): Promise<GraphvizOut | null>;
24
- diagrams(): Promise<Array<DiagramView>>;
25
- viewsAsGraphvizOut(): Promise<Array<GraphvizSvgOut>>;
30
+ computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
31
+ layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<Readonly<GraphvizOut>>>;
32
+ layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
33
+ diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<DiagramView>>;
34
+ viewsAsGraphvizOut(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<GraphvizSvgOut>>;
26
35
  overviewGraph(): Promise<OverviewGraph>;
27
36
  }
28
37
  export {};
@@ -1,9 +1,10 @@
1
1
  import { loggable } from "@likec4/log";
2
2
  import prettyMs from "pretty-ms";
3
3
  import { values } from "remeda";
4
+ import { CancellationToken } from "vscode-jsonrpc";
4
5
  import { logError, logger as rootLogger, logWarnError } from "../logger.js";
5
6
  const logger = rootLogger.getChild("Views");
6
- export class LikeC4Views {
7
+ export class DefaultLikeC4Views {
7
8
  constructor(services) {
8
9
  this.services = services;
9
10
  this.ModelBuilder = services.likec4.ModelBuilder;
@@ -14,12 +15,12 @@ export class LikeC4Views {
14
15
  get layouter() {
15
16
  return this.services.likec4.Layouter;
16
17
  }
17
- async computedViews(cancelToken) {
18
- const likeC4Model = await this.ModelBuilder.buildLikeC4Model(cancelToken);
18
+ async computedViews(projectId, cancelToken = CancellationToken.None) {
19
+ const likeC4Model = await this.ModelBuilder.buildLikeC4Model(projectId, cancelToken);
19
20
  return values(likeC4Model.$model.views);
20
21
  }
21
- async layoutAllViews(cancelToken) {
22
- const views = await this.computedViews(cancelToken);
22
+ async layoutAllViews(projectId, cancelToken = CancellationToken.None) {
23
+ const views = await this.computedViews(projectId, cancelToken);
23
24
  if (views.length === 0) {
24
25
  return [];
25
26
  }
@@ -52,8 +53,8 @@ export class LikeC4Views {
52
53
  }
53
54
  return results;
54
55
  }
55
- async layoutView(viewId, cancelToken) {
56
- const model = await this.ModelBuilder.buildLikeC4Model(cancelToken);
56
+ async layoutView(viewId, projectId, cancelToken = CancellationToken.None) {
57
+ const model = await this.ModelBuilder.buildLikeC4Model(projectId, cancelToken);
57
58
  const view = model.findView(viewId)?.$view;
58
59
  if (!view) {
59
60
  logger.warn`layoutView ${viewId} not found`;
@@ -81,17 +82,17 @@ export class LikeC4Views {
81
82
  return Promise.reject(e);
82
83
  }
83
84
  }
84
- async diagrams() {
85
- const layouted = await this.layoutAllViews();
85
+ async diagrams(projectId, cancelToken = CancellationToken.None) {
86
+ const layouted = await this.layoutAllViews(projectId, cancelToken);
86
87
  return layouted.map((l) => l.diagram);
87
88
  }
88
- async viewsAsGraphvizOut() {
89
+ async viewsAsGraphvizOut(projectId, cancelToken = CancellationToken.None) {
89
90
  const KEY = "All-LayoutedViews-DotWithSvg";
90
91
  const cache = this.services.ValidatedWorkspaceCache;
91
92
  if (cache.has(KEY)) {
92
93
  return await Promise.resolve(cache.get(KEY));
93
94
  }
94
- const views = await this.computedViews();
95
+ const views = await this.computedViews(projectId, cancelToken);
95
96
  const tasks = views.map(async (view) => {
96
97
  const { dot, svg } = await this.layouter.svg(view);
97
98
  return {
@@ -0,0 +1,7 @@
1
+ import { type AstNode, type AstNodeDescription, type LangiumDocument, DefaultAstNodeDescriptionProvider } from 'langium';
2
+ import type { LikeC4Services } from '../module';
3
+ export declare class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
4
+ private projects;
5
+ constructor(services: LikeC4Services);
6
+ createDescription(node: AstNode, name: string | undefined, document?: LangiumDocument): AstNodeDescription;
7
+ }
@@ -0,0 +1,18 @@
1
+ import {
2
+ AstUtils,
3
+ DefaultAstNodeDescriptionProvider
4
+ } from "langium";
5
+ export class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
6
+ projects;
7
+ constructor(services) {
8
+ super(services);
9
+ this.projects = services.shared.workspace.ProjectsManager;
10
+ }
11
+ createDescription(node, name, document) {
12
+ const doc = document ?? AstUtils.getDocument(node);
13
+ const description = super.createDescription(node, name, document);
14
+ doc.likec4ProjectId ??= this.projects.belongsTo(doc.uri);
15
+ description.likec4ProjectId = doc.likec4ProjectId;
16
+ return description;
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ import type { ProjectId } from '@likec4/core';
2
+ import { type AstNodeDescription, type LangiumDocument, type Stream, DefaultIndexManager } from 'langium';
3
+ import { CancellationToken } from 'vscode-jsonrpc';
4
+ import type { LikeC4SharedServices } from '../module';
5
+ export declare class IndexManager extends DefaultIndexManager {
6
+ private projects;
7
+ constructor(services: LikeC4SharedServices);
8
+ updateContent(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void>;
9
+ projectElements(projectId: ProjectId, nodeType?: string, uris?: Set<string>): Stream<AstNodeDescription>;
10
+ }
@@ -0,0 +1,17 @@
1
+ import { DefaultIndexManager, stream } from "langium";
2
+ import { CancellationToken } from "vscode-jsonrpc";
3
+ export class IndexManager extends DefaultIndexManager {
4
+ projects;
5
+ constructor(services) {
6
+ super(services);
7
+ this.projects = services.workspace.ProjectsManager;
8
+ }
9
+ async updateContent(document, cancelToken = CancellationToken.None) {
10
+ document.likec4ProjectId = this.projects.belongsTo(document.uri);
11
+ await super.updateContent(document, cancelToken);
12
+ }
13
+ projectElements(projectId, nodeType, uris) {
14
+ let documentUris = stream(this.symbolIndex.keys());
15
+ return documentUris.filter((uri) => this.projects.belongsTo(uri) === projectId && (!uris || uris.has(uri))).flatMap((uri) => this.getFileDescriptions(uri, nodeType));
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ import type { NonEmptyArray, ProjectId } from '@likec4/core';
2
+ import { type Stream, DefaultLangiumDocuments } from 'langium';
3
+ import { type LikeC4LangiumDocument } from '../ast';
4
+ import type { LikeC4SharedServices } from '../module';
5
+ export declare class LangiumDocuments extends DefaultLangiumDocuments {
6
+ private projects;
7
+ constructor(services: LikeC4SharedServices);
8
+ /**
9
+ * Returns all user documents, excluding built-in documents.
10
+ */
11
+ get allExcludingBuiltin(): Stream<LikeC4LangiumDocument>;
12
+ projectDocuments(projectId: ProjectId): Stream<LikeC4LangiumDocument>;
13
+ groupedByProject(): Record<ProjectId, NonEmptyArray<LikeC4LangiumDocument>>;
14
+ }
@@ -0,0 +1,31 @@
1
+ import { DefaultLangiumDocuments } from "langium";
2
+ import { groupBy, prop } from "remeda";
3
+ import { isLikeC4LangiumDocument } from "../ast.js";
4
+ import { isLikeC4Builtin } from "../likec4lib.js";
5
+ export class LangiumDocuments extends DefaultLangiumDocuments {
6
+ projects;
7
+ constructor(services) {
8
+ super(services);
9
+ this.projects = services.workspace.ProjectsManager;
10
+ }
11
+ /**
12
+ * Returns all user documents, excluding built-in documents.
13
+ */
14
+ get allExcludingBuiltin() {
15
+ return super.all.filter((doc) => {
16
+ if (!isLikeC4LangiumDocument(doc) || isLikeC4Builtin(doc.uri)) {
17
+ return false;
18
+ }
19
+ if (!doc.likec4ProjectId) {
20
+ doc.likec4ProjectId = this.projects.belongsTo(doc.uri);
21
+ }
22
+ return true;
23
+ });
24
+ }
25
+ projectDocuments(projectId) {
26
+ return this.allExcludingBuiltin.filter((doc) => doc.likec4ProjectId === projectId);
27
+ }
28
+ groupedByProject() {
29
+ return groupBy(this.allExcludingBuiltin.toArray(), prop("likec4ProjectId"));
30
+ }
31
+ }
@@ -0,0 +1,48 @@
1
+ import type { NonEmptyReadonlyArray, ProjectId } from '@likec4/core';
2
+ import { type FileSystemNode, type LangiumDocument, URI, WorkspaceCache } from 'langium';
3
+ import { ProjectConfig } from '../config';
4
+ import type { LikeC4SharedServices } from '../module';
5
+ export declare class ProjectsManager {
6
+ protected services: LikeC4SharedServices;
7
+ /**
8
+ * The global project ID used for all documents
9
+ * that are not part of a specific project.
10
+ */
11
+ static readonly DefaultProjectId: ProjectId;
12
+ static readonly ConfigFileNames: string[];
13
+ /**
14
+ * The mapping between project config files and project IDs.
15
+ */
16
+ private projectIdToFolder;
17
+ private _mappingsToProject;
18
+ /**
19
+ * Registered projects.
20
+ * Sorted descending by the number of segments in the folder path.
21
+ * This ensures that the most specific project is used for a document.
22
+ */
23
+ private _projects;
24
+ constructor(services: LikeC4SharedServices);
25
+ get defaultProjectId(): ProjectId | undefined;
26
+ get all(): NonEmptyReadonlyArray<ProjectId>;
27
+ getProject(projectId: ProjectId): {
28
+ folder: URI;
29
+ config: Readonly<ProjectConfig>;
30
+ };
31
+ ensureProjectId(projectId?: ProjectId | undefined): ProjectId;
32
+ hasMultipleProjects(): boolean;
33
+ /**
34
+ * Checks if the provided file system entry is a valid project config file.
35
+ *
36
+ * @param entry The file system entry to check
37
+ * @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
38
+ */
39
+ loadConfigFile(entry: FileSystemNode): Promise<boolean>;
40
+ registerProject(configFile: URI): Promise<void>;
41
+ registerProject(opts: {
42
+ config: ProjectConfig;
43
+ folderUri: URI | string;
44
+ }): Promise<void>;
45
+ belongsTo(document: LangiumDocument | URI | string): ProjectId;
46
+ private getProjectId;
47
+ protected get mappingsToProject(): WorkspaceCache<string, ProjectId>;
48
+ }