@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.
- package/dist/LikeC4FileSystem.js +6 -1
- package/dist/LikeC4LanguageServices.d.ts +77 -0
- package/dist/LikeC4LanguageServices.js +118 -0
- package/dist/Rpc.js +106 -28
- package/dist/ast.d.ts +10 -0
- package/dist/bundled.mjs +2352 -2278
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +1 -0
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.js +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/lsp/CodeLensProvider.js +3 -1
- package/dist/model/builder/buildModel.d.ts +8 -1
- package/dist/model/deployments-index.js +1 -1
- package/dist/model/fqn-index.d.ts +9 -6
- package/dist/model/fqn-index.js +24 -14
- package/dist/model/model-builder.d.ts +16 -6
- package/dist/model/model-builder.js +64 -50
- package/dist/model/model-locator.d.ts +11 -10
- package/dist/model/model-locator.js +32 -14
- package/dist/model/model-parser.d.ts +148 -148
- package/dist/model/model-parser.js +2 -2
- package/dist/model/parser/ModelParser.js +14 -2
- package/dist/model-change/ModelChanges.d.ts +1 -1
- package/dist/model-change/ModelChanges.js +2 -2
- package/dist/module.d.ts +9 -3
- package/dist/module.js +19 -5
- package/dist/protocol.d.ts +98 -16
- package/dist/protocol.js +21 -6
- package/dist/references/scope-provider.d.ts +24 -11
- package/dist/references/scope-provider.js +97 -68
- package/dist/shared/index.d.ts +0 -1
- package/dist/shared/index.js +0 -1
- package/dist/test/testServices.d.ts +15 -1
- package/dist/test/testServices.js +65 -17
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/projectId.d.ts +3 -0
- package/dist/utils/projectId.js +6 -0
- package/dist/validation/deployment-checks.js +5 -2
- package/dist/validation/element.js +2 -1
- package/dist/validation/specification.js +14 -7
- package/dist/validation/view.js +10 -8
- package/dist/views/index.d.ts +1 -1
- package/dist/views/index.js +1 -1
- package/dist/views/likec4-views.d.ts +17 -8
- package/dist/views/likec4-views.js +12 -11
- package/dist/workspace/AstNodeDescriptionProvider.d.ts +7 -0
- package/dist/workspace/AstNodeDescriptionProvider.js +18 -0
- package/dist/workspace/IndexManager.d.ts +10 -0
- package/dist/workspace/IndexManager.js +17 -0
- package/dist/workspace/LangiumDocuments.d.ts +14 -0
- package/dist/workspace/LangiumDocuments.js +31 -0
- package/dist/workspace/ProjectsManager.d.ts +48 -0
- package/dist/workspace/ProjectsManager.js +150 -0
- package/dist/{shared → workspace}/WorkspaceManager.d.ts +8 -2
- package/dist/{shared → workspace}/WorkspaceManager.js +22 -0
- package/dist/workspace/index.d.ts +5 -0
- package/dist/workspace/index.js +5 -0
- package/package.json +17 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseConfigJson, ProjectConfig, validateConfig } from './schema';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseConfigJson, ProjectConfig, validateConfig } from "./schema.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
export declare const ProjectConfig: v.ObjectSchema<{
|
|
3
|
+
readonly name: v.SchemaWithPipe<[v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.DescriptionAction<string, "Project name, must be unique in the workspace">]>;
|
|
4
|
+
readonly exclude: v.SchemaWithPipe<[v.ExactOptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, v.DescriptionAction<string[], "List of file patterns to exclude from the project, default is [\"node_modules\"]">]>;
|
|
5
|
+
}, undefined>;
|
|
6
|
+
export type ProjectConfig = v.InferOutput<typeof ProjectConfig>;
|
|
7
|
+
export declare function parseConfigJson(config: string): ProjectConfig;
|
|
8
|
+
export declare function validateConfig(config: unknown): ProjectConfig;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import JSON5 from "json5";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
export const ProjectConfig = v.object({
|
|
4
|
+
name: v.pipe(
|
|
5
|
+
v.string(),
|
|
6
|
+
v.nonEmpty(),
|
|
7
|
+
v.description("Project name, must be unique in the workspace")
|
|
8
|
+
),
|
|
9
|
+
exclude: v.pipe(
|
|
10
|
+
v.exactOptional(v.array(v.string())),
|
|
11
|
+
v.description('List of file patterns to exclude from the project, default is ["node_modules"]')
|
|
12
|
+
)
|
|
13
|
+
});
|
|
14
|
+
export function parseConfigJson(config) {
|
|
15
|
+
return validateConfig(JSON5.parse(config));
|
|
16
|
+
}
|
|
17
|
+
export function validateConfig(config) {
|
|
18
|
+
return v.parse(ProjectConfig, config);
|
|
19
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { LikeC4FileSystem } from './LikeC4FileSystem';
|
|
|
2
2
|
import { type LikeC4Services, type LikeC4SharedServices } from './module';
|
|
3
3
|
export { getLspConnectionSink, logger as lspLogger } from './logger';
|
|
4
4
|
export type { DocumentParser, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model';
|
|
5
|
+
export type { LikeC4LanguageServices } from './LikeC4LanguageServices';
|
|
5
6
|
export { isLikeC4Builtin } from './likec4lib';
|
|
6
7
|
export { createCustomLanguageServices, createLanguageServices, LikeC4Module } from './module';
|
|
7
8
|
export type { LikeC4Services, LikeC4SharedServices } from './module';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DocumentState } from "langium";
|
|
2
2
|
import { isLikeC4LangiumDocument, ViewOps } from "../ast.js";
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
|
+
import { projectIdFrom } from "../utils/index.js";
|
|
4
5
|
export class LikeC4CodeLensProvider {
|
|
5
6
|
constructor(services) {
|
|
6
7
|
this.services = services;
|
|
@@ -21,6 +22,7 @@ export class LikeC4CodeLensProvider {
|
|
|
21
22
|
if (!range || !viewId) {
|
|
22
23
|
return [];
|
|
23
24
|
}
|
|
25
|
+
const projectId = projectIdFrom(ast);
|
|
24
26
|
return {
|
|
25
27
|
range: {
|
|
26
28
|
start: range.start,
|
|
@@ -31,7 +33,7 @@ export class LikeC4CodeLensProvider {
|
|
|
31
33
|
},
|
|
32
34
|
command: {
|
|
33
35
|
command: "likec4.open-preview",
|
|
34
|
-
arguments: [viewId],
|
|
36
|
+
arguments: [viewId, projectId],
|
|
35
37
|
title: "open preview"
|
|
36
38
|
}
|
|
37
39
|
};
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core';
|
|
2
2
|
import type { ParsedLikeC4LangiumDocument } from '../../ast';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Each document was parsed into a ParsedLikeC4LangiumDocument, where elements
|
|
5
|
+
* do not inherit styles from specification.
|
|
6
|
+
*
|
|
7
|
+
* This function builds a model from all documents, merging the specifications
|
|
8
|
+
* and globals, and applying the extends to the elements.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildModel(docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4ModelData;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { type Fqn } from '@likec4/core/types';
|
|
1
|
+
import { type Fqn, type ProjectId } from '@likec4/core/types';
|
|
2
2
|
import { DefaultWeakMap, MultiMap } from '@likec4/core/utils';
|
|
3
|
-
import { type AstNode, type
|
|
3
|
+
import { type AstNode, type Stream, WorkspaceCache } from 'langium';
|
|
4
4
|
import { type AstNodeDescriptionWithFqn, type LikeC4LangiumDocument, ast } from '../ast';
|
|
5
5
|
import type { LikeC4Services } from '../module';
|
|
6
6
|
import { ADisposable } from '../utils';
|
|
7
|
+
import { type LangiumDocuments, ProjectsManager } from '../workspace';
|
|
7
8
|
export declare class FqnIndex<AstNd extends AstNode = ast.Element> extends ADisposable {
|
|
8
9
|
protected services: LikeC4Services;
|
|
9
10
|
private cachePrefix;
|
|
11
|
+
protected projects: ProjectsManager;
|
|
10
12
|
protected langiumDocuments: LangiumDocuments;
|
|
11
13
|
protected documentCache: DefaultWeakMap<LikeC4LangiumDocument, DocumentFqnIndex>;
|
|
12
14
|
protected workspaceCache: WorkspaceCache<string, AstNodeDescriptionWithFqn[]>;
|
|
@@ -14,12 +16,12 @@ export declare class FqnIndex<AstNd extends AstNode = ast.Element> extends ADisp
|
|
|
14
16
|
private documents;
|
|
15
17
|
get(document: LikeC4LangiumDocument): DocumentFqnIndex;
|
|
16
18
|
getFqn(el: AstNd): Fqn;
|
|
17
|
-
byFqn(fqn: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
18
|
-
directChildrenOf(parent: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
19
|
+
byFqn(projectId: ProjectId, fqn: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
20
|
+
directChildrenOf(projectId: ProjectId, parent: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
19
21
|
/**
|
|
20
22
|
* Returns descedant elements with unique names in the scope
|
|
21
23
|
*/
|
|
22
|
-
uniqueDescedants(parent: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
24
|
+
uniqueDescedants(projectId: ProjectId, parent: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
23
25
|
protected createDocumentIndex(document: LikeC4LangiumDocument): DocumentFqnIndex;
|
|
24
26
|
}
|
|
25
27
|
export declare class DocumentFqnIndex {
|
|
@@ -36,6 +38,7 @@ export declare class DocumentFqnIndex {
|
|
|
36
38
|
* All elements by FQN
|
|
37
39
|
*/
|
|
38
40
|
private _byfqn;
|
|
41
|
+
readonly projectId: ProjectId;
|
|
39
42
|
static readonly EMPTY: DocumentFqnIndex;
|
|
40
43
|
constructor(_rootElements: Array<AstNodeDescriptionWithFqn>,
|
|
41
44
|
/**
|
|
@@ -49,7 +52,7 @@ export declare class DocumentFqnIndex {
|
|
|
49
52
|
/**
|
|
50
53
|
* All elements by FQN
|
|
51
54
|
*/
|
|
52
|
-
_byfqn: MultiMap<Fqn, AstNodeDescriptionWithFqn
|
|
55
|
+
_byfqn: MultiMap<Fqn, AstNodeDescriptionWithFqn>, projectId: ProjectId);
|
|
53
56
|
rootElements(): readonly AstNodeDescriptionWithFqn[];
|
|
54
57
|
byFqn(fqn: Fqn): readonly AstNodeDescriptionWithFqn[];
|
|
55
58
|
children(parent: Fqn): readonly AstNodeDescriptionWithFqn[];
|
package/dist/model/fqn-index.js
CHANGED
|
@@ -16,12 +16,14 @@ import {
|
|
|
16
16
|
import { logWarnError } from "../logger.js";
|
|
17
17
|
import { ADisposable } from "../utils/index.js";
|
|
18
18
|
import { readStrictFqn } from "../utils/elementRef.js";
|
|
19
|
+
import { ProjectsManager } from "../workspace/index.js";
|
|
19
20
|
export class FqnIndex extends ADisposable {
|
|
20
21
|
constructor(services, cachePrefix = "fqn-index") {
|
|
21
22
|
super();
|
|
22
23
|
this.services = services;
|
|
23
24
|
this.cachePrefix = cachePrefix;
|
|
24
25
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
26
|
+
this.projects = services.shared.workspace.ProjectsManager;
|
|
25
27
|
this.documentCache = new DefaultWeakMap((doc) => this.createDocumentIndex(doc));
|
|
26
28
|
this.workspaceCache = new WorkspaceCache(services.shared, DocumentState.IndexedContent);
|
|
27
29
|
this.onDispose(
|
|
@@ -36,11 +38,12 @@ export class FqnIndex extends ADisposable {
|
|
|
36
38
|
)
|
|
37
39
|
);
|
|
38
40
|
}
|
|
41
|
+
projects;
|
|
39
42
|
langiumDocuments;
|
|
40
43
|
documentCache;
|
|
41
44
|
workspaceCache;
|
|
42
|
-
documents() {
|
|
43
|
-
return this.langiumDocuments.
|
|
45
|
+
documents(projectId) {
|
|
46
|
+
return this.langiumDocuments.projectDocuments(projectId).filter(
|
|
44
47
|
(d) => isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
|
|
45
48
|
);
|
|
46
49
|
}
|
|
@@ -62,17 +65,17 @@ export class FqnIndex extends ADisposable {
|
|
|
62
65
|
this.get(doc);
|
|
63
66
|
return nonNullable(ElementOps.readId(el), "Element fqn must be set, invalid state");
|
|
64
67
|
}
|
|
65
|
-
byFqn(fqn) {
|
|
66
|
-
return stream(this.workspaceCache.get(`${this.cachePrefix}:${fqn}`, () => {
|
|
67
|
-
return this.documents().toArray().flatMap((doc) => {
|
|
68
|
+
byFqn(projectId, fqn) {
|
|
69
|
+
return stream(this.workspaceCache.get(`${this.cachePrefix}:${projectId}:${fqn}`, () => {
|
|
70
|
+
return this.documents(projectId).toArray().flatMap((doc) => {
|
|
68
71
|
return this.get(doc).byFqn(fqn);
|
|
69
72
|
});
|
|
70
73
|
}));
|
|
71
74
|
}
|
|
72
|
-
directChildrenOf(parent) {
|
|
75
|
+
directChildrenOf(projectId, parent) {
|
|
73
76
|
return stream(
|
|
74
|
-
this.workspaceCache.get(`${this.cachePrefix}:directChildrenOf:${parent}`, () => {
|
|
75
|
-
const allchildren = this.documents().reduce((map, doc) => {
|
|
77
|
+
this.workspaceCache.get(`${this.cachePrefix}:${projectId}:directChildrenOf:${parent}`, () => {
|
|
78
|
+
const allchildren = this.documents(projectId).reduce((map, doc) => {
|
|
76
79
|
this.get(doc).children(parent).forEach((desc) => {
|
|
77
80
|
map.set(desc.name, desc);
|
|
78
81
|
});
|
|
@@ -85,10 +88,10 @@ export class FqnIndex extends ADisposable {
|
|
|
85
88
|
/**
|
|
86
89
|
* Returns descedant elements with unique names in the scope
|
|
87
90
|
*/
|
|
88
|
-
uniqueDescedants(parent) {
|
|
91
|
+
uniqueDescedants(projectId, parent) {
|
|
89
92
|
return stream(
|
|
90
|
-
this.workspaceCache.get(`${this.cachePrefix}:uniqueDescedants:${parent}`, () => {
|
|
91
|
-
const { children, descendants } = this.documents().reduce((map, doc) => {
|
|
93
|
+
this.workspaceCache.get(`${this.cachePrefix}:${projectId}:uniqueDescedants:${parent}`, () => {
|
|
94
|
+
const { children, descendants } = this.documents(projectId).reduce((map, doc) => {
|
|
92
95
|
const docIndex = this.get(doc);
|
|
93
96
|
docIndex.children(parent).forEach((desc) => {
|
|
94
97
|
map.children.set(desc.name, desc);
|
|
@@ -181,20 +184,27 @@ export class FqnIndex extends ADisposable {
|
|
|
181
184
|
logWarnError(e);
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
|
-
return new DocumentFqnIndex(root, children, descendants, byfqn);
|
|
187
|
+
return new DocumentFqnIndex(root, children, descendants, byfqn, this.projects.belongsTo(document));
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
function uniqueByName(multimap) {
|
|
188
191
|
return [...multimap.associations()].flatMap(([_name, descs]) => descs.length === 1 ? descs : []);
|
|
189
192
|
}
|
|
190
193
|
export class DocumentFqnIndex {
|
|
191
|
-
constructor(_rootElements, _children, _descendants, _byfqn) {
|
|
194
|
+
constructor(_rootElements, _children, _descendants, _byfqn, projectId) {
|
|
192
195
|
this._rootElements = _rootElements;
|
|
193
196
|
this._children = _children;
|
|
194
197
|
this._descendants = _descendants;
|
|
195
198
|
this._byfqn = _byfqn;
|
|
199
|
+
this.projectId = projectId;
|
|
196
200
|
}
|
|
197
|
-
static EMPTY = new DocumentFqnIndex(
|
|
201
|
+
static EMPTY = new DocumentFqnIndex(
|
|
202
|
+
[],
|
|
203
|
+
new MultiMap(),
|
|
204
|
+
new MultiMap(),
|
|
205
|
+
new MultiMap(),
|
|
206
|
+
ProjectsManager.DefaultProjectId
|
|
207
|
+
);
|
|
198
208
|
rootElements() {
|
|
199
209
|
return this._rootElements;
|
|
200
210
|
}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core';
|
|
2
2
|
import { type ViewId, LikeC4Model } from '@likec4/core';
|
|
3
|
-
import { type
|
|
3
|
+
import { type URI, Disposable } from 'langium';
|
|
4
|
+
import { CancellationToken } from 'vscode-jsonrpc';
|
|
4
5
|
import type { LikeC4Services } from '../module';
|
|
5
6
|
import { ADisposable } from '../utils';
|
|
6
7
|
type ModelParsedListener = (docs: URI[]) => void;
|
|
7
|
-
export
|
|
8
|
+
export interface LikeC4ModelBuilder {
|
|
9
|
+
parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<c4.ParsedLikeC4ModelData | null>;
|
|
10
|
+
unsafeSyncBuildModel(projectId: c4.ProjectId): LikeC4Model.Computed;
|
|
11
|
+
buildLikeC4Model(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Computed>;
|
|
12
|
+
computeView(viewId: ViewId, projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<c4.ComputedView | null>;
|
|
13
|
+
onModelParsed(callback: ModelParsedListener): Disposable;
|
|
14
|
+
}
|
|
15
|
+
export declare class DefaultLikeC4ModelBuilder extends ADisposable implements LikeC4ModelBuilder {
|
|
16
|
+
private projects;
|
|
8
17
|
private parser;
|
|
9
18
|
private listeners;
|
|
10
19
|
private cache;
|
|
11
20
|
private DocumentBuilder;
|
|
12
21
|
private LangiumDocuments;
|
|
22
|
+
private mutex;
|
|
13
23
|
constructor(services: LikeC4Services);
|
|
14
24
|
/**
|
|
15
25
|
* WARNING:
|
|
@@ -17,16 +27,16 @@ export declare class LikeC4ModelBuilder extends ADisposable {
|
|
|
17
27
|
* Otherwise, the model may be incomplete.
|
|
18
28
|
*/
|
|
19
29
|
private unsafeSyncParseModel;
|
|
20
|
-
parseModel(cancelToken?:
|
|
30
|
+
parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<c4.ParsedLikeC4ModelData | null>;
|
|
21
31
|
private previousViews;
|
|
22
32
|
/**
|
|
23
33
|
* WARNING:
|
|
24
34
|
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
25
35
|
* Otherwise, the model may be incomplete.
|
|
26
36
|
*/
|
|
27
|
-
unsafeSyncBuildModel(): LikeC4Model.Computed;
|
|
28
|
-
buildLikeC4Model(cancelToken?:
|
|
29
|
-
computeView(viewId: ViewId, cancelToken?:
|
|
37
|
+
unsafeSyncBuildModel(projectId: c4.ProjectId): LikeC4Model.Computed;
|
|
38
|
+
buildLikeC4Model(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Computed>;
|
|
39
|
+
computeView(viewId: ViewId, projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<c4.ComputedView | null>;
|
|
30
40
|
onModelParsed(callback: ModelParsedListener): Disposable;
|
|
31
41
|
private documents;
|
|
32
42
|
private notifyListeners;
|
|
@@ -6,7 +6,8 @@ import { loggable } from "@likec4/log";
|
|
|
6
6
|
import { deepEqual as eq } from "fast-equals";
|
|
7
7
|
import {
|
|
8
8
|
Disposable,
|
|
9
|
-
DocumentState
|
|
9
|
+
DocumentState,
|
|
10
|
+
interruptAndCheck
|
|
10
11
|
} from "langium";
|
|
11
12
|
import prettyMs from "pretty-ms";
|
|
12
13
|
import {
|
|
@@ -18,26 +19,31 @@ import {
|
|
|
18
19
|
prop,
|
|
19
20
|
values
|
|
20
21
|
} from "remeda";
|
|
22
|
+
import { CancellationToken } from "vscode-jsonrpc";
|
|
21
23
|
import { isLikeC4Builtin } from "../likec4lib.js";
|
|
22
24
|
import { logger as mainLogger, logWarnError } from "../logger.js";
|
|
23
25
|
import { ADisposable } from "../utils/index.js";
|
|
24
26
|
import { assignNavigateTo } from "../view-utils/index.js";
|
|
25
27
|
import { buildModel } from "./builder/buildModel.js";
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
+
const parsedModelCacheKey = (projectId) => `parsed-model-${projectId}`;
|
|
29
|
+
const computedModelCacheKey = (projectId) => `computed-model-${projectId}`;
|
|
28
30
|
const logger = mainLogger.getChild("ModelBuilder");
|
|
29
|
-
export class
|
|
31
|
+
export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
32
|
+
projects;
|
|
30
33
|
parser;
|
|
31
34
|
listeners = [];
|
|
32
35
|
cache;
|
|
33
36
|
DocumentBuilder;
|
|
34
37
|
LangiumDocuments;
|
|
38
|
+
mutex;
|
|
35
39
|
constructor(services) {
|
|
36
40
|
super();
|
|
41
|
+
this.projects = services.shared.workspace.ProjectsManager;
|
|
37
42
|
this.parser = services.likec4.ModelParser;
|
|
38
43
|
this.cache = services.ValidatedWorkspaceCache;
|
|
39
44
|
this.DocumentBuilder = services.shared.workspace.DocumentBuilder;
|
|
40
45
|
this.LangiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
46
|
+
this.mutex = services.shared.workspace.WorkspaceLock;
|
|
41
47
|
this.onDispose(
|
|
42
48
|
this.DocumentBuilder.onUpdate((_changed, deleted) => {
|
|
43
49
|
if (deleted.length > 0) {
|
|
@@ -63,33 +69,38 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
63
69
|
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
64
70
|
* Otherwise, the model may be incomplete.
|
|
65
71
|
*/
|
|
66
|
-
unsafeSyncParseModel() {
|
|
67
|
-
const docs = this.documents();
|
|
68
|
-
if (docs.length === 0) {
|
|
69
|
-
logger.debug("no documents to build model from");
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
+
unsafeSyncParseModel(projectId) {
|
|
72
73
|
const cache = this.cache;
|
|
73
|
-
|
|
74
|
+
const log = logger.getChild(["project", projectId]);
|
|
75
|
+
if (cache.has(parsedModelCacheKey(projectId))) {
|
|
76
|
+
log.debug("unsafeSyncParseModel from cache");
|
|
77
|
+
}
|
|
78
|
+
return cache.get(parsedModelCacheKey(projectId), () => {
|
|
79
|
+
const docs = this.documents(projectId);
|
|
80
|
+
if (docs.length === 0) {
|
|
81
|
+
logger.debug(`no documents to build model - project ${projectId}`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
log.debug("unsafeSyncParseModel from documents");
|
|
74
85
|
return buildModel(docs);
|
|
75
86
|
});
|
|
76
87
|
}
|
|
77
|
-
async parseModel(cancelToken) {
|
|
88
|
+
async parseModel(projectId, cancelToken = CancellationToken.None) {
|
|
89
|
+
const project = this.projects.ensureProjectId(projectId);
|
|
90
|
+
const log = logger.getChild(["project", project]);
|
|
78
91
|
const cache = this.cache;
|
|
79
|
-
const cached = cache.get(
|
|
92
|
+
const cached = cache.get(parsedModelCacheKey(project));
|
|
80
93
|
if (cached) {
|
|
81
|
-
|
|
94
|
+
log.debug("parseModel from cache");
|
|
82
95
|
return cached;
|
|
83
96
|
}
|
|
84
|
-
if (this.LangiumDocuments.all.some((doc) => doc.state < DocumentState.Validated)) {
|
|
85
|
-
logger.debug("parseModel: waiting for documents to be validated");
|
|
86
|
-
await this.DocumentBuilder.waitUntil(DocumentState.Validated, cancelToken);
|
|
87
|
-
logger.debug("parseModel: documents are validated");
|
|
88
|
-
}
|
|
89
97
|
const t0 = performance.now();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
return await this.mutex.read(async () => {
|
|
99
|
+
await interruptAndCheck(cancelToken);
|
|
100
|
+
const result = this.unsafeSyncParseModel(project);
|
|
101
|
+
log.debug(`parseModel in ${prettyMs(performance.now() - t0)}`);
|
|
102
|
+
return result;
|
|
103
|
+
});
|
|
93
104
|
}
|
|
94
105
|
previousViews = {};
|
|
95
106
|
/**
|
|
@@ -97,11 +108,11 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
97
108
|
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
98
109
|
* Otherwise, the model may be incomplete.
|
|
99
110
|
*/
|
|
100
|
-
unsafeSyncBuildModel() {
|
|
111
|
+
unsafeSyncBuildModel(projectId) {
|
|
101
112
|
const cache = this.cache;
|
|
102
113
|
const viewsCache = this.cache;
|
|
103
|
-
return cache.get(
|
|
104
|
-
const parsed = this.unsafeSyncParseModel();
|
|
114
|
+
return cache.get(computedModelCacheKey(projectId), () => {
|
|
115
|
+
const parsed = this.unsafeSyncParseModel(projectId);
|
|
105
116
|
if (!parsed) {
|
|
106
117
|
return LikeC4Model.EMPTY;
|
|
107
118
|
}
|
|
@@ -121,52 +132,55 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
121
132
|
}
|
|
122
133
|
assignNavigateTo(allViews);
|
|
123
134
|
const views = mapToObj(allViews, (v) => {
|
|
124
|
-
const
|
|
135
|
+
const key = computedViewKey(projectId, v.id);
|
|
136
|
+
const previous = this.previousViews[key];
|
|
125
137
|
const view = previous && eq(v, previous) ? previous : v;
|
|
126
|
-
viewsCache.set(
|
|
138
|
+
viewsCache.set(key, view);
|
|
127
139
|
return [v.id, view];
|
|
128
140
|
});
|
|
129
|
-
this.previousViews = { ...views };
|
|
141
|
+
this.previousViews = { ...this.previousViews, ...views };
|
|
130
142
|
return LikeC4Model.create({
|
|
131
143
|
...model,
|
|
132
144
|
views
|
|
133
145
|
});
|
|
134
146
|
});
|
|
135
147
|
}
|
|
136
|
-
async buildLikeC4Model(cancelToken) {
|
|
148
|
+
async buildLikeC4Model(projectId, cancelToken = CancellationToken.None) {
|
|
149
|
+
const project = this.projects.ensureProjectId(projectId);
|
|
150
|
+
const log = logger.getChild(["project", project]);
|
|
137
151
|
const cache = this.cache;
|
|
138
|
-
const cached = cache.get(
|
|
152
|
+
const cached = cache.get(computedModelCacheKey(project));
|
|
139
153
|
if (cached) {
|
|
140
|
-
|
|
154
|
+
log.debug("buildLikeC4Model from cache");
|
|
141
155
|
return cached;
|
|
142
156
|
}
|
|
143
157
|
const t0 = performance.now();
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
logger.debug(`buildLikeC4Model in ${prettyMs(performance.now() - t0)}`);
|
|
151
|
-
return result;
|
|
158
|
+
return await this.mutex.read(async () => {
|
|
159
|
+
await interruptAndCheck(cancelToken);
|
|
160
|
+
const result = this.unsafeSyncBuildModel(project);
|
|
161
|
+
log.debug(`buildLikeC4Model in ${prettyMs(performance.now() - t0)}`);
|
|
162
|
+
return result;
|
|
163
|
+
});
|
|
152
164
|
}
|
|
153
|
-
async computeView(viewId, cancelToken) {
|
|
165
|
+
async computeView(viewId, projectId, cancelToken = CancellationToken.None) {
|
|
166
|
+
const project = this.projects.ensureProjectId(projectId);
|
|
167
|
+
const log = logger.getChild(["project", project]);
|
|
154
168
|
const cache = this.cache;
|
|
155
|
-
const cacheKey = computedViewKey(viewId);
|
|
169
|
+
const cacheKey = computedViewKey(project, viewId);
|
|
156
170
|
if (cache.has(cacheKey)) {
|
|
157
171
|
return cache.get(cacheKey);
|
|
158
172
|
}
|
|
159
|
-
const parsed = await this.parseModel(cancelToken);
|
|
173
|
+
const parsed = await this.parseModel(project, cancelToken);
|
|
160
174
|
if (!parsed) {
|
|
161
175
|
return null;
|
|
162
176
|
}
|
|
163
177
|
return cache.get(cacheKey, () => {
|
|
164
178
|
const view = parsed.views[viewId];
|
|
165
179
|
if (!view) {
|
|
166
|
-
|
|
180
|
+
log.warn`computeView: cant find view ${viewId}`;
|
|
167
181
|
return null;
|
|
168
182
|
}
|
|
169
|
-
|
|
183
|
+
log.debug`computeView: ${viewId}`;
|
|
170
184
|
const computeView = LikeC4Model.makeCompute(parsed);
|
|
171
185
|
const result = computeView(view);
|
|
172
186
|
if (!result.isSuccess) {
|
|
@@ -189,11 +203,11 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
189
203
|
}
|
|
190
204
|
}
|
|
191
205
|
}
|
|
192
|
-
const previous = this.previousViews[
|
|
206
|
+
const previous = this.previousViews[cacheKey];
|
|
193
207
|
if (previous && eq(computedView, previous)) {
|
|
194
208
|
computedView = previous;
|
|
195
209
|
} else {
|
|
196
|
-
this.previousViews[
|
|
210
|
+
this.previousViews[cacheKey] = computedView;
|
|
197
211
|
}
|
|
198
212
|
return computedView;
|
|
199
213
|
});
|
|
@@ -207,8 +221,8 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
207
221
|
}
|
|
208
222
|
});
|
|
209
223
|
}
|
|
210
|
-
documents() {
|
|
211
|
-
return this.parser.documents().toArray();
|
|
224
|
+
documents(projectId) {
|
|
225
|
+
return this.parser.documents(projectId).toArray();
|
|
212
226
|
}
|
|
213
227
|
notifyListeners(docs) {
|
|
214
228
|
for (const listener of this.listeners) {
|
|
@@ -220,6 +234,6 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
220
234
|
}
|
|
221
235
|
}
|
|
222
236
|
}
|
|
223
|
-
function computedViewKey(viewId) {
|
|
224
|
-
return `computed-view-${viewId}`;
|
|
237
|
+
function computedViewKey(projectId, viewId) {
|
|
238
|
+
return `computed-view-${projectId}-${viewId}`;
|
|
225
239
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core';
|
|
2
2
|
import type { Location } from 'vscode-languageserver-types';
|
|
3
|
-
import type { ParsedAstElement } from '../ast';
|
|
3
|
+
import type { ParsedAstElement, ParsedAstView, ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
4
|
import { ast } from '../ast';
|
|
5
5
|
import type { LikeC4Services } from '../module';
|
|
6
6
|
export declare class LikeC4ModelLocator {
|
|
@@ -9,16 +9,17 @@ export declare class LikeC4ModelLocator {
|
|
|
9
9
|
private deploymentsIndex;
|
|
10
10
|
private langiumDocuments;
|
|
11
11
|
private parser;
|
|
12
|
+
private projects;
|
|
12
13
|
constructor(services: LikeC4Services);
|
|
13
14
|
private documents;
|
|
14
|
-
getParsedElement(
|
|
15
|
-
locateElement(fqn: c4.Fqn,
|
|
16
|
-
locateDeploymentElement(fqn: c4.Fqn,
|
|
17
|
-
locateRelation(relationId: c4.RelationId): Location | null;
|
|
18
|
-
locateViewAst(viewId: c4.ViewId): {
|
|
19
|
-
doc:
|
|
20
|
-
view:
|
|
15
|
+
getParsedElement(...args: [ast.Element] | [c4.Fqn] | [c4.Fqn, c4.ProjectId]): ParsedAstElement | null;
|
|
16
|
+
locateElement(fqn: c4.Fqn, projectId?: c4.ProjectId | undefined): Location | null;
|
|
17
|
+
locateDeploymentElement(fqn: c4.Fqn, projectId?: c4.ProjectId | undefined): Location | null;
|
|
18
|
+
locateRelation(relationId: c4.RelationId, projectId?: c4.ProjectId): Location | null;
|
|
19
|
+
locateViewAst(viewId: c4.ViewId, projectId?: c4.ProjectId | undefined): null | {
|
|
20
|
+
doc: ParsedLikeC4LangiumDocument;
|
|
21
|
+
view: ParsedAstView;
|
|
21
22
|
viewAst: ast.LikeC4View;
|
|
22
|
-
}
|
|
23
|
-
locateView(viewId: c4.ViewId): Location | null;
|
|
23
|
+
};
|
|
24
|
+
locateView(viewId: c4.ViewId, projectId?: c4.ProjectId): Location | null;
|
|
24
25
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AstUtils, GrammarUtils } from "langium";
|
|
2
2
|
import { isString } from "remeda";
|
|
3
3
|
import { ast } from "../ast.js";
|
|
4
|
+
import { projectIdFrom } from "../utils/index.js";
|
|
4
5
|
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
|
|
5
6
|
const { getDocument } = AstUtils;
|
|
6
7
|
export class LikeC4ModelLocator {
|
|
@@ -10,18 +11,31 @@ export class LikeC4ModelLocator {
|
|
|
10
11
|
this.deploymentsIndex = services.likec4.DeploymentsIndex;
|
|
11
12
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
12
13
|
this.parser = services.likec4.ModelParser;
|
|
14
|
+
this.projects = services.shared.workspace.ProjectsManager;
|
|
13
15
|
}
|
|
14
16
|
fqnIndex;
|
|
15
17
|
deploymentsIndex;
|
|
16
18
|
langiumDocuments;
|
|
17
19
|
parser;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
projects;
|
|
21
|
+
documents(projectId) {
|
|
22
|
+
return this.parser.documents(projectId);
|
|
20
23
|
}
|
|
21
|
-
getParsedElement(astNodeOrFqn)
|
|
24
|
+
// public getParsedElement(astNodeOrFqn: ast.Element): ParsedAstElement | null
|
|
25
|
+
// public getParsedElement(astNodeOrFqn: c4.Fqn, projectId?: c4.ProjectId): ParsedAstElement | null
|
|
26
|
+
getParsedElement(...args) {
|
|
27
|
+
let astNodeOrFqn;
|
|
28
|
+
let projectId;
|
|
29
|
+
if (args.length === 2) {
|
|
30
|
+
astNodeOrFqn = args[0];
|
|
31
|
+
projectId = args[1];
|
|
32
|
+
} else {
|
|
33
|
+
astNodeOrFqn = args[0];
|
|
34
|
+
projectId = isString(astNodeOrFqn) ? this.projects.ensureProjectId() : projectIdFrom(astNodeOrFqn);
|
|
35
|
+
}
|
|
22
36
|
if (isString(astNodeOrFqn)) {
|
|
23
37
|
const fqn2 = astNodeOrFqn;
|
|
24
|
-
const entry = this.fqnIndex.byFqn(astNodeOrFqn).head();
|
|
38
|
+
const entry = this.fqnIndex.byFqn(projectId, astNodeOrFqn).head();
|
|
25
39
|
if (!entry) {
|
|
26
40
|
return null;
|
|
27
41
|
}
|
|
@@ -35,8 +49,9 @@ export class LikeC4ModelLocator {
|
|
|
35
49
|
const doc = this.parser.parse(getDocument(astNodeOrFqn));
|
|
36
50
|
return doc.c4Elements.find((e) => e.id === fqn) ?? null;
|
|
37
51
|
}
|
|
38
|
-
locateElement(fqn,
|
|
39
|
-
const
|
|
52
|
+
locateElement(fqn, projectId) {
|
|
53
|
+
const _projectId = this.projects.ensureProjectId(projectId);
|
|
54
|
+
const entry = this.fqnIndex.byFqn(_projectId, fqn).head();
|
|
40
55
|
const docsegment = entry?.nameSegment ?? entry?.selectionSegment;
|
|
41
56
|
if (!entry || !docsegment) {
|
|
42
57
|
return null;
|
|
@@ -46,8 +61,9 @@ export class LikeC4ModelLocator {
|
|
|
46
61
|
range: docsegment.range
|
|
47
62
|
};
|
|
48
63
|
}
|
|
49
|
-
locateDeploymentElement(fqn,
|
|
50
|
-
const
|
|
64
|
+
locateDeploymentElement(fqn, projectId) {
|
|
65
|
+
const _projectId = this.projects.ensureProjectId(projectId);
|
|
66
|
+
const entry = this.deploymentsIndex.byFqn(_projectId, fqn).head();
|
|
51
67
|
const docsegment = entry?.nameSegment ?? entry?.selectionSegment;
|
|
52
68
|
if (!entry || !docsegment) {
|
|
53
69
|
return null;
|
|
@@ -57,8 +73,9 @@ export class LikeC4ModelLocator {
|
|
|
57
73
|
range: docsegment.range
|
|
58
74
|
};
|
|
59
75
|
}
|
|
60
|
-
locateRelation(relationId) {
|
|
61
|
-
|
|
76
|
+
locateRelation(relationId, projectId) {
|
|
77
|
+
const project = this.projects.ensureProjectId(projectId);
|
|
78
|
+
for (const doc of this.documents(project)) {
|
|
62
79
|
const relation = doc.c4Relations.find((r) => r.id === relationId) ?? doc.c4DeploymentRelations.find((r) => r.id === relationId);
|
|
63
80
|
if (!relation) {
|
|
64
81
|
continue;
|
|
@@ -84,8 +101,9 @@ export class LikeC4ModelLocator {
|
|
|
84
101
|
}
|
|
85
102
|
return null;
|
|
86
103
|
}
|
|
87
|
-
locateViewAst(viewId) {
|
|
88
|
-
|
|
104
|
+
locateViewAst(viewId, projectId) {
|
|
105
|
+
const project = this.projects.ensureProjectId(projectId);
|
|
106
|
+
for (const doc of this.documents(project)) {
|
|
89
107
|
const view = doc.c4Views.find((r) => r.id === viewId);
|
|
90
108
|
if (!view) {
|
|
91
109
|
continue;
|
|
@@ -104,8 +122,8 @@ export class LikeC4ModelLocator {
|
|
|
104
122
|
}
|
|
105
123
|
return null;
|
|
106
124
|
}
|
|
107
|
-
locateView(viewId) {
|
|
108
|
-
const res = this.locateViewAst(viewId);
|
|
125
|
+
locateView(viewId, projectId) {
|
|
126
|
+
const res = this.locateViewAst(viewId, projectId);
|
|
109
127
|
if (!res) {
|
|
110
128
|
return null;
|
|
111
129
|
}
|