@likec4/language-server 1.4.0 → 1.6.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/README.md +1 -1
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +26 -14
- package/src/Rpc.ts +25 -2
- package/src/ast.ts +19 -15
- package/src/generated/ast.ts +390 -203
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +952 -0
- package/src/like-c4.langium +120 -64
- package/src/likec4lib.ts +7 -0
- package/src/lsp/DocumentSymbolProvider.ts +28 -1
- package/src/lsp/SemanticTokenProvider.ts +41 -22
- package/src/model/fqn-computation.ts +29 -7
- package/src/model/fqn-index.ts +18 -31
- package/src/model/model-builder.ts +13 -9
- package/src/model/model-locator.ts +7 -7
- package/src/model/model-parser.ts +166 -69
- package/src/model-change/changeElementStyle.ts +6 -6
- package/src/model-graph/compute-view/__test__/fixture.ts +52 -24
- package/src/model-graph/compute-view/compute.ts +51 -20
- package/src/model-graph/compute-view/predicates.ts +6 -1
- package/src/model-graph/dynamic-view/__test__/fixture.ts +2 -2
- package/src/model-graph/dynamic-view/compute.ts +2 -2
- package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts} +5 -3
- package/src/model-graph/utils/applyCustomRelationProperties.ts +50 -0
- package/src/model-graph/utils/applyViewRuleStyles.ts +11 -34
- package/src/model-graph/utils/elementExpressionToPredicate.ts +32 -0
- package/src/references/scope-computation.ts +113 -60
- package/src/references/scope-provider.ts +3 -23
- package/src/shared/NodeKindProvider.ts +1 -0
- package/src/shared/WorkspaceManager.ts +15 -6
- package/src/validation/dynamic-view-rule.ts +19 -26
- package/src/validation/element.ts +8 -4
- package/src/validation/index.ts +9 -6
- package/src/validation/property-checks.ts +23 -1
- package/src/validation/view-predicates/custom-element-expr.ts +21 -8
- package/src/validation/view-predicates/custom-relation-expr.ts +16 -0
- package/src/validation/view-predicates/expanded-element.ts +13 -24
- package/src/validation/view-predicates/incoming.ts +5 -5
- package/src/validation/view-predicates/index.ts +1 -0
- package/src/validation/view-predicates/outgoing.ts +5 -5
- package/src/view-utils/assignNavigateTo.ts +2 -2
- package/src/view-utils/manual-layout.ts +4 -2
- package/src/view-utils/resolve-extended-views.ts +2 -2
- package/src/view-utils/resolve-relative-paths.ts +3 -3
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
type c4,
|
|
3
3
|
compareByFqnHierarchically,
|
|
4
4
|
isElementView,
|
|
5
|
-
|
|
5
|
+
isScopedElementView,
|
|
6
6
|
parentFqn,
|
|
7
|
-
type
|
|
7
|
+
type ScopedElementView,
|
|
8
8
|
type ViewID
|
|
9
9
|
} from '@likec4/core'
|
|
10
10
|
import { deepEqual as eq } from 'fast-equals'
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sort,
|
|
27
27
|
values
|
|
28
28
|
} from 'remeda'
|
|
29
|
-
import { type CancellationToken, Disposable } from 'vscode-languageserver'
|
|
29
|
+
import { type CancellationToken, Disposable } from 'vscode-languageserver-protocol'
|
|
30
30
|
import type {
|
|
31
31
|
ParsedAstElement,
|
|
32
32
|
ParsedAstRelation,
|
|
@@ -107,7 +107,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
107
107
|
|
|
108
108
|
const elements = pipe(
|
|
109
109
|
docs,
|
|
110
|
-
flatMap(d => d.c4Elements
|
|
110
|
+
flatMap(d => map(d.c4Elements, toModelElement(d))),
|
|
111
111
|
filter(isTruthy),
|
|
112
112
|
sort(compareByFqnHierarchically),
|
|
113
113
|
reduce(
|
|
@@ -140,10 +140,13 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
140
140
|
...model
|
|
141
141
|
}: ParsedAstRelation): c4.Relation | null => {
|
|
142
142
|
if (isNullish(elements[source]) || isNullish(elements[target])) {
|
|
143
|
+
logger.warn(
|
|
144
|
+
`Invalid relation ${id}, source: ${source}(${!!elements[source]}), target: ${target}(${!!elements[target]})`
|
|
145
|
+
)
|
|
143
146
|
return null
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
if (
|
|
149
|
+
if (!isNullish(kind) && kind in c4Specification.relationships) {
|
|
147
150
|
return {
|
|
148
151
|
...(links && { links: resolveLinks(doc, links) }),
|
|
149
152
|
...c4Specification.relationships[kind],
|
|
@@ -165,14 +168,15 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
const relations = pipe(
|
|
168
|
-
|
|
171
|
+
docs,
|
|
172
|
+
flatMap(d => map(d.c4Relations, toModelRelation(d))),
|
|
169
173
|
filter(isTruthy),
|
|
170
174
|
mapToObj(r => [r.id, r])
|
|
171
175
|
)
|
|
172
176
|
|
|
173
177
|
const toC4View = (doc: LangiumDocument) => {
|
|
174
178
|
const docUri = doc.uri.toString()
|
|
175
|
-
return (parsedAstView: ParsedAstView): c4.
|
|
179
|
+
return (parsedAstView: ParsedAstView): c4.LikeC4View => {
|
|
176
180
|
let {
|
|
177
181
|
id,
|
|
178
182
|
title,
|
|
@@ -188,7 +192,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
188
192
|
} = parsedAstView
|
|
189
193
|
|
|
190
194
|
if (parsedAstView.__ === 'element' && isNullish(title) && 'viewOf' in parsedAstView) {
|
|
191
|
-
title
|
|
195
|
+
title = elements[parsedAstView.viewOf]?.title ?? null
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
if (isNullish(title) && id === 'index') {
|
|
@@ -359,7 +363,7 @@ export class LikeC4ModelBuilder {
|
|
|
359
363
|
}
|
|
360
364
|
|
|
361
365
|
const allElementViews = values(model.views).filter(
|
|
362
|
-
(v): v is
|
|
366
|
+
(v): v is ScopedElementView => isScopedElementView(v) && v.id !== viewId
|
|
363
367
|
)
|
|
364
368
|
|
|
365
369
|
let computedView = result.view
|
|
@@ -33,18 +33,18 @@ export class LikeC4ModelLocator {
|
|
|
33
33
|
return doc.c4Elements.find(e => e.id === fqn) ?? null
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
public locateElement(fqn: c4.Fqn,
|
|
36
|
+
public locateElement(fqn: c4.Fqn, _property = 'name'): Location | null {
|
|
37
37
|
const entry = this.fqnIndex.byFqn(fqn).head()
|
|
38
38
|
if (!entry) {
|
|
39
39
|
return null
|
|
40
40
|
}
|
|
41
|
-
const propertyNode = findNodeForProperty(entry.el.$cstNode, property) ?? entry.el.$cstNode
|
|
42
|
-
if (!propertyNode) {
|
|
43
|
-
|
|
44
|
-
}
|
|
41
|
+
// const propertyNode = findNodeForProperty(entry.el.$cstNode, property) ?? entry.el.$cstNode
|
|
42
|
+
// if (!propertyNode) {
|
|
43
|
+
// return null
|
|
44
|
+
// }
|
|
45
45
|
return {
|
|
46
|
-
uri: entry.
|
|
47
|
-
range:
|
|
46
|
+
uri: entry.documentUri.toString(),
|
|
47
|
+
range: entry.nameSegment?.range!
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type c4, InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
2
|
import type { AstNode, LangiumDocument } from 'langium'
|
|
3
3
|
import { AstUtils, CstUtils } from 'langium'
|
|
4
|
-
import { isTruthy } from 'remeda'
|
|
4
|
+
import { isTruthy, mapToObj } from 'remeda'
|
|
5
5
|
import stripIndent from 'strip-indent'
|
|
6
6
|
import type {
|
|
7
7
|
ChecksFromDiagnostics,
|
|
@@ -37,14 +37,14 @@ const { getDocument } = AstUtils
|
|
|
37
37
|
export type ModelParsedListener = () => void
|
|
38
38
|
|
|
39
39
|
function toSingleLine<T extends string | undefined>(str: T): T {
|
|
40
|
-
return (str ? removeIndent(str).split('\n').join(' ') : undefined) as T
|
|
40
|
+
return (isTruthy(str) ? removeIndent(str).split('\n').join(' ') : undefined) as T
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function removeIndent<T extends string | undefined>(str: T): T {
|
|
44
|
-
return (str ? stripIndent(str).trim() : undefined) as T
|
|
44
|
+
return (isTruthy(str) ? stripIndent(str).trim() : undefined) as T
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
type IsValidFn = ChecksFromDiagnostics['isValid']
|
|
47
|
+
export type IsValidFn = ChecksFromDiagnostics['isValid']
|
|
48
48
|
|
|
49
49
|
export class LikeC4ModelParser {
|
|
50
50
|
private fqnIndex: FqnIndex
|
|
@@ -142,14 +142,23 @@ export class LikeC4ModelParser {
|
|
|
142
142
|
|
|
143
143
|
let [title, description, technology] = astNode.props ?? []
|
|
144
144
|
|
|
145
|
-
const bodyProps = astNode.body?.props.filter(ast.isElementStringProperty) ?? []
|
|
145
|
+
const bodyProps = mapToObj(astNode.body?.props.filter(ast.isElementStringProperty) ?? [], p => [p.key, p.value])
|
|
146
146
|
|
|
147
|
-
title = toSingleLine(title ?? bodyProps.
|
|
148
|
-
description = removeIndent(description ??
|
|
149
|
-
technology = toSingleLine(technology ??
|
|
147
|
+
title = toSingleLine(title ?? bodyProps.title)
|
|
148
|
+
description = removeIndent(bodyProps.description ?? description)
|
|
149
|
+
technology = toSingleLine(bodyProps.technology ?? technology)
|
|
150
150
|
|
|
151
151
|
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
|
|
152
152
|
|
|
153
|
+
// Property has higher priority than from style
|
|
154
|
+
const iconProp = astNode.body?.props.find(ast.isIconProperty)
|
|
155
|
+
if (iconProp) {
|
|
156
|
+
const value = iconProp.libicon?.ref?.name ?? iconProp.value
|
|
157
|
+
if (isTruthy(value)) {
|
|
158
|
+
style.icon = value as c4.IconUrl
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
153
162
|
return {
|
|
154
163
|
id,
|
|
155
164
|
kind,
|
|
@@ -171,7 +180,7 @@ export class LikeC4ModelParser {
|
|
|
171
180
|
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
|
|
172
181
|
const kind = astNode.kind?.ref?.name as c4.RelationshipKind
|
|
173
182
|
const astPath = this.getAstNodePath(astNode)
|
|
174
|
-
const title =
|
|
183
|
+
const title = removeIndent(
|
|
175
184
|
astNode.title ?? astNode.body?.props.find((p): p is ast.RelationStringProperty => p.key === 'title')?.value
|
|
176
185
|
) ?? ''
|
|
177
186
|
const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
|
|
@@ -209,39 +218,70 @@ export class LikeC4ModelParser {
|
|
|
209
218
|
}
|
|
210
219
|
}
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
// TODO validate view rules
|
|
222
|
+
private parseViewRulePredicate(astNode: ast.ViewRulePredicate, _isValid: IsValidFn): c4.ViewRulePredicate {
|
|
223
|
+
const exprs = [] as c4.Expression[]
|
|
224
|
+
let exprNode: ast.Expressions | undefined = astNode.exprs
|
|
225
|
+
while (exprNode) {
|
|
226
|
+
try {
|
|
227
|
+
if (isTruthy(exprNode.value)) {
|
|
228
|
+
exprs.unshift(this.parseExpression(exprNode.value))
|
|
229
|
+
}
|
|
230
|
+
} catch (e) {
|
|
231
|
+
logWarnError(e)
|
|
232
|
+
}
|
|
233
|
+
exprNode = exprNode.prev
|
|
234
|
+
}
|
|
235
|
+
return ast.isIncludePredicate(astNode) ? { include: exprs } : { exclude: exprs }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private parseElementExpressionsIterator(astNode: ast.ElementExpressionsIterator): c4.ElementExpression[] {
|
|
239
|
+
const exprs = [] as c4.ElementExpression[]
|
|
240
|
+
let iter: ast.ElementExpressionsIterator | undefined = astNode
|
|
241
|
+
while (iter) {
|
|
242
|
+
try {
|
|
243
|
+
exprs.unshift(this.parseElementExpr(iter.value))
|
|
244
|
+
} catch (e) {
|
|
245
|
+
logWarnError(e)
|
|
246
|
+
}
|
|
247
|
+
iter = iter.prev
|
|
248
|
+
}
|
|
249
|
+
return exprs
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private parseElementExpr(astNode: ast.ElementExpression): c4.ElementExpression {
|
|
253
|
+
if (ast.isWildcardExpression(astNode)) {
|
|
214
254
|
return {
|
|
215
255
|
wildcard: true
|
|
216
256
|
}
|
|
217
257
|
}
|
|
218
|
-
if (ast.
|
|
219
|
-
|
|
258
|
+
if (ast.isElementKindExpression(astNode)) {
|
|
259
|
+
invariant(astNode.kind, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
|
|
220
260
|
return {
|
|
221
261
|
elementKind: astNode.kind.$refText as c4.ElementKind,
|
|
222
262
|
isEqual: astNode.isEqual
|
|
223
263
|
}
|
|
224
264
|
}
|
|
225
|
-
if (ast.
|
|
265
|
+
if (ast.isElementTagExpression(astNode)) {
|
|
266
|
+
invariant(astNode.tag, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
|
|
226
267
|
let elementTag = astNode.tag.$refText
|
|
227
268
|
if (elementTag.startsWith('#')) {
|
|
228
269
|
elementTag = elementTag.slice(1)
|
|
229
270
|
}
|
|
230
|
-
// invariant(astNode.tag.ref, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
|
|
231
271
|
return {
|
|
232
272
|
elementTag: elementTag as c4.Tag,
|
|
233
273
|
isEqual: astNode.isEqual
|
|
234
274
|
}
|
|
235
275
|
}
|
|
236
|
-
if (ast.
|
|
237
|
-
const elementNode = elementRef(astNode.
|
|
238
|
-
invariant(elementNode, 'Element not found ' + astNode.
|
|
276
|
+
if (ast.isExpandElementExpression(astNode)) {
|
|
277
|
+
const elementNode = elementRef(astNode.expand)
|
|
278
|
+
invariant(elementNode, 'Element not found ' + astNode.expand.$cstNode?.text)
|
|
239
279
|
const expanded = this.resolveFqn(elementNode)
|
|
240
280
|
return {
|
|
241
281
|
expanded
|
|
242
282
|
}
|
|
243
283
|
}
|
|
244
|
-
if (ast.
|
|
284
|
+
if (ast.isElementDescedantsExpression(astNode)) {
|
|
245
285
|
const elementNode = elementRef(astNode.parent)
|
|
246
286
|
invariant(elementNode, 'Element not found ' + astNode.parent.$cstNode?.text)
|
|
247
287
|
const element = this.resolveFqn(elementNode)
|
|
@@ -261,20 +301,25 @@ export class LikeC4ModelParser {
|
|
|
261
301
|
nonexhaustive(astNode)
|
|
262
302
|
}
|
|
263
303
|
|
|
264
|
-
private parseCustomElementExpr(astNode: ast.
|
|
304
|
+
private parseCustomElementExpr(astNode: ast.CustomElementExpression): c4.CustomElementExpr {
|
|
265
305
|
let targetRef
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
306
|
+
switch (true) {
|
|
307
|
+
case ast.isElementRef(astNode.target):
|
|
308
|
+
targetRef = astNode.target
|
|
309
|
+
break
|
|
310
|
+
case ast.isExpandElementExpression(astNode.target):
|
|
311
|
+
targetRef = astNode.target.expand
|
|
312
|
+
break
|
|
313
|
+
case ast.isElementDescedantsExpression(astNode.target):
|
|
314
|
+
targetRef = astNode.target.parent
|
|
315
|
+
break
|
|
316
|
+
default:
|
|
317
|
+
throw new Error('Unsupported target of custom element')
|
|
318
|
+
}
|
|
274
319
|
const elementNode = elementRef(targetRef)
|
|
275
320
|
invariant(elementNode, 'element not found: ' + astNode.$cstNode?.text)
|
|
276
321
|
const element = this.resolveFqn(elementNode)
|
|
277
|
-
const props = astNode.
|
|
322
|
+
const props = astNode.custom.props ?? []
|
|
278
323
|
return props.reduce(
|
|
279
324
|
(acc, prop) => {
|
|
280
325
|
if (ast.isNavigateToProperty(prop)) {
|
|
@@ -290,7 +335,10 @@ export class LikeC4ModelParser {
|
|
|
290
335
|
return acc
|
|
291
336
|
}
|
|
292
337
|
if (ast.isIconProperty(prop)) {
|
|
293
|
-
|
|
338
|
+
const value = prop.libicon?.ref?.name ?? prop.value
|
|
339
|
+
if (isTruthy(value)) {
|
|
340
|
+
acc.custom[prop.key] = value as c4.IconUrl
|
|
341
|
+
}
|
|
294
342
|
return acc
|
|
295
343
|
}
|
|
296
344
|
if (ast.isColorProperty(prop)) {
|
|
@@ -320,54 +368,90 @@ export class LikeC4ModelParser {
|
|
|
320
368
|
)
|
|
321
369
|
}
|
|
322
370
|
|
|
323
|
-
private
|
|
324
|
-
if (ast.
|
|
371
|
+
private parseExpression(astNode: ast.Expression): c4.Expression {
|
|
372
|
+
if (ast.isCustomRelationExpression(astNode)) {
|
|
373
|
+
return this.parseCustomRelationExpr(astNode)
|
|
374
|
+
}
|
|
375
|
+
if (ast.isCustomElementExpression(astNode)) {
|
|
376
|
+
return this.parseCustomElementExpr(astNode)
|
|
377
|
+
}
|
|
378
|
+
if (ast.isElementExpression(astNode)) {
|
|
379
|
+
return this.parseElementExpr(astNode)
|
|
380
|
+
}
|
|
381
|
+
if (ast.isRelationExpression(astNode)) {
|
|
382
|
+
return this.parseRelationExpr(astNode)
|
|
383
|
+
}
|
|
384
|
+
nonexhaustive(astNode)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private parseCustomRelationExpr(astNode: ast.CustomRelationExpression): c4.CustomRelationExpr {
|
|
388
|
+
const relation = this.parseRelationExpr(astNode.relation)
|
|
389
|
+
const props = astNode.custom.props ?? []
|
|
390
|
+
return props.reduce(
|
|
391
|
+
(acc, prop) => {
|
|
392
|
+
if (ast.isRelationStringProperty(prop)) {
|
|
393
|
+
const value = removeIndent(prop.value)
|
|
394
|
+
if (isTruthy(value)) {
|
|
395
|
+
acc.customRelation['title'] = value
|
|
396
|
+
}
|
|
397
|
+
return acc
|
|
398
|
+
}
|
|
399
|
+
if (ast.isArrowProperty(prop)) {
|
|
400
|
+
acc.customRelation[prop.key] = prop.value
|
|
401
|
+
return acc
|
|
402
|
+
}
|
|
403
|
+
if (ast.isColorProperty(prop)) {
|
|
404
|
+
acc.customRelation[prop.key] = prop.value
|
|
405
|
+
return acc
|
|
406
|
+
}
|
|
407
|
+
if (ast.isLineProperty(prop)) {
|
|
408
|
+
acc.customRelation[prop.key] = prop.value
|
|
409
|
+
return acc
|
|
410
|
+
}
|
|
411
|
+
nonexhaustive(prop)
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
customRelation: {
|
|
415
|
+
relation
|
|
416
|
+
}
|
|
417
|
+
} as c4.CustomRelationExpr
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private parseRelationExpr(astNode: ast.RelationExpression): c4.RelationExpression {
|
|
422
|
+
if (ast.isDirectedRelationExpression(astNode)) {
|
|
325
423
|
return {
|
|
326
|
-
source: this.parseElementExpr(astNode.source),
|
|
424
|
+
source: this.parseElementExpr(astNode.source.from),
|
|
327
425
|
target: this.parseElementExpr(astNode.target),
|
|
328
|
-
isBidirectional: astNode.isBidirectional
|
|
426
|
+
isBidirectional: astNode.source.isBidirectional
|
|
329
427
|
}
|
|
330
428
|
}
|
|
331
|
-
if (ast.
|
|
429
|
+
if (ast.isInOutRelationExpression(astNode)) {
|
|
332
430
|
return {
|
|
333
431
|
inout: this.parseElementExpr(astNode.inout.to)
|
|
334
432
|
}
|
|
335
433
|
}
|
|
336
|
-
if (ast.
|
|
434
|
+
if (ast.isOutgoingRelationExpression(astNode)) {
|
|
337
435
|
return {
|
|
338
436
|
outgoing: this.parseElementExpr(astNode.from)
|
|
339
437
|
}
|
|
340
438
|
}
|
|
341
|
-
if (ast.
|
|
439
|
+
if (ast.isIncomingRelationExpression(astNode)) {
|
|
342
440
|
return {
|
|
343
441
|
incoming: this.parseElementExpr(astNode.to)
|
|
344
442
|
}
|
|
345
443
|
}
|
|
346
|
-
if (ast.isCustomElementExpr(astNode)) {
|
|
347
|
-
return this.parseCustomElementExpr(astNode)
|
|
348
|
-
}
|
|
349
|
-
if (ast.isElementExpr(astNode)) {
|
|
350
|
-
return this.parseElementExpr(astNode)
|
|
351
|
-
}
|
|
352
444
|
nonexhaustive(astNode)
|
|
353
445
|
}
|
|
354
446
|
|
|
355
447
|
private parseViewRule(astRule: ast.ViewRule, isValid: IsValidFn): c4.ViewRule {
|
|
356
|
-
if (ast.
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
return isValid(n) ? this.parsePredicateExpr(n) : []
|
|
360
|
-
} catch (e) {
|
|
361
|
-
logWarnError(e)
|
|
362
|
-
return []
|
|
363
|
-
}
|
|
364
|
-
})
|
|
365
|
-
return ast.isIncludePredicate(astRule) ? { include: exprs } : { exclude: exprs }
|
|
448
|
+
if (ast.isViewRulePredicate(astRule)) {
|
|
449
|
+
return this.parseViewRulePredicate(astRule, isValid)
|
|
366
450
|
}
|
|
367
451
|
if (ast.isViewRuleStyle(astRule)) {
|
|
368
|
-
const styleProps = toElementStyle(astRule.
|
|
452
|
+
const styleProps = toElementStyle(astRule.props)
|
|
369
453
|
return {
|
|
370
|
-
targets:
|
|
454
|
+
targets: this.parseElementExpressionsIterator(astRule.target),
|
|
371
455
|
style: {
|
|
372
456
|
...styleProps
|
|
373
457
|
}
|
|
@@ -398,18 +482,22 @@ export class LikeC4ModelParser {
|
|
|
398
482
|
if (!targetEl) {
|
|
399
483
|
throw new Error('Invalid reference to target')
|
|
400
484
|
}
|
|
485
|
+
const title = removeIndent(node.title) ?? null
|
|
401
486
|
let source = this.resolveFqn(sourceEl)
|
|
402
487
|
let target = this.resolveFqn(targetEl)
|
|
403
488
|
if (node.isBackward) {
|
|
404
|
-
|
|
489
|
+
return {
|
|
490
|
+
source: target,
|
|
491
|
+
target: source,
|
|
492
|
+
title,
|
|
493
|
+
isBackward: true
|
|
494
|
+
}
|
|
405
495
|
}
|
|
406
496
|
|
|
407
|
-
const title = toSingleLine(node.title) ?? null
|
|
408
497
|
return {
|
|
409
498
|
source,
|
|
410
499
|
target,
|
|
411
|
-
title
|
|
412
|
-
isBackward: node.isBackward
|
|
500
|
+
title
|
|
413
501
|
}
|
|
414
502
|
}
|
|
415
503
|
|
|
@@ -518,15 +606,24 @@ export class LikeC4ModelParser {
|
|
|
518
606
|
try {
|
|
519
607
|
if (ast.isDynamicViewRulePredicate(n)) {
|
|
520
608
|
const include = [] as (c4.ElementExpression | c4.CustomElementExpr)[]
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
609
|
+
let iter: ast.DynamicViewRulePredicateIterator | undefined = n.exprs
|
|
610
|
+
while (iter) {
|
|
611
|
+
try {
|
|
612
|
+
switch (true) {
|
|
613
|
+
case ast.isElementExpression(iter.value):
|
|
614
|
+
isValid(iter.value) && include.unshift(this.parseElementExpr(iter.value))
|
|
615
|
+
break
|
|
616
|
+
|
|
617
|
+
case ast.isCustomElementExpression(iter.value):
|
|
618
|
+
isValid(iter.value) && include.unshift(this.parseCustomElementExpr(iter.value))
|
|
619
|
+
break
|
|
620
|
+
default:
|
|
621
|
+
nonexhaustive(iter.value)
|
|
622
|
+
}
|
|
623
|
+
} catch (e) {
|
|
624
|
+
logWarnError(e)
|
|
529
625
|
}
|
|
626
|
+
iter = iter.prev
|
|
530
627
|
}
|
|
531
628
|
if (include.length > 0) {
|
|
532
629
|
acc.push({ include })
|
|
@@ -534,8 +631,8 @@ export class LikeC4ModelParser {
|
|
|
534
631
|
return acc
|
|
535
632
|
}
|
|
536
633
|
if (ast.isViewRuleStyle(n)) {
|
|
537
|
-
const styleProps = toElementStyle(n.
|
|
538
|
-
const targets =
|
|
634
|
+
const styleProps = toElementStyle(n.props)
|
|
635
|
+
const targets = this.parseElementExpressionsIterator(n.target)
|
|
539
636
|
if (targets.length > 0) {
|
|
540
637
|
acc.push({
|
|
541
638
|
targets,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Fqn, invariant, isAncestor, type NonEmptyArray, nonNullable, type ViewChanges } from '@likec4/core'
|
|
2
2
|
import { GrammarUtils } from 'langium'
|
|
3
|
-
import { entries, filter, findLast, last } from 'remeda'
|
|
3
|
+
import { entries, filter, findLast, isTruthy, last } from 'remeda'
|
|
4
4
|
import { type Range, TextEdit } from 'vscode-languageserver-protocol'
|
|
5
5
|
import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast'
|
|
6
6
|
import type { FqnIndex } from '../model'
|
|
@@ -12,7 +12,7 @@ const asViewStyleRule = (target: string, style: ViewChanges.ChangeElementStyle['
|
|
|
12
12
|
const indentStr = indent > 0 ? ' '.repeat(indent) : ''
|
|
13
13
|
return [
|
|
14
14
|
indentStr + `style ${target} {`,
|
|
15
|
-
...entries
|
|
15
|
+
...entries(style).map(([key, value]) =>
|
|
16
16
|
indentStr + ` ${key} ${key === 'opacity' ? value.toString() + '%' : value}`
|
|
17
17
|
),
|
|
18
18
|
indentStr + `}`
|
|
@@ -37,8 +37,8 @@ const isMatchingViewRule =
|
|
|
37
37
|
if (!ast.isViewRuleStyle(rule)) {
|
|
38
38
|
return false
|
|
39
39
|
}
|
|
40
|
-
const
|
|
41
|
-
if (!target ||
|
|
40
|
+
const target = rule.target.value
|
|
41
|
+
if (!target || isTruthy(rule.target.prev) || !ast.isElementRef(target)) {
|
|
42
42
|
return false
|
|
43
43
|
}
|
|
44
44
|
const ref = target.el.ref
|
|
@@ -125,9 +125,9 @@ export function changeElementStyle(services: LikeC4Services, {
|
|
|
125
125
|
for (const { rule } of existing) {
|
|
126
126
|
const ruleCstNode = rule.$cstNode
|
|
127
127
|
invariant(ruleCstNode, 'RuleCstNode not found')
|
|
128
|
-
for (const [key, _value] of entries
|
|
128
|
+
for (const [key, _value] of entries(style)) {
|
|
129
129
|
const value = key === 'opacity' ? _value.toString() + '%' : _value
|
|
130
|
-
const ruleProp = rule.
|
|
130
|
+
const ruleProp = rule.props.find(p => p.key === key)
|
|
131
131
|
// replace existing property
|
|
132
132
|
if (ruleProp && ruleProp.$cstNode) {
|
|
133
133
|
const { range: { start, end } } = nonNullable(
|