@likec4/language-server 1.1.0 → 1.2.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/contrib/likec4.monarch.ts +4 -4
- package/contrib/likec4.tmLanguage.json +1 -1
- package/dist/ast.d.ts +28 -11
- package/dist/ast.js +14 -13
- package/dist/generated/ast.d.ts +75 -19
- package/dist/generated/ast.js +96 -7
- package/dist/generated/grammar.js +1 -1
- package/dist/lsp/CodeLensProvider.js +5 -2
- package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
- package/dist/lsp/DocumentSymbolProvider.js +14 -9
- package/dist/lsp/SemanticTokenProvider.js +1 -1
- package/dist/model/model-builder.js +68 -45
- package/dist/model/model-locator.d.ts +2 -2
- package/dist/model/model-locator.js +12 -16
- package/dist/model/model-parser.d.ts +2 -0
- package/dist/model/model-parser.js +140 -30
- package/dist/model-change/ModelChanges.d.ts +2 -1
- package/dist/model-change/ModelChanges.js +39 -35
- package/dist/model-change/changeElementStyle.d.ts +18 -0
- package/dist/model-change/changeElementStyle.js +141 -0
- package/dist/model-change/changeViewLayout.d.ts +4 -4
- package/dist/model-change/changeViewLayout.js +4 -5
- package/dist/protocol.d.ts +12 -12
- package/dist/references/scope-computation.js +36 -31
- package/dist/shared/NodeKindProvider.js +4 -2
- package/dist/validation/dynamic-view-rule.d.ts +5 -0
- package/dist/validation/dynamic-view-rule.js +32 -0
- package/dist/validation/dynamic-view-step.d.ts +5 -0
- package/dist/validation/dynamic-view-step.js +33 -0
- package/dist/validation/index.js +5 -1
- package/dist/validation/view-predicates/expanded-element.js +1 -0
- package/dist/validation/view-predicates/outgoing.js +2 -2
- package/dist/validation/view.d.ts +1 -1
- package/dist/validation/view.js +1 -3
- package/dist/view-utils/assignNavigateTo.d.ts +1 -1
- package/dist/view-utils/assignNavigateTo.js +2 -1
- package/dist/view-utils/resolve-extended-views.d.ts +2 -2
- package/dist/view-utils/resolve-relative-paths.d.ts +2 -2
- package/dist/view-utils/resolve-relative-paths.js +2 -3
- package/package.json +6 -6
- package/dist/model-change/changeViewStyle.d.ts +0 -15
- package/dist/model-change/changeViewStyle.js +0 -123
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compareByFqnHierarchically,
|
|
3
|
+
isElementView,
|
|
3
4
|
isStrictElementView,
|
|
4
5
|
parentFqn
|
|
5
6
|
} from "@likec4/core";
|
|
6
|
-
import { computeView, LikeC4ModelGraph } from "@likec4/graph";
|
|
7
|
+
import { computeDynamicView, computeView, LikeC4ModelGraph } from "@likec4/graph";
|
|
7
8
|
import { deepEqual as eq } from "fast-equals";
|
|
8
9
|
import { DocumentState, interruptAndCheck } from "langium";
|
|
9
10
|
import {
|
|
@@ -32,14 +33,14 @@ function buildModel(services, docs) {
|
|
|
32
33
|
relationships: {}
|
|
33
34
|
};
|
|
34
35
|
forEach(map(docs, prop("c4Specification")), (spec) => {
|
|
35
|
-
Object.assign(c4Specification.kinds, spec.kinds)
|
|
36
|
+
Object.assign(c4Specification.kinds, spec.kinds);
|
|
37
|
+
Object.assign(c4Specification.relationships, spec.relationships);
|
|
36
38
|
});
|
|
37
39
|
const resolveLinks = (doc, links) => {
|
|
38
40
|
return links.map((l) => services.lsp.DocumentLinkProvider.resolveLink(doc, l));
|
|
39
41
|
};
|
|
40
42
|
const toModelElement = (doc) => {
|
|
41
43
|
return ({
|
|
42
|
-
astPath,
|
|
43
44
|
tags,
|
|
44
45
|
links,
|
|
45
46
|
style: {
|
|
@@ -49,19 +50,23 @@ function buildModel(services, docs) {
|
|
|
49
50
|
opacity,
|
|
50
51
|
border
|
|
51
52
|
},
|
|
52
|
-
|
|
53
|
+
id,
|
|
54
|
+
kind,
|
|
55
|
+
title,
|
|
56
|
+
description,
|
|
57
|
+
technology
|
|
53
58
|
}) => {
|
|
54
59
|
try {
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
logger.warn(`No kind '${
|
|
60
|
+
const __kind = c4Specification.kinds[kind];
|
|
61
|
+
if (!__kind) {
|
|
62
|
+
logger.warn(`No kind '${kind}' found for ${id}`);
|
|
58
63
|
return null;
|
|
59
64
|
}
|
|
60
|
-
color ??=
|
|
61
|
-
shape ??=
|
|
62
|
-
icon ??=
|
|
63
|
-
opacity ??=
|
|
64
|
-
border ??=
|
|
65
|
+
color ??= __kind.color;
|
|
66
|
+
shape ??= __kind.shape;
|
|
67
|
+
icon ??= __kind.icon;
|
|
68
|
+
opacity ??= __kind.opacity;
|
|
69
|
+
border ??= __kind.border;
|
|
65
70
|
return {
|
|
66
71
|
...color && { color },
|
|
67
72
|
...shape && { shape },
|
|
@@ -70,11 +75,13 @@ function buildModel(services, docs) {
|
|
|
70
75
|
...border && { border },
|
|
71
76
|
...opacity && { opacity }
|
|
72
77
|
},
|
|
73
|
-
description: null,
|
|
74
|
-
technology: null,
|
|
75
|
-
tags: tags ?? null,
|
|
76
78
|
links: links ? resolveLinks(doc, links) : null,
|
|
77
|
-
|
|
79
|
+
tags: tags ?? null,
|
|
80
|
+
technology: technology ?? null,
|
|
81
|
+
description: description ?? null,
|
|
82
|
+
title,
|
|
83
|
+
kind,
|
|
84
|
+
id
|
|
78
85
|
};
|
|
79
86
|
} catch (e) {
|
|
80
87
|
logWarnError(e);
|
|
@@ -83,7 +90,8 @@ function buildModel(services, docs) {
|
|
|
83
90
|
};
|
|
84
91
|
};
|
|
85
92
|
const elements = pipe(
|
|
86
|
-
|
|
93
|
+
docs,
|
|
94
|
+
flatMap((d) => d.c4Elements.map(toModelElement(d))),
|
|
87
95
|
filter(isTruthy),
|
|
88
96
|
sort(compareByFqnHierarchically),
|
|
89
97
|
reduce(
|
|
@@ -110,27 +118,30 @@ function buildModel(services, docs) {
|
|
|
110
118
|
target,
|
|
111
119
|
kind,
|
|
112
120
|
links,
|
|
121
|
+
id,
|
|
113
122
|
...model
|
|
114
123
|
}) => {
|
|
115
|
-
if (source
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
target,
|
|
120
|
-
kind,
|
|
121
|
-
...links && { links: resolveLinks(doc, links) },
|
|
122
|
-
...c4Specification.relationships[kind],
|
|
123
|
-
...model
|
|
124
|
-
};
|
|
125
|
-
}
|
|
124
|
+
if (isNullish(elements[source]) || isNullish(elements[target])) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (!!kind && kind in c4Specification.relationships) {
|
|
126
128
|
return {
|
|
129
|
+
...links && { links: resolveLinks(doc, links) },
|
|
130
|
+
...c4Specification.relationships[kind],
|
|
131
|
+
...model,
|
|
127
132
|
source,
|
|
128
133
|
target,
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
kind,
|
|
135
|
+
id
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
|
-
return
|
|
138
|
+
return {
|
|
139
|
+
...links && { links: resolveLinks(doc, links) },
|
|
140
|
+
...model,
|
|
141
|
+
source,
|
|
142
|
+
target,
|
|
143
|
+
id
|
|
144
|
+
};
|
|
134
145
|
};
|
|
135
146
|
};
|
|
136
147
|
const relations = pipe(
|
|
@@ -138,35 +149,47 @@ function buildModel(services, docs) {
|
|
|
138
149
|
filter(isTruthy),
|
|
139
150
|
mapToObj((r) => [r.id, r])
|
|
140
151
|
);
|
|
141
|
-
const
|
|
152
|
+
const toC4View = (doc) => {
|
|
142
153
|
const docUri = doc.uri.toString();
|
|
143
|
-
return (
|
|
144
|
-
let {
|
|
145
|
-
|
|
146
|
-
title
|
|
154
|
+
return (parsedAstView) => {
|
|
155
|
+
let {
|
|
156
|
+
id,
|
|
157
|
+
title,
|
|
158
|
+
description,
|
|
159
|
+
tags,
|
|
160
|
+
links,
|
|
161
|
+
// ignore this property
|
|
162
|
+
astPath: _ignore,
|
|
163
|
+
// model should include discriminant __
|
|
164
|
+
...model
|
|
165
|
+
} = parsedAstView;
|
|
166
|
+
if (parsedAstView.__ === "element" && isNullish(title) && "viewOf" in parsedAstView) {
|
|
167
|
+
title ??= elements[parsedAstView.viewOf]?.title ?? null;
|
|
147
168
|
}
|
|
148
|
-
if (
|
|
169
|
+
if (isNullish(title) && id === "index") {
|
|
149
170
|
title = "Landscape view";
|
|
150
171
|
}
|
|
151
172
|
return {
|
|
152
|
-
|
|
153
|
-
title
|
|
154
|
-
description
|
|
155
|
-
tags
|
|
173
|
+
id,
|
|
174
|
+
title,
|
|
175
|
+
description,
|
|
176
|
+
tags,
|
|
156
177
|
links: links ? resolveLinks(doc, links) : null,
|
|
157
178
|
docUri,
|
|
158
|
-
|
|
179
|
+
...model
|
|
159
180
|
};
|
|
160
181
|
};
|
|
161
182
|
};
|
|
162
183
|
const views = pipe(
|
|
163
|
-
|
|
184
|
+
docs,
|
|
185
|
+
flatMap((d) => map(d.c4Views, toC4View(d))),
|
|
164
186
|
resolveRelativePaths,
|
|
165
187
|
mapToObj((v) => [v.id, v]),
|
|
166
188
|
resolveRulesExtendedViews
|
|
167
189
|
);
|
|
168
190
|
if (!("index" in views)) {
|
|
169
191
|
views["index"] = {
|
|
192
|
+
__: "element",
|
|
170
193
|
id: "index",
|
|
171
194
|
title: "Landscape",
|
|
172
195
|
description: null,
|
|
@@ -254,7 +277,7 @@ ${printDocs(docs)}`);
|
|
|
254
277
|
const index = new LikeC4ModelGraph(model);
|
|
255
278
|
const allViews = [];
|
|
256
279
|
for (const view of values(model.views)) {
|
|
257
|
-
const result = computeView(view, index);
|
|
280
|
+
const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index);
|
|
258
281
|
if (!result.isSuccess) {
|
|
259
282
|
logWarnError(result.error);
|
|
260
283
|
continue;
|
|
@@ -291,7 +314,7 @@ ${printDocs(docs)}`);
|
|
|
291
314
|
const cache = this.services.WorkspaceCache;
|
|
292
315
|
return cache.get(computedViewKey(viewId), () => {
|
|
293
316
|
const index = new LikeC4ModelGraph(model);
|
|
294
|
-
const result = computeView(view, index);
|
|
317
|
+
const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index);
|
|
295
318
|
if (!result.isSuccess) {
|
|
296
319
|
logError(result.error);
|
|
297
320
|
return null;
|
|
@@ -14,8 +14,8 @@ export declare class LikeC4ModelLocator {
|
|
|
14
14
|
locateRelation(relationId: c4.RelationID): Location | null;
|
|
15
15
|
locateViewAst(viewId: c4.ViewID): {
|
|
16
16
|
doc: import("../ast").ParsedLikeC4LangiumDocument;
|
|
17
|
-
view: import("../ast").
|
|
18
|
-
viewAst: ast.
|
|
17
|
+
view: import("../ast").ParsedAstView;
|
|
18
|
+
viewAst: ast.LikeC4View;
|
|
19
19
|
} | null;
|
|
20
20
|
locateView(viewId: c4.ViewID): Location | null;
|
|
21
21
|
}
|
|
@@ -55,21 +55,19 @@ export class LikeC4ModelLocator {
|
|
|
55
55
|
if (targetNode2) {
|
|
56
56
|
return {
|
|
57
57
|
uri: doc.uri.toString(),
|
|
58
|
-
range:
|
|
59
|
-
start: targetNode2.range.start,
|
|
60
|
-
end: targetNode2.range.end
|
|
61
|
-
}
|
|
58
|
+
range: targetNode2.range
|
|
62
59
|
};
|
|
63
60
|
}
|
|
64
61
|
}
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
let targetNode = node.kind ? findNodeForProperty(node.$cstNode, "kind") : findNodeForKeyword(node.$cstNode, "->");
|
|
63
|
+
targetNode ??= findNodeForProperty(node.$cstNode, "target");
|
|
64
|
+
if (!targetNode) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
67
68
|
uri: doc.uri.toString(),
|
|
68
|
-
range:
|
|
69
|
-
|
|
70
|
-
end: targetNode.range.end
|
|
71
|
-
}
|
|
72
|
-
} : null;
|
|
69
|
+
range: targetNode.range
|
|
70
|
+
};
|
|
73
71
|
}
|
|
74
72
|
return null;
|
|
75
73
|
}
|
|
@@ -83,7 +81,7 @@ export class LikeC4ModelLocator {
|
|
|
83
81
|
doc.parseResult.value,
|
|
84
82
|
view.astPath
|
|
85
83
|
);
|
|
86
|
-
if (ast.
|
|
84
|
+
if (ast.isLikeC4View(viewAst)) {
|
|
87
85
|
return {
|
|
88
86
|
doc,
|
|
89
87
|
view,
|
|
@@ -105,15 +103,13 @@ export class LikeC4ModelLocator {
|
|
|
105
103
|
} else if ("viewOf" in node) {
|
|
106
104
|
targetNode = findNodeForProperty(node.$cstNode, "viewOf") ?? targetNode;
|
|
107
105
|
}
|
|
106
|
+
targetNode ??= findNodeForKeyword(node.$cstNode, "view");
|
|
108
107
|
if (!targetNode) {
|
|
109
108
|
return null;
|
|
110
109
|
}
|
|
111
110
|
return {
|
|
112
111
|
uri: res.doc.uri.toString(),
|
|
113
|
-
range:
|
|
114
|
-
start: targetNode.range.start,
|
|
115
|
-
end: targetNode.range.start
|
|
116
|
-
}
|
|
112
|
+
range: targetNode.range
|
|
117
113
|
};
|
|
118
114
|
}
|
|
119
115
|
}
|
|
@@ -19,7 +19,9 @@ export declare class LikeC4ModelParser {
|
|
|
19
19
|
private parseCustomElementExpr;
|
|
20
20
|
private parsePredicateExpr;
|
|
21
21
|
private parseViewRule;
|
|
22
|
+
private parseDynamicStep;
|
|
22
23
|
private parseElementView;
|
|
24
|
+
private parseDynamicElementView;
|
|
23
25
|
protected resolveFqn(node: ast.Element | ast.ExtendElement): c4.Fqn;
|
|
24
26
|
private getAstNodePath;
|
|
25
27
|
private convertTags;
|
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
ast,
|
|
7
7
|
checksFromDiagnostics,
|
|
8
8
|
cleanParsedModel,
|
|
9
|
-
ElementViewOps,
|
|
10
9
|
isFqnIndexedDocument,
|
|
11
10
|
parseAstOpacityProperty,
|
|
12
11
|
resolveRelationPoints,
|
|
13
12
|
streamModel,
|
|
14
13
|
toAutoLayout,
|
|
15
14
|
toElementStyle,
|
|
16
|
-
toRelationshipStyleExcludeDefaults
|
|
15
|
+
toRelationshipStyleExcludeDefaults,
|
|
16
|
+
ViewOps
|
|
17
17
|
} from "../ast.js";
|
|
18
18
|
import { elementRef, getFqnElementRef } from "../elementRef.js";
|
|
19
19
|
import { logError, logger, logWarnError } from "../logger.js";
|
|
@@ -50,13 +50,13 @@ export class LikeC4ModelParser {
|
|
|
50
50
|
}
|
|
51
51
|
parseLikeC4Document(_doc) {
|
|
52
52
|
const doc = cleanParsedModel(_doc);
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
this.
|
|
53
|
+
const { isValid } = checksFromDiagnostics(doc);
|
|
54
|
+
this.parseSpecification(doc, isValid);
|
|
55
|
+
this.parseModel(doc, isValid);
|
|
56
|
+
this.parseViews(doc, isValid);
|
|
56
57
|
return doc;
|
|
57
58
|
}
|
|
58
|
-
parseSpecification(doc) {
|
|
59
|
-
const { isValid } = checksFromDiagnostics(doc);
|
|
59
|
+
parseSpecification(doc, isValid) {
|
|
60
60
|
const { parseResult, c4Specification } = doc;
|
|
61
61
|
const specifications = parseResult.value.specifications.filter(isValid);
|
|
62
62
|
const element_specs = specifications.flatMap((s) => s.elements.filter(isValid));
|
|
@@ -84,8 +84,8 @@ export class LikeC4ModelParser {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
parseModel(doc) {
|
|
88
|
-
for (const el of streamModel(doc)) {
|
|
87
|
+
parseModel(doc, isValid) {
|
|
88
|
+
for (const el of streamModel(doc, isValid)) {
|
|
89
89
|
if (ast.isElement(el)) {
|
|
90
90
|
try {
|
|
91
91
|
doc.c4Elements.push(this.parseElement(el));
|
|
@@ -159,13 +159,16 @@ export class LikeC4ModelParser {
|
|
|
159
159
|
...toRelationshipStyleExcludeDefaults(styleProp?.props)
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
|
-
parseViews(doc) {
|
|
163
|
-
const
|
|
164
|
-
const views = doc.parseResult.value.views.flatMap((v) => isValid(v) ? v.views.filter(isValid) : []);
|
|
162
|
+
parseViews(doc, isValid) {
|
|
163
|
+
const views = doc.parseResult.value.views.flatMap((v) => isValid(v) ? v.views : []);
|
|
165
164
|
for (const view of views) {
|
|
166
165
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
if (!isValid(view)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
doc.c4Views.push(
|
|
170
|
+
ast.isElementView(view) ? this.parseElementView(view, isValid) : this.parseDynamicElementView(view, isValid)
|
|
171
|
+
);
|
|
169
172
|
} catch (e) {
|
|
170
173
|
logWarnError(e);
|
|
171
174
|
}
|
|
@@ -307,11 +310,11 @@ export class LikeC4ModelParser {
|
|
|
307
310
|
}
|
|
308
311
|
nonexhaustive(astNode);
|
|
309
312
|
}
|
|
310
|
-
parseViewRule(astRule) {
|
|
313
|
+
parseViewRule(astRule, isValid) {
|
|
311
314
|
if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
|
|
312
315
|
const exprs = astRule.expressions.flatMap((n) => {
|
|
313
316
|
try {
|
|
314
|
-
return this.parsePredicateExpr(n);
|
|
317
|
+
return isValid(n) ? this.parsePredicateExpr(n) : [];
|
|
315
318
|
} catch (e) {
|
|
316
319
|
logWarnError(e);
|
|
317
320
|
return [];
|
|
@@ -335,7 +338,30 @@ export class LikeC4ModelParser {
|
|
|
335
338
|
}
|
|
336
339
|
nonexhaustive(astRule);
|
|
337
340
|
}
|
|
338
|
-
|
|
341
|
+
parseDynamicStep(node) {
|
|
342
|
+
const sourceEl = elementRef(node.source);
|
|
343
|
+
if (!sourceEl) {
|
|
344
|
+
throw new Error("Invalid reference to source");
|
|
345
|
+
}
|
|
346
|
+
const targetEl = elementRef(node.target);
|
|
347
|
+
if (!targetEl) {
|
|
348
|
+
throw new Error("Invalid reference to target");
|
|
349
|
+
}
|
|
350
|
+
let source = this.resolveFqn(sourceEl);
|
|
351
|
+
let target = this.resolveFqn(targetEl);
|
|
352
|
+
if (node.isBackward) {
|
|
353
|
+
;
|
|
354
|
+
[source, target] = [target, source];
|
|
355
|
+
}
|
|
356
|
+
const title = toSingleLine(node.title) ?? null;
|
|
357
|
+
return {
|
|
358
|
+
source,
|
|
359
|
+
target,
|
|
360
|
+
title,
|
|
361
|
+
isBackward: node.isBackward
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
parseElementView(astNode, isValid) {
|
|
339
365
|
const body = astNode.body;
|
|
340
366
|
invariant(body, "ElementView body is not defined");
|
|
341
367
|
const astPath = this.getAstNodePath(astNode);
|
|
@@ -357,37 +383,121 @@ export class LikeC4ModelParser {
|
|
|
357
383
|
viewOf ?? ""
|
|
358
384
|
);
|
|
359
385
|
}
|
|
360
|
-
const title = body.props.find((p) => p.key === "title")?.value;
|
|
361
|
-
const description = body.props.find((p) => p.key === "description")?.value;
|
|
386
|
+
const title = toSingleLine(body.props.find((p) => p.key === "title")?.value) ?? null;
|
|
387
|
+
const description = removeIndent(body.props.find((p) => p.key === "description")?.value) ?? null;
|
|
362
388
|
const tags = this.convertTags(body);
|
|
363
389
|
const links = body.props.filter(ast.isLinkProperty).map((p) => p.value);
|
|
364
|
-
const
|
|
390
|
+
const view = {
|
|
391
|
+
__: "element",
|
|
365
392
|
id,
|
|
366
393
|
astPath,
|
|
367
394
|
...viewOf && { viewOf },
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
395
|
+
title,
|
|
396
|
+
description,
|
|
397
|
+
tags,
|
|
398
|
+
links: isNonEmptyArray(links) ? links : null,
|
|
372
399
|
rules: body.rules.flatMap((n) => {
|
|
373
400
|
try {
|
|
374
|
-
return this.parseViewRule(n);
|
|
401
|
+
return isValid(n) ? this.parseViewRule(n, isValid) : [];
|
|
375
402
|
} catch (e) {
|
|
376
403
|
logWarnError(e);
|
|
377
404
|
return [];
|
|
378
405
|
}
|
|
379
406
|
})
|
|
380
407
|
};
|
|
381
|
-
|
|
408
|
+
ViewOps.writeId(astNode, view.id);
|
|
382
409
|
if ("extends" in astNode) {
|
|
383
410
|
const extendsView = astNode.extends.view.ref;
|
|
384
411
|
invariant(extendsView?.name, "view extends is not resolved: " + astNode.$cstNode?.text);
|
|
385
|
-
return {
|
|
386
|
-
...basic,
|
|
412
|
+
return Object.assign(view, {
|
|
387
413
|
extends: extendsView.name
|
|
388
|
-
};
|
|
414
|
+
});
|
|
389
415
|
}
|
|
390
|
-
return
|
|
416
|
+
return view;
|
|
417
|
+
}
|
|
418
|
+
parseDynamicElementView(astNode, isValid) {
|
|
419
|
+
const body = astNode.body;
|
|
420
|
+
invariant(body, "ElementView body is not defined");
|
|
421
|
+
const props = body.props.filter(isValid);
|
|
422
|
+
const astPath = this.getAstNodePath(astNode);
|
|
423
|
+
let id = astNode.name;
|
|
424
|
+
if (!id) {
|
|
425
|
+
id = "dynamic_" + stringHash(
|
|
426
|
+
getDocument(astNode).uri.toString(),
|
|
427
|
+
astPath
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
const title = toSingleLine(props.find((p) => p.key === "title")?.value) ?? null;
|
|
431
|
+
const description = removeIndent(props.find((p) => p.key === "description")?.value) ?? null;
|
|
432
|
+
const tags = this.convertTags(body);
|
|
433
|
+
const links = props.filter(ast.isLinkProperty).map((p) => p.value);
|
|
434
|
+
ViewOps.writeId(astNode, id);
|
|
435
|
+
return {
|
|
436
|
+
__: "dynamic",
|
|
437
|
+
id,
|
|
438
|
+
astPath,
|
|
439
|
+
title,
|
|
440
|
+
description,
|
|
441
|
+
tags,
|
|
442
|
+
links: isNonEmptyArray(links) ? links : null,
|
|
443
|
+
rules: body.rules.reduce((acc, n) => {
|
|
444
|
+
if (!isValid(n)) {
|
|
445
|
+
return acc;
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
if (ast.isDynamicViewRulePredicate(n)) {
|
|
449
|
+
const include = [];
|
|
450
|
+
for (const expr of n.expressions) {
|
|
451
|
+
if (ast.isElementExpr(expr)) {
|
|
452
|
+
include.push(this.parseElementExpr(expr));
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (ast.isCustomElementExpr(expr)) {
|
|
456
|
+
include.push(this.parseCustomElementExpr(expr));
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (include.length > 0) {
|
|
461
|
+
acc.push({ include });
|
|
462
|
+
}
|
|
463
|
+
return acc;
|
|
464
|
+
}
|
|
465
|
+
if (ast.isViewRuleStyle(n)) {
|
|
466
|
+
const styleProps = toElementStyle(n.styleprops);
|
|
467
|
+
const targets = n.targets.map((n2) => this.parseElementExpr(n2));
|
|
468
|
+
if (targets.length > 0) {
|
|
469
|
+
acc.push({
|
|
470
|
+
targets,
|
|
471
|
+
style: {
|
|
472
|
+
...styleProps
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return acc;
|
|
477
|
+
}
|
|
478
|
+
if (ast.isViewRuleAutoLayout(n)) {
|
|
479
|
+
acc.push({
|
|
480
|
+
autoLayout: toAutoLayout(n.direction)
|
|
481
|
+
});
|
|
482
|
+
return acc;
|
|
483
|
+
}
|
|
484
|
+
nonexhaustive(n);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
logWarnError(e);
|
|
487
|
+
return acc;
|
|
488
|
+
}
|
|
489
|
+
}, []),
|
|
490
|
+
steps: body.steps.reduce((acc, n) => {
|
|
491
|
+
try {
|
|
492
|
+
if (isValid(n)) {
|
|
493
|
+
acc.push(this.parseDynamicStep(n));
|
|
494
|
+
}
|
|
495
|
+
} catch (e) {
|
|
496
|
+
logWarnError(e);
|
|
497
|
+
}
|
|
498
|
+
return acc;
|
|
499
|
+
}, [])
|
|
500
|
+
};
|
|
391
501
|
}
|
|
392
502
|
resolveFqn(node) {
|
|
393
503
|
if (ast.isExtendElement(node)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Location, TextEdit } from 'vscode-languageserver-protocol';
|
|
1
|
+
import { Location, Range, TextEdit } from 'vscode-languageserver-protocol';
|
|
2
2
|
import { type ParsedLikeC4LangiumDocument } from '../ast';
|
|
3
3
|
import type { LikeC4Services } from '../module';
|
|
4
4
|
import type { ChangeViewRequestParams } from '../protocol';
|
|
@@ -9,6 +9,7 @@ export declare class LikeC4ModelChanges {
|
|
|
9
9
|
applyChange(changeView: ChangeViewRequestParams): Promise<Location | null>;
|
|
10
10
|
protected convertToTextEdit({ viewId, changes }: ChangeViewRequestParams): {
|
|
11
11
|
doc: ParsedLikeC4LangiumDocument;
|
|
12
|
+
ranges: Range[];
|
|
12
13
|
edits: TextEdit[];
|
|
13
14
|
};
|
|
14
15
|
}
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import { invariant, nonexhaustive } from "@likec4/core";
|
|
2
|
-
import { Range
|
|
2
|
+
import { Range } from "vscode-languageserver-protocol";
|
|
3
|
+
import { changeElementStyle } from "./changeElementStyle.js";
|
|
3
4
|
import { changeViewLayout } from "./changeViewLayout.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
let
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
function unionRangeOfAllEdits(ranges) {
|
|
6
|
+
let startLine = Number.MAX_SAFE_INTEGER;
|
|
7
|
+
let endLine = Number.MIN_SAFE_INTEGER;
|
|
8
|
+
let startCharacter = Number.MAX_SAFE_INTEGER;
|
|
9
|
+
let endCharacter = Number.MIN_SAFE_INTEGER;
|
|
10
|
+
for (const { start, end } of ranges) {
|
|
11
|
+
if (start.line <= startLine) {
|
|
12
|
+
if (startLine == start.line) {
|
|
13
|
+
startCharacter = Math.min(start.character, startCharacter);
|
|
14
|
+
} else {
|
|
15
|
+
startLine = start.line;
|
|
16
|
+
startCharacter = start.character;
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
if (endLine <= end.line) {
|
|
20
|
+
if (endLine == end.line) {
|
|
21
|
+
endCharacter = Math.max(end.character, endCharacter);
|
|
22
|
+
} else {
|
|
23
|
+
endLine = end.line;
|
|
24
|
+
endCharacter = end.character;
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
|
-
return Range.create(
|
|
28
|
+
return Range.create(startLine, startCharacter, endLine, endCharacter);
|
|
21
29
|
}
|
|
22
30
|
export class LikeC4ModelChanges {
|
|
23
31
|
constructor(services) {
|
|
@@ -41,9 +49,9 @@ export class LikeC4ModelChanges {
|
|
|
41
49
|
const applyResult = await lspConnection.workspace.applyEdit({
|
|
42
50
|
label: `LikeC4 - change view ${changeView.viewId}`,
|
|
43
51
|
edit: {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
changes: {
|
|
53
|
+
[textDocument.uri]: edits
|
|
54
|
+
}
|
|
47
55
|
}
|
|
48
56
|
});
|
|
49
57
|
if (!applyResult.applied) {
|
|
@@ -52,7 +60,7 @@ export class LikeC4ModelChanges {
|
|
|
52
60
|
}
|
|
53
61
|
result = {
|
|
54
62
|
uri: textDocument.uri,
|
|
55
|
-
range: unionRangeOfAllEdits(edits)
|
|
63
|
+
range: unionRangeOfAllEdits(edits.map((edit) => edit.range))
|
|
56
64
|
};
|
|
57
65
|
});
|
|
58
66
|
return result;
|
|
@@ -62,32 +70,27 @@ export class LikeC4ModelChanges {
|
|
|
62
70
|
if (!lookup) {
|
|
63
71
|
throw new Error(`View not found: ${viewId}`);
|
|
64
72
|
}
|
|
73
|
+
const ranges = [];
|
|
65
74
|
const edits = [];
|
|
66
75
|
for (const change of changes) {
|
|
67
76
|
switch (change.op) {
|
|
68
|
-
case "change-
|
|
69
|
-
edits
|
|
70
|
-
...lookup,
|
|
71
|
-
targets: change.targets,
|
|
72
|
-
key: "color",
|
|
73
|
-
value: change.color
|
|
74
|
-
}));
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case "change-shape": {
|
|
78
|
-
edits.push(...changeViewStyle(this.services, {
|
|
77
|
+
case "change-element-style": {
|
|
78
|
+
const { edits: elementEdits, modifiedRange } = changeElementStyle(this.services, {
|
|
79
79
|
...lookup,
|
|
80
80
|
targets: change.targets,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
style: change.style
|
|
82
|
+
});
|
|
83
|
+
ranges.push(modifiedRange);
|
|
84
|
+
edits.push(...elementEdits);
|
|
84
85
|
break;
|
|
85
86
|
}
|
|
86
87
|
case "change-autolayout": {
|
|
87
|
-
|
|
88
|
+
const edit = changeViewLayout(this.services, {
|
|
88
89
|
...lookup,
|
|
89
90
|
layout: change.layout
|
|
90
|
-
})
|
|
91
|
+
});
|
|
92
|
+
edits.push(edit);
|
|
93
|
+
ranges.push(edit.range);
|
|
91
94
|
break;
|
|
92
95
|
}
|
|
93
96
|
default:
|
|
@@ -96,6 +99,7 @@ export class LikeC4ModelChanges {
|
|
|
96
99
|
}
|
|
97
100
|
return {
|
|
98
101
|
doc: lookup.doc,
|
|
102
|
+
ranges,
|
|
99
103
|
edits
|
|
100
104
|
};
|
|
101
105
|
}
|