@likec4/language-server 1.24.0 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Rpc.js +42 -26
- package/dist/bundled.mjs +3936 -2439
- package/dist/lsp/CodeLensProvider.js +5 -8
- package/dist/lsp/DocumentLinkProvider.js +4 -13
- package/dist/lsp/DocumentSymbolProvider.d.ts +4 -3
- package/dist/lsp/DocumentSymbolProvider.js +17 -7
- package/dist/model/deployments-index.d.ts +2 -3
- package/dist/model/deployments-index.js +14 -12
- package/dist/model/fqn-index.d.ts +7 -6
- package/dist/model/fqn-index.js +64 -44
- package/dist/model/model-builder.d.ts +2 -5
- package/dist/model/model-builder.js +43 -25
- package/dist/model/model-locator.d.ts +1 -1
- package/dist/model/model-locator.js +1 -1
- package/dist/model/model-parser.d.ts +148 -148
- package/dist/model/model-parser.js +26 -16
- package/dist/model-change/ModelChanges.d.ts +3 -3
- package/dist/protocol.d.ts +88 -61
- package/dist/protocol.js +32 -14
- package/dist/test/testServices.js +7 -7
- package/dist/views/likec4-views.js +15 -2
- package/package.json +14 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocumentState
|
|
1
|
+
import { DocumentState } from "langium";
|
|
2
2
|
import { isLikeC4LangiumDocument, ViewOps } from "../ast.js";
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
4
|
export class LikeC4CodeLensProvider {
|
|
@@ -9,13 +9,10 @@ export class LikeC4CodeLensProvider {
|
|
|
9
9
|
if (!isLikeC4LangiumDocument(doc)) {
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
|
-
if (doc.state
|
|
13
|
-
logger.debug(`Waiting for document ${doc.uri.path} to be
|
|
14
|
-
await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.
|
|
15
|
-
logger.debug(`Document
|
|
16
|
-
}
|
|
17
|
-
if (cancelToken) {
|
|
18
|
-
await interruptAndCheck(cancelToken);
|
|
12
|
+
if (doc.state <= DocumentState.Linked) {
|
|
13
|
+
logger.debug(`Waiting for document ${doc.uri.path} to be Linked`);
|
|
14
|
+
await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Linked, doc.uri, cancelToken);
|
|
15
|
+
logger.debug(`Document is linked`);
|
|
19
16
|
}
|
|
20
17
|
const views = doc.parseResult.value.views.flatMap((v) => v.views);
|
|
21
18
|
return views.flatMap((ast) => {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { AstUtils,
|
|
1
|
+
import { AstUtils, GrammarUtils } from "langium";
|
|
2
2
|
import { hasLeadingSlash, hasProtocol, isRelative, withoutBase, withoutLeadingSlash } from "ufo";
|
|
3
3
|
import { ast, isLikeC4LangiumDocument } from "../ast.js";
|
|
4
|
-
import {
|
|
5
|
-
const log = logger.getChild("DocumentLinkProvider");
|
|
4
|
+
import { logWarnError } from "../logger.js";
|
|
6
5
|
export class LikeC4DocumentLinkProvider {
|
|
7
6
|
constructor(services) {
|
|
8
7
|
this.services = services;
|
|
@@ -11,19 +10,11 @@ export class LikeC4DocumentLinkProvider {
|
|
|
11
10
|
if (!isLikeC4LangiumDocument(doc)) {
|
|
12
11
|
return [];
|
|
13
12
|
}
|
|
14
|
-
if (doc.state !== DocumentState.Validated) {
|
|
15
|
-
log.debug(`Waiting for document ${doc.uri.path} to be validated`);
|
|
16
|
-
await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Validated, doc.uri, cancelToken);
|
|
17
|
-
log.debug(`Document ${doc.uri.path} is validated`);
|
|
18
|
-
}
|
|
19
|
-
if (cancelToken) {
|
|
20
|
-
await interruptAndCheck(cancelToken);
|
|
21
|
-
}
|
|
22
13
|
return AstUtils.streamAllContents(doc.parseResult.value).filter(ast.isLinkProperty).map((n) => {
|
|
23
14
|
try {
|
|
24
15
|
const range = GrammarUtils.findNodeForProperty(n.$cstNode, "value")?.range;
|
|
25
|
-
const target = this.resolveLink(doc, n.value);
|
|
26
|
-
if (
|
|
16
|
+
const target = range && this.resolveLink(doc, n.value);
|
|
17
|
+
if (target && hasProtocol(target)) {
|
|
27
18
|
return {
|
|
28
19
|
range,
|
|
29
20
|
target
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type AstNode, type
|
|
1
|
+
import { type AstNode, type LangiumDocument } from 'langium';
|
|
2
2
|
import type { DocumentSymbolProvider, NodeKindProvider } from 'langium/lsp';
|
|
3
|
+
import type { CancellationToken, DocumentSymbolParams } from 'vscode-languageserver';
|
|
3
4
|
import { type DocumentSymbol, SymbolKind } from 'vscode-languageserver-types';
|
|
4
|
-
import {
|
|
5
|
+
import { ast } from '../ast';
|
|
5
6
|
import type { LikeC4ModelLocator, LikeC4ModelParser } from '../model';
|
|
6
7
|
import type { LikeC4Services } from '../module';
|
|
7
8
|
import type { LikeC4NameProvider } from '../references';
|
|
@@ -12,7 +13,7 @@ export declare class LikeC4DocumentSymbolProvider implements DocumentSymbolProvi
|
|
|
12
13
|
protected readonly parser: LikeC4ModelParser;
|
|
13
14
|
protected readonly locator: LikeC4ModelLocator;
|
|
14
15
|
constructor(services: LikeC4Services);
|
|
15
|
-
getSymbols(
|
|
16
|
+
getSymbols(doc: LangiumDocument, _params: DocumentSymbolParams, cancelToken?: CancellationToken): Promise<DocumentSymbol[]>;
|
|
16
17
|
protected getLikec4LibSymbol(astLib: ast.LikeC4Lib): DocumentSymbol[];
|
|
17
18
|
protected getSpecSymbol(astSpec: ast.SpecificationRule): DocumentSymbol[];
|
|
18
19
|
protected getModelSymbol(astModel: ast.Model): DocumentSymbol[];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { nonexhaustive } from "@likec4/core";
|
|
2
|
-
import { AstUtils, GrammarUtils } from "langium";
|
|
2
|
+
import { AstUtils, DocumentState, GrammarUtils } from "langium";
|
|
3
3
|
import { filter, isEmpty, isTruthy, map, pipe } from "remeda";
|
|
4
4
|
import { SymbolKind } from "vscode-languageserver-types";
|
|
5
|
-
import { ast } from "../ast.js";
|
|
6
|
-
import { logWarnError } from "../logger.js";
|
|
5
|
+
import { ast, isLikeC4LangiumDocument } from "../ast.js";
|
|
6
|
+
import { logger as rootLogger, logWarnError } from "../logger.js";
|
|
7
7
|
import { readStrictFqn } from "../utils/elementRef.js";
|
|
8
|
+
const logger = rootLogger.getChild("DocumentSymbolProvider");
|
|
8
9
|
export class LikeC4DocumentSymbolProvider {
|
|
9
10
|
constructor(services) {
|
|
10
11
|
this.services = services;
|
|
@@ -17,11 +18,20 @@ export class LikeC4DocumentSymbolProvider {
|
|
|
17
18
|
nameProvider;
|
|
18
19
|
parser;
|
|
19
20
|
locator;
|
|
20
|
-
getSymbols({
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
async getSymbols(doc, _params, cancelToken) {
|
|
22
|
+
if (!isLikeC4LangiumDocument(doc)) {
|
|
23
|
+
return [];
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
if (doc.state <= DocumentState.Linked) {
|
|
26
|
+
logger.debug(`Waiting for document ${doc.uri.path} to be Linked`);
|
|
27
|
+
await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Linked, doc.uri, cancelToken);
|
|
28
|
+
logger.debug(`document is Linked`);
|
|
29
|
+
}
|
|
30
|
+
const {
|
|
31
|
+
parseResult: {
|
|
32
|
+
value: { specifications, models, deployments, views, likec4lib }
|
|
33
|
+
}
|
|
34
|
+
} = doc;
|
|
25
35
|
return [
|
|
26
36
|
...likec4lib.map((l) => () => this.getLikec4LibSymbol(l)),
|
|
27
37
|
...specifications.map((s) => () => this.getSpecSymbol(s)),
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { type LikeC4LangiumDocument } from '../ast';
|
|
1
|
+
import { type LikeC4LangiumDocument, ast } from '../ast';
|
|
2
2
|
import type { LikeC4Services } from '../module';
|
|
3
3
|
import type { LikeC4NameProvider } from '../references';
|
|
4
4
|
import { DocumentFqnIndex, FqnIndex } from './fqn-index';
|
|
5
|
-
export declare class DeploymentsIndex extends FqnIndex {
|
|
5
|
+
export declare class DeploymentsIndex extends FqnIndex<ast.DeploymentElement> {
|
|
6
6
|
protected services: LikeC4Services;
|
|
7
7
|
protected Names: LikeC4NameProvider;
|
|
8
|
-
protected cachePrefix: string;
|
|
9
8
|
constructor(services: LikeC4Services);
|
|
10
9
|
protected createDocumentIndex(document: LikeC4LangiumDocument): DocumentFqnIndex;
|
|
11
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ancestorsFqn, AsFqn } from "@likec4/core";
|
|
2
|
-
import { MultiMap } from "
|
|
2
|
+
import { MultiMap } from "@likec4/core/utils";
|
|
3
3
|
import { isDefined, isTruthy } from "remeda";
|
|
4
4
|
import {
|
|
5
5
|
ast,
|
|
@@ -10,12 +10,11 @@ import { readStrictFqn } from "../utils/elementRef.js";
|
|
|
10
10
|
import { DocumentFqnIndex, FqnIndex } from "./fqn-index.js";
|
|
11
11
|
export class DeploymentsIndex extends FqnIndex {
|
|
12
12
|
constructor(services) {
|
|
13
|
-
super(services);
|
|
13
|
+
super(services, "deployments-index");
|
|
14
14
|
this.services = services;
|
|
15
15
|
this.Names = services.references.NameProvider;
|
|
16
16
|
}
|
|
17
17
|
Names;
|
|
18
|
-
cachePrefix = "deployments-index";
|
|
19
18
|
createDocumentIndex(document) {
|
|
20
19
|
const rootNodes = document.parseResult.value.deployments.flatMap((m) => m.elements);
|
|
21
20
|
if (rootNodes.length === 0) {
|
|
@@ -33,7 +32,7 @@ export class DeploymentsIndex extends FqnIndex {
|
|
|
33
32
|
id: fqn
|
|
34
33
|
};
|
|
35
34
|
ElementOps.writeId(node, fqn);
|
|
36
|
-
byfqn.
|
|
35
|
+
byfqn.set(fqn, desc);
|
|
37
36
|
return desc;
|
|
38
37
|
};
|
|
39
38
|
const traverseNode = (node, parentFqn) => {
|
|
@@ -50,7 +49,7 @@ export class DeploymentsIndex extends FqnIndex {
|
|
|
50
49
|
if (!parentFqn) {
|
|
51
50
|
root.push(desc);
|
|
52
51
|
} else {
|
|
53
|
-
children.
|
|
52
|
+
children.set(parentFqn, desc);
|
|
54
53
|
}
|
|
55
54
|
if (ast.isDeployedInstance(node)) {
|
|
56
55
|
return [];
|
|
@@ -68,18 +67,21 @@ export class DeploymentsIndex extends FqnIndex {
|
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
|
-
const directChildren = children.get(thisFqn);
|
|
72
70
|
_nested = [
|
|
73
|
-
...
|
|
71
|
+
...children.get(thisFqn) ?? [],
|
|
74
72
|
..._nested
|
|
75
73
|
];
|
|
76
|
-
|
|
74
|
+
for (const child of _nested) {
|
|
75
|
+
descendants.set(thisFqn, child);
|
|
76
|
+
}
|
|
77
77
|
if (ast.isExtendDeployment(node)) {
|
|
78
|
-
ancestorsFqn(thisFqn)
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
for (const ancestor of ancestorsFqn(thisFqn)) {
|
|
79
|
+
for (const child of _nested) {
|
|
80
|
+
descendants.set(ancestor, child);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
81
83
|
}
|
|
82
|
-
return descendants.get(thisFqn);
|
|
84
|
+
return descendants.get(thisFqn) ?? [];
|
|
83
85
|
};
|
|
84
86
|
for (const node of rootNodes) {
|
|
85
87
|
try {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { type Fqn } from '@likec4/core/types';
|
|
2
|
-
import { DefaultWeakMap } from '@likec4/core/utils';
|
|
3
|
-
import { type LangiumDocuments, type Stream,
|
|
2
|
+
import { DefaultWeakMap, MultiMap } from '@likec4/core/utils';
|
|
3
|
+
import { type AstNode, type LangiumDocuments, 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
|
-
export declare class FqnIndex extends ADisposable {
|
|
7
|
+
export declare class FqnIndex<AstNd extends AstNode = ast.Element> extends ADisposable {
|
|
8
8
|
protected services: LikeC4Services;
|
|
9
|
+
private cachePrefix;
|
|
9
10
|
protected langiumDocuments: LangiumDocuments;
|
|
10
11
|
protected documentCache: DefaultWeakMap<LikeC4LangiumDocument, DocumentFqnIndex>;
|
|
11
|
-
protected
|
|
12
|
-
constructor(services: LikeC4Services);
|
|
12
|
+
protected workspaceCache: WorkspaceCache<string, AstNodeDescriptionWithFqn[]>;
|
|
13
|
+
constructor(services: LikeC4Services, cachePrefix?: string);
|
|
13
14
|
private documents;
|
|
14
15
|
get(document: LikeC4LangiumDocument): DocumentFqnIndex;
|
|
15
|
-
getFqn(el:
|
|
16
|
+
getFqn(el: AstNd): Fqn;
|
|
16
17
|
byFqn(fqn: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
17
18
|
directChildrenOf(parent: Fqn): Stream<AstNodeDescriptionWithFqn>;
|
|
18
19
|
/**
|
package/dist/model/fqn-index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { invariant, nonNullable } from "@likec4/core";
|
|
2
2
|
import { AsFqn } from "@likec4/core/types";
|
|
3
|
-
import { ancestorsFqn, compareNatural, DefaultWeakMap, sortNaturalByFqn } from "@likec4/core/utils";
|
|
3
|
+
import { ancestorsFqn, compareNatural, DefaultWeakMap, MultiMap, sortNaturalByFqn } from "@likec4/core/utils";
|
|
4
4
|
import {
|
|
5
5
|
AstUtils,
|
|
6
6
|
DocumentState,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
stream,
|
|
8
|
+
WorkspaceCache
|
|
9
9
|
} from "langium";
|
|
10
10
|
import { isDefined, isEmpty, isTruthy } from "remeda";
|
|
11
11
|
import {
|
|
@@ -17,11 +17,13 @@ import { logWarnError } from "../logger.js";
|
|
|
17
17
|
import { ADisposable } from "../utils/index.js";
|
|
18
18
|
import { readStrictFqn } from "../utils/elementRef.js";
|
|
19
19
|
export class FqnIndex extends ADisposable {
|
|
20
|
-
constructor(services) {
|
|
20
|
+
constructor(services, cachePrefix = "fqn-index") {
|
|
21
21
|
super();
|
|
22
22
|
this.services = services;
|
|
23
|
+
this.cachePrefix = cachePrefix;
|
|
23
24
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
24
25
|
this.documentCache = new DefaultWeakMap((doc) => this.createDocumentIndex(doc));
|
|
26
|
+
this.workspaceCache = new WorkspaceCache(services.shared, DocumentState.IndexedContent);
|
|
25
27
|
this.onDispose(
|
|
26
28
|
services.shared.workspace.DocumentBuilder.onDocumentPhase(
|
|
27
29
|
DocumentState.IndexedContent,
|
|
@@ -36,7 +38,7 @@ export class FqnIndex extends ADisposable {
|
|
|
36
38
|
}
|
|
37
39
|
langiumDocuments;
|
|
38
40
|
documentCache;
|
|
39
|
-
|
|
41
|
+
workspaceCache;
|
|
40
42
|
documents() {
|
|
41
43
|
return this.langiumDocuments.all.filter(
|
|
42
44
|
(d) => isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
|
|
@@ -49,53 +51,64 @@ export class FqnIndex extends ADisposable {
|
|
|
49
51
|
return this.documentCache.get(document);
|
|
50
52
|
}
|
|
51
53
|
getFqn(el) {
|
|
54
|
+
invariant(ast.isElement(el) || ast.isDeploymentElement(el));
|
|
52
55
|
let id = ElementOps.readId(el);
|
|
53
56
|
if (isTruthy(id)) {
|
|
54
57
|
return id;
|
|
55
58
|
}
|
|
56
59
|
const doc = AstUtils.getDocument(el);
|
|
57
60
|
invariant(isLikeC4LangiumDocument(doc));
|
|
61
|
+
logWarnError(`Document ${doc.uri.path} is not indexed, but getFqn was called`);
|
|
58
62
|
this.get(doc);
|
|
59
63
|
return nonNullable(ElementOps.readId(el), "Element fqn must be set, invalid state");
|
|
60
64
|
}
|
|
61
65
|
byFqn(fqn) {
|
|
62
|
-
return this.
|
|
63
|
-
return this.
|
|
64
|
-
|
|
66
|
+
return stream(this.workspaceCache.get(`${this.cachePrefix}:${fqn}`, () => {
|
|
67
|
+
return this.documents().toArray().flatMap((doc) => {
|
|
68
|
+
return this.get(doc).byFqn(fqn);
|
|
69
|
+
});
|
|
70
|
+
}));
|
|
65
71
|
}
|
|
66
72
|
directChildrenOf(parent) {
|
|
67
73
|
return stream(
|
|
68
|
-
this.
|
|
69
|
-
this.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
this.workspaceCache.get(`${this.cachePrefix}:directChildrenOf:${parent}`, () => {
|
|
75
|
+
const allchildren = this.documents().reduce((map, doc) => {
|
|
76
|
+
this.get(doc).children(parent).forEach((desc) => {
|
|
77
|
+
map.set(desc.name, desc);
|
|
78
|
+
});
|
|
79
|
+
return map;
|
|
80
|
+
}, new MultiMap());
|
|
81
|
+
return uniqueByName(allchildren).sort((a, b) => compareNatural(a.name, b.name));
|
|
82
|
+
})
|
|
74
83
|
);
|
|
75
84
|
}
|
|
76
85
|
/**
|
|
77
86
|
* Returns descedant elements with unique names in the scope
|
|
78
87
|
*/
|
|
79
88
|
uniqueDescedants(parent) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
return stream(
|
|
90
|
+
this.workspaceCache.get(`${this.cachePrefix}:uniqueDescedants:${parent}`, () => {
|
|
91
|
+
const { children, descendants } = this.documents().reduce((map, doc) => {
|
|
92
|
+
const docIndex = this.get(doc);
|
|
93
|
+
docIndex.children(parent).forEach((desc) => {
|
|
94
|
+
map.children.set(desc.name, desc);
|
|
95
|
+
});
|
|
96
|
+
docIndex.descendants(parent).forEach((desc) => {
|
|
97
|
+
map.descendants.set(desc.name, desc);
|
|
98
|
+
});
|
|
99
|
+
return map;
|
|
100
|
+
}, {
|
|
101
|
+
children: new MultiMap(),
|
|
102
|
+
descendants: new MultiMap()
|
|
103
|
+
});
|
|
104
|
+
const uniqueChildren = uniqueByName(children).sort((a, b) => compareNatural(a.name, b.name));
|
|
105
|
+
const uniqueDescendants = [...descendants.associations()].flatMap(([_name, descs]) => descs.length === 1 && !children.has(_name) ? descs : []);
|
|
106
|
+
return [
|
|
107
|
+
...uniqueChildren,
|
|
108
|
+
...sortNaturalByFqn(uniqueDescendants)
|
|
109
|
+
];
|
|
110
|
+
})
|
|
111
|
+
);
|
|
99
112
|
}
|
|
100
113
|
createDocumentIndex(document) {
|
|
101
114
|
const rootElements = document.parseResult.value.models.flatMap((m) => m.elements);
|
|
@@ -113,7 +126,7 @@ export class FqnIndex extends ADisposable {
|
|
|
113
126
|
id: fqn
|
|
114
127
|
};
|
|
115
128
|
ElementOps.writeId(node, fqn);
|
|
116
|
-
byfqn.
|
|
129
|
+
byfqn.set(fqn, desc);
|
|
117
130
|
return desc;
|
|
118
131
|
};
|
|
119
132
|
const traverseNode = (el, parentFqn) => {
|
|
@@ -124,7 +137,7 @@ export class FqnIndex extends ADisposable {
|
|
|
124
137
|
if (!parentFqn) {
|
|
125
138
|
root.push(desc);
|
|
126
139
|
} else {
|
|
127
|
-
children.
|
|
140
|
+
children.set(parentFqn, desc);
|
|
128
141
|
}
|
|
129
142
|
} else {
|
|
130
143
|
thisFqn = readStrictFqn(el.element);
|
|
@@ -141,18 +154,22 @@ export class FqnIndex extends ADisposable {
|
|
|
141
154
|
}
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
|
-
const directChildren = children.get(thisFqn);
|
|
157
|
+
const directChildren = children.get(thisFqn) ?? [];
|
|
145
158
|
_nested = [
|
|
146
159
|
...directChildren,
|
|
147
160
|
..._nested
|
|
148
161
|
];
|
|
149
|
-
|
|
162
|
+
for (const child of _nested) {
|
|
163
|
+
descendants.set(thisFqn, child);
|
|
164
|
+
}
|
|
150
165
|
if (ast.isExtendElement(el)) {
|
|
151
|
-
ancestorsFqn(thisFqn)
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
for (const ancestor of ancestorsFqn(thisFqn)) {
|
|
167
|
+
for (const child of _nested) {
|
|
168
|
+
descendants.set(ancestor, child);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
154
171
|
}
|
|
155
|
-
return descendants.get(thisFqn);
|
|
172
|
+
return descendants.get(thisFqn) ?? [];
|
|
156
173
|
};
|
|
157
174
|
for (const node of rootElements) {
|
|
158
175
|
try {
|
|
@@ -167,6 +184,9 @@ export class FqnIndex extends ADisposable {
|
|
|
167
184
|
return new DocumentFqnIndex(root, children, descendants, byfqn);
|
|
168
185
|
}
|
|
169
186
|
}
|
|
187
|
+
function uniqueByName(multimap) {
|
|
188
|
+
return [...multimap.associations()].flatMap(([_name, descs]) => descs.length === 1 ? descs : []);
|
|
189
|
+
}
|
|
170
190
|
export class DocumentFqnIndex {
|
|
171
191
|
constructor(_rootElements, _children, _descendants, _byfqn) {
|
|
172
192
|
this._rootElements = _rootElements;
|
|
@@ -179,12 +199,12 @@ export class DocumentFqnIndex {
|
|
|
179
199
|
return this._rootElements;
|
|
180
200
|
}
|
|
181
201
|
byFqn(fqn) {
|
|
182
|
-
return this._byfqn.get(fqn);
|
|
202
|
+
return this._byfqn.get(fqn) ?? [];
|
|
183
203
|
}
|
|
184
204
|
children(parent) {
|
|
185
|
-
return this._children.get(parent);
|
|
205
|
+
return this._children.get(parent) ?? [];
|
|
186
206
|
}
|
|
187
207
|
descendants(nodeName) {
|
|
188
|
-
return this._descendants.get(nodeName);
|
|
208
|
+
return this._descendants.get(nodeName) ?? [];
|
|
189
209
|
}
|
|
190
210
|
}
|
|
@@ -4,15 +4,12 @@ import { type Cancellation, type URI, Disposable } from 'langium';
|
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
5
|
import { ADisposable } from '../utils';
|
|
6
6
|
type ModelParsedListener = (docs: URI[]) => void;
|
|
7
|
-
type ParseModelResult = {
|
|
8
|
-
model: c4.ParsedLikeC4Model;
|
|
9
|
-
computeView: (view: c4.LikeC4View) => c4.ComputeViewResult;
|
|
10
|
-
};
|
|
11
7
|
export declare class LikeC4ModelBuilder extends ADisposable {
|
|
12
8
|
private parser;
|
|
13
9
|
private listeners;
|
|
14
10
|
private cache;
|
|
15
11
|
private DocumentBuilder;
|
|
12
|
+
private LangiumDocuments;
|
|
16
13
|
constructor(services: LikeC4Services);
|
|
17
14
|
/**
|
|
18
15
|
* WARNING:
|
|
@@ -20,7 +17,7 @@ export declare class LikeC4ModelBuilder extends ADisposable {
|
|
|
20
17
|
* Otherwise, the model may be incomplete.
|
|
21
18
|
*/
|
|
22
19
|
private unsafeSyncParseModel;
|
|
23
|
-
parseModel(cancelToken?: Cancellation.CancellationToken): Promise<
|
|
20
|
+
parseModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null>;
|
|
24
21
|
private previousViews;
|
|
25
22
|
/**
|
|
26
23
|
* WARNING:
|
|
@@ -8,30 +8,36 @@ import {
|
|
|
8
8
|
Disposable,
|
|
9
9
|
DocumentState
|
|
10
10
|
} from "langium";
|
|
11
|
+
import prettyMs from "pretty-ms";
|
|
11
12
|
import {
|
|
12
13
|
filter,
|
|
13
14
|
groupBy,
|
|
15
|
+
isNot,
|
|
14
16
|
mapToObj,
|
|
15
17
|
pipe,
|
|
18
|
+
prop,
|
|
16
19
|
values
|
|
17
20
|
} from "remeda";
|
|
21
|
+
import { isLikeC4Builtin } from "../likec4lib.js";
|
|
18
22
|
import { logger as mainLogger, logWarnError } from "../logger.js";
|
|
19
23
|
import { ADisposable } from "../utils/index.js";
|
|
20
24
|
import { assignNavigateTo } from "../view-utils/index.js";
|
|
21
25
|
import { buildModel } from "./builder/buildModel.js";
|
|
22
26
|
const CACHE_KEY_PARSED_MODEL = "ParsedLikeC4Model";
|
|
23
27
|
const CACHE_KEY_COMPUTED_MODEL = "ComputedLikeC4Model";
|
|
24
|
-
const logger = mainLogger.getChild("
|
|
28
|
+
const logger = mainLogger.getChild("ModelBuilder");
|
|
25
29
|
export class LikeC4ModelBuilder extends ADisposable {
|
|
26
30
|
parser;
|
|
27
31
|
listeners = [];
|
|
28
32
|
cache;
|
|
29
33
|
DocumentBuilder;
|
|
34
|
+
LangiumDocuments;
|
|
30
35
|
constructor(services) {
|
|
31
36
|
super();
|
|
32
37
|
this.parser = services.likec4.ModelParser;
|
|
33
38
|
this.cache = services.ValidatedWorkspaceCache;
|
|
34
39
|
this.DocumentBuilder = services.shared.workspace.DocumentBuilder;
|
|
40
|
+
this.LangiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
35
41
|
this.onDispose(
|
|
36
42
|
this.DocumentBuilder.onUpdate((_changed, deleted) => {
|
|
37
43
|
if (deleted.length > 0) {
|
|
@@ -43,8 +49,10 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
43
49
|
this.DocumentBuilder.onBuildPhase(
|
|
44
50
|
DocumentState.Validated,
|
|
45
51
|
(docs, _cancelToken) => {
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const validated = docs.map(prop("uri")).filter(isNot(isLikeC4Builtin));
|
|
53
|
+
if (validated.length > 0) {
|
|
54
|
+
this.notifyListeners(validated);
|
|
55
|
+
}
|
|
48
56
|
}
|
|
49
57
|
)
|
|
50
58
|
);
|
|
@@ -63,20 +71,25 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
63
71
|
}
|
|
64
72
|
const cache = this.cache;
|
|
65
73
|
return cache.get(CACHE_KEY_PARSED_MODEL, () => {
|
|
66
|
-
|
|
67
|
-
const model = buildModel(docs);
|
|
68
|
-
const computeView = LikeC4Model.makeCompute(model);
|
|
69
|
-
return { model, computeView };
|
|
74
|
+
return buildModel(docs);
|
|
70
75
|
});
|
|
71
76
|
}
|
|
72
77
|
async parseModel(cancelToken) {
|
|
73
78
|
const cache = this.cache;
|
|
74
79
|
const cached = cache.get(CACHE_KEY_PARSED_MODEL);
|
|
75
80
|
if (cached) {
|
|
76
|
-
|
|
81
|
+
logger.debug("parseModel from cache");
|
|
82
|
+
return cached;
|
|
83
|
+
}
|
|
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");
|
|
77
88
|
}
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
const t0 = performance.now();
|
|
90
|
+
const result = this.unsafeSyncParseModel();
|
|
91
|
+
logger.debug(`parseModel in ${prettyMs(performance.now() - t0)}`);
|
|
92
|
+
return result;
|
|
80
93
|
}
|
|
81
94
|
previousViews = {};
|
|
82
95
|
/**
|
|
@@ -85,20 +98,18 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
85
98
|
* Otherwise, the model may be incomplete.
|
|
86
99
|
*/
|
|
87
100
|
unsafeSyncBuildModel() {
|
|
88
|
-
const parsed = this.unsafeSyncParseModel();
|
|
89
|
-
if (!parsed) {
|
|
90
|
-
return LikeC4Model.EMPTY;
|
|
91
|
-
}
|
|
92
101
|
const cache = this.cache;
|
|
93
102
|
const viewsCache = this.cache;
|
|
94
103
|
return cache.get(CACHE_KEY_COMPUTED_MODEL, () => {
|
|
104
|
+
const parsed = this.unsafeSyncParseModel();
|
|
105
|
+
if (!parsed) {
|
|
106
|
+
return LikeC4Model.EMPTY;
|
|
107
|
+
}
|
|
95
108
|
const {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
...model
|
|
99
|
-
},
|
|
100
|
-
computeView
|
|
109
|
+
views: parsedViews,
|
|
110
|
+
...model
|
|
101
111
|
} = parsed;
|
|
112
|
+
const computeView = LikeC4Model.makeCompute(parsed);
|
|
102
113
|
const allViews = [];
|
|
103
114
|
for (const view of values(parsedViews)) {
|
|
104
115
|
const result = computeView(view);
|
|
@@ -126,13 +137,18 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
126
137
|
const cache = this.cache;
|
|
127
138
|
const cached = cache.get(CACHE_KEY_COMPUTED_MODEL);
|
|
128
139
|
if (cached) {
|
|
129
|
-
|
|
140
|
+
logger.debug("buildLikeC4Model from cache");
|
|
141
|
+
return cached;
|
|
130
142
|
}
|
|
143
|
+
const t0 = performance.now();
|
|
131
144
|
const model = await this.parseModel(cancelToken);
|
|
132
145
|
if (!model) {
|
|
146
|
+
logger.debug("buildLikeC4Model: no model");
|
|
133
147
|
return LikeC4Model.EMPTY;
|
|
134
148
|
}
|
|
135
|
-
|
|
149
|
+
const result = this.unsafeSyncBuildModel();
|
|
150
|
+
logger.debug(`buildLikeC4Model in ${prettyMs(performance.now() - t0)}`);
|
|
151
|
+
return result;
|
|
136
152
|
}
|
|
137
153
|
async computeView(viewId, cancelToken) {
|
|
138
154
|
const cache = this.cache;
|
|
@@ -145,19 +161,21 @@ export class LikeC4ModelBuilder extends ADisposable {
|
|
|
145
161
|
return null;
|
|
146
162
|
}
|
|
147
163
|
return cache.get(cacheKey, () => {
|
|
148
|
-
const view = parsed.
|
|
164
|
+
const view = parsed.views[viewId];
|
|
149
165
|
if (!view) {
|
|
150
|
-
logger.warn
|
|
166
|
+
logger.warn`computeView: cant find view ${viewId}`;
|
|
151
167
|
return null;
|
|
152
168
|
}
|
|
153
|
-
|
|
169
|
+
logger.debug`computeView: ${viewId}`;
|
|
170
|
+
const computeView = LikeC4Model.makeCompute(parsed);
|
|
171
|
+
const result = computeView(view);
|
|
154
172
|
if (!result.isSuccess) {
|
|
155
173
|
logWarnError(result.error);
|
|
156
174
|
return null;
|
|
157
175
|
}
|
|
158
176
|
let computedView = result.view;
|
|
159
177
|
const allElementViews = pipe(
|
|
160
|
-
parsed.
|
|
178
|
+
parsed.views,
|
|
161
179
|
values(),
|
|
162
180
|
filter(isScopedElementView),
|
|
163
181
|
filter((v) => v.id !== viewId),
|
|
@@ -16,7 +16,7 @@ export declare class LikeC4ModelLocator {
|
|
|
16
16
|
locateDeploymentElement(fqn: c4.Fqn, _prop?: string): Location | null;
|
|
17
17
|
locateRelation(relationId: c4.RelationId): Location | null;
|
|
18
18
|
locateViewAst(viewId: c4.ViewId): {
|
|
19
|
-
doc:
|
|
19
|
+
doc: Stream<import("../ast").ParsedLikeC4LangiumDocument>;
|
|
20
20
|
view: any;
|
|
21
21
|
viewAst: ast.LikeC4View;
|
|
22
22
|
} | null;
|