@likec4/language-server 1.13.0 → 1.15.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.tmLanguage.json +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/likec4lib.cjs +0 -1
- package/dist/likec4lib.mjs +0 -1
- package/dist/model-graph/index.cjs +1 -1
- package/dist/model-graph/index.mjs +1 -1
- package/dist/shared/{language-server.CbDa016p.cjs → language-server.80ITEDo5.cjs} +272 -64
- package/dist/shared/{language-server.C2ebP2pZ.cjs → language-server.BUtiWTKg.cjs} +383 -32
- package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.DXC9g4_f.mjs} +274 -66
- package/dist/shared/{language-server.ryB8CivX.d.mts → language-server.DfMwkd2l.d.mts} +81 -15
- package/dist/shared/{language-server.Cnq_hgfm.d.cts → language-server.U2piOAVt.d.cts} +81 -15
- package/dist/shared/{language-server.eY70DuKx.d.ts → language-server.j-ShR6as.d.ts} +81 -15
- package/dist/shared/{language-server.CrE0nFSB.mjs → language-server.zY53FGJE.mjs} +385 -34
- package/package.json +12 -12
- package/src/ast.ts +11 -0
- package/src/generated/ast.ts +177 -17
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +0 -1
- package/src/like-c4.langium +50 -4
- package/src/lsp/CompletionProvider.ts +70 -2
- package/src/model/model-builder.ts +25 -3
- package/src/model/model-parser.ts +150 -33
- package/src/model-graph/compute-view/__test__/fixture.ts +45 -4
- package/src/model-graph/compute-view/compute.ts +223 -40
- package/src/model-graph/compute-view/predicates.ts +12 -6
- package/src/model-graph/utils/applyCustomElementProperties.ts +18 -4
- package/src/model-graph/utils/applyCustomRelationProperties.ts +2 -1
- package/src/model-graph/utils/applyViewRuleStyles.ts +30 -25
- package/src/model-graph/utils/buildComputeNodes.ts +69 -17
- package/src/model-graph/utils/elementExpressionToPredicate.ts +3 -1
- package/src/model-graph/utils/sortNodes.ts +11 -7
- package/src/references/scope-computation.ts +24 -1
- package/src/validation/index.ts +4 -0
- package/src/validation/specification.ts +30 -0
- package/src/view-utils/index.ts +1 -0
- package/src/view-utils/resolve-global-rules.ts +72 -0
- package/dist/shared/language-server.B-9_mDoo.d.mts +0 -1238
- package/dist/shared/language-server.C3oS5yhF.d.cts +0 -1238
- package/dist/shared/language-server.r5AXAWzc.d.ts +0 -1238
|
@@ -1,36 +1,59 @@
|
|
|
1
|
-
import
|
|
2
|
-
Color,
|
|
3
|
-
ComputedEdge,
|
|
4
|
-
ComputedElementView,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import {
|
|
2
|
+
type Color,
|
|
3
|
+
type ComputedEdge,
|
|
4
|
+
type ComputedElementView,
|
|
5
|
+
type ComputedNode,
|
|
6
|
+
type EdgeId,
|
|
7
|
+
type Element,
|
|
8
|
+
ElementKind,
|
|
9
|
+
type ElementPredicateExpression,
|
|
10
|
+
type ElementView,
|
|
11
|
+
type NodeId,
|
|
12
|
+
type NonEmptyArray,
|
|
13
|
+
type Relation,
|
|
14
|
+
type RelationPredicateExpression,
|
|
15
|
+
type RelationshipArrowType,
|
|
16
|
+
type RelationshipKind,
|
|
17
|
+
type RelationshipLineType,
|
|
18
|
+
type Tag,
|
|
19
|
+
type ViewID,
|
|
20
|
+
type ViewRuleGroup,
|
|
21
|
+
type ViewRulePredicate
|
|
18
22
|
} from '@likec4/core'
|
|
19
23
|
import {
|
|
20
|
-
ancestorsFqn,
|
|
21
24
|
commonAncestor,
|
|
25
|
+
commonHead,
|
|
22
26
|
compareRelations,
|
|
23
27
|
Expr,
|
|
24
28
|
invariant,
|
|
25
29
|
isAncestor,
|
|
26
30
|
isScopedElementView,
|
|
27
31
|
isViewRuleAutoLayout,
|
|
32
|
+
isViewRuleGroup,
|
|
28
33
|
isViewRulePredicate,
|
|
29
34
|
nonexhaustive,
|
|
30
|
-
|
|
35
|
+
nonNullable,
|
|
31
36
|
whereOperatorAsPredicate
|
|
32
37
|
} from '@likec4/core'
|
|
33
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
anyPass,
|
|
40
|
+
concat,
|
|
41
|
+
difference,
|
|
42
|
+
filter,
|
|
43
|
+
first,
|
|
44
|
+
hasAtLeast,
|
|
45
|
+
isNonNull,
|
|
46
|
+
isTruthy,
|
|
47
|
+
last,
|
|
48
|
+
map,
|
|
49
|
+
omit,
|
|
50
|
+
only,
|
|
51
|
+
pipe,
|
|
52
|
+
reduce,
|
|
53
|
+
reverse,
|
|
54
|
+
sort,
|
|
55
|
+
unique
|
|
56
|
+
} from 'remeda'
|
|
34
57
|
import { calcViewLayoutHash } from '../../view-utils/view-hash'
|
|
35
58
|
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
36
59
|
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
@@ -86,12 +109,78 @@ function isDirectEdge({ relations: [rel, ...tail], source, target }: ComputeCtx.
|
|
|
86
109
|
return false
|
|
87
110
|
}
|
|
88
111
|
|
|
112
|
+
export class NodesGroup {
|
|
113
|
+
static readonly kind = ElementKind.Group
|
|
114
|
+
|
|
115
|
+
static root() {
|
|
116
|
+
return new NodesGroup('@root' as NodeId, { title: null, groupRules: [] })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static is(node: ComputedNode) {
|
|
120
|
+
return node.kind === NodesGroup.kind
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
readonly explicits = new Set<Element>()
|
|
124
|
+
readonly implicits = new Set<Element>()
|
|
125
|
+
|
|
126
|
+
constructor(
|
|
127
|
+
public id: NodeId,
|
|
128
|
+
public viewRule: ViewRuleGroup,
|
|
129
|
+
public parent: NodeId | null = null
|
|
130
|
+
) {
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Add element explicitly
|
|
135
|
+
* Included even without relationships
|
|
136
|
+
*/
|
|
137
|
+
addElement(...el: Element[]) {
|
|
138
|
+
for (const r of el) {
|
|
139
|
+
this.explicits.add(r)
|
|
140
|
+
this.implicits.add(r)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Add element implicitly
|
|
146
|
+
* Included if only has relationships
|
|
147
|
+
*/
|
|
148
|
+
addImplicit(...el: Element[]) {
|
|
149
|
+
for (const r of el) {
|
|
150
|
+
this.implicits.add(r)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
excludeElement(...excludes: Element[]) {
|
|
155
|
+
for (const el of excludes) {
|
|
156
|
+
this.explicits.delete(el)
|
|
157
|
+
this.implicits.delete(el)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
isEmpty() {
|
|
162
|
+
return this.explicits.size === 0 && this.implicits.size === 0
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
89
166
|
export class ComputeCtx {
|
|
90
167
|
// Intermediate state
|
|
91
168
|
private explicits = new Set<Element>()
|
|
92
169
|
private implicits = new Set<Element>()
|
|
93
170
|
private ctxEdges = [] as ComputeCtx.Edge[]
|
|
94
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Root group - not included in the groups
|
|
174
|
+
* But used to accumulate elements that are not in any group
|
|
175
|
+
*/
|
|
176
|
+
private __rootGroup = NodesGroup.root()
|
|
177
|
+
private groups = [] as NodesGroup[]
|
|
178
|
+
private activeGroupStack = [] as NodesGroup[]
|
|
179
|
+
|
|
180
|
+
protected get activeGroup() {
|
|
181
|
+
return this.activeGroupStack[0] ?? this.__rootGroup
|
|
182
|
+
}
|
|
183
|
+
|
|
95
184
|
public static elementView(view: ElementView, graph: LikeC4ModelGraph) {
|
|
96
185
|
return new ComputeCtx(view, graph).compute()
|
|
97
186
|
}
|
|
@@ -102,7 +191,6 @@ export class ComputeCtx {
|
|
|
102
191
|
) {}
|
|
103
192
|
|
|
104
193
|
protected compute(): ComputedElementView {
|
|
105
|
-
// reset ctx
|
|
106
194
|
this.reset()
|
|
107
195
|
const {
|
|
108
196
|
docUri: _docUri, // exclude docUri
|
|
@@ -110,15 +198,34 @@ export class ComputeCtx {
|
|
|
110
198
|
...view
|
|
111
199
|
} = this.view
|
|
112
200
|
|
|
113
|
-
const viewPredicates = rules.filter(isViewRulePredicate)
|
|
201
|
+
const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup])) as Array<
|
|
202
|
+
ViewRulePredicate | ViewRuleGroup
|
|
203
|
+
>
|
|
114
204
|
if (this.root && viewPredicates.length == 0) {
|
|
115
205
|
this.addElement(this.graph.element(this.root))
|
|
116
206
|
}
|
|
117
207
|
this.processPredicates(viewPredicates)
|
|
118
208
|
this.removeRedundantImplicitEdges()
|
|
209
|
+
if (this.groups.length > 0) {
|
|
210
|
+
this.cleanGroupElements()
|
|
211
|
+
}
|
|
119
212
|
|
|
120
213
|
const elements = [...this.includedElements]
|
|
121
|
-
const nodesMap = buildComputeNodes(elements)
|
|
214
|
+
const nodesMap = buildComputeNodes(elements, this.groups)
|
|
215
|
+
|
|
216
|
+
const ancestorsOf = (node: ComputedNode) => {
|
|
217
|
+
const ancestors = [] as ComputedNode[]
|
|
218
|
+
let parent = node.parent
|
|
219
|
+
while (parent) {
|
|
220
|
+
const parentNode = nodesMap.get(parent)
|
|
221
|
+
if (!parentNode) {
|
|
222
|
+
break
|
|
223
|
+
}
|
|
224
|
+
ancestors.push(parentNode)
|
|
225
|
+
parent = parentNode.parent
|
|
226
|
+
}
|
|
227
|
+
return ancestors
|
|
228
|
+
}
|
|
122
229
|
|
|
123
230
|
const edgesMap = new Map<EdgeId, ComputedEdge>()
|
|
124
231
|
const edges = this.computeEdges()
|
|
@@ -128,30 +235,45 @@ export class ComputeCtx {
|
|
|
128
235
|
const target = nodesMap.get(edge.target)
|
|
129
236
|
invariant(source, `Source node ${edge.source} not found`)
|
|
130
237
|
invariant(target, `Target node ${edge.target} not found`)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
238
|
+
// These ancestors are reversed: from bottom to top
|
|
239
|
+
const sourceAncestors = ancestorsOf(source)
|
|
240
|
+
const targetAncestors = ancestorsOf(target)
|
|
241
|
+
|
|
242
|
+
const edgeParent = last(
|
|
243
|
+
commonHead(
|
|
244
|
+
reverse(sourceAncestors),
|
|
245
|
+
reverse(targetAncestors)
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
edge.parent = edgeParent?.id ?? null
|
|
134
249
|
source.outEdges.push(edge.id)
|
|
135
250
|
target.inEdges.push(edge.id)
|
|
136
251
|
// Process edge source ancestors
|
|
137
|
-
for (const sourceAncestor of
|
|
138
|
-
if (sourceAncestor ===
|
|
252
|
+
for (const sourceAncestor of sourceAncestors) {
|
|
253
|
+
if (sourceAncestor === edgeParent) {
|
|
139
254
|
break
|
|
140
255
|
}
|
|
141
|
-
|
|
256
|
+
sourceAncestor.outEdges.push(edge.id)
|
|
142
257
|
}
|
|
143
258
|
// Process target hierarchy
|
|
144
|
-
for (const targetAncestor of
|
|
145
|
-
if (targetAncestor ===
|
|
259
|
+
for (const targetAncestor of targetAncestors) {
|
|
260
|
+
if (targetAncestor === edgeParent) {
|
|
146
261
|
break
|
|
147
262
|
}
|
|
148
|
-
|
|
263
|
+
targetAncestor.inEdges.push(edge.id)
|
|
149
264
|
}
|
|
150
265
|
}
|
|
151
266
|
|
|
152
267
|
// nodesMap sorted hierarchically,
|
|
153
268
|
// but we need to keep the initial sort
|
|
154
|
-
|
|
269
|
+
let initialSort = elements.map(e => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`))
|
|
270
|
+
|
|
271
|
+
if (this.groups.length > 0) {
|
|
272
|
+
initialSort = concat(
|
|
273
|
+
this.groups.map(g => nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
|
|
274
|
+
initialSort
|
|
275
|
+
)
|
|
276
|
+
}
|
|
155
277
|
|
|
156
278
|
const nodes = applyCustomElementProperties(
|
|
157
279
|
rules,
|
|
@@ -326,6 +448,7 @@ export class ComputeCtx {
|
|
|
326
448
|
}
|
|
327
449
|
|
|
328
450
|
protected addEdges(edges: ComputeCtx.Edge[]) {
|
|
451
|
+
const added = [] as ComputeCtx.Edge[]
|
|
329
452
|
for (const e of edges) {
|
|
330
453
|
if (!hasAtLeast(e.relations, 1)) {
|
|
331
454
|
continue
|
|
@@ -335,10 +458,13 @@ export class ComputeCtx {
|
|
|
335
458
|
)
|
|
336
459
|
if (existing) {
|
|
337
460
|
existing.relations = unique([...existing.relations, ...e.relations])
|
|
461
|
+
added.push(existing)
|
|
338
462
|
continue
|
|
339
463
|
}
|
|
464
|
+
added.push(e)
|
|
340
465
|
this.ctxEdges.push(e)
|
|
341
466
|
}
|
|
467
|
+
return added
|
|
342
468
|
}
|
|
343
469
|
|
|
344
470
|
/**
|
|
@@ -347,6 +473,14 @@ export class ComputeCtx {
|
|
|
347
473
|
*/
|
|
348
474
|
protected addElement(...el: Element[]) {
|
|
349
475
|
for (const r of el) {
|
|
476
|
+
if (!this.explicits.has(r)) {
|
|
477
|
+
this.activeGroup.addElement(r)
|
|
478
|
+
} else if (this.activeGroup !== this.__rootGroup) {
|
|
479
|
+
this.groups.forEach(g => {
|
|
480
|
+
g.implicits.delete(r)
|
|
481
|
+
})
|
|
482
|
+
this.activeGroup.addImplicit(r)
|
|
483
|
+
}
|
|
350
484
|
this.explicits.add(r)
|
|
351
485
|
this.implicits.add(r)
|
|
352
486
|
}
|
|
@@ -359,7 +493,14 @@ export class ComputeCtx {
|
|
|
359
493
|
protected addImplicit(...el: Element[]) {
|
|
360
494
|
for (const r of el) {
|
|
361
495
|
this.implicits.add(r)
|
|
496
|
+
// Remove implicits from other groups
|
|
497
|
+
if (this.activeGroup !== this.__rootGroup) {
|
|
498
|
+
this.groups.forEach(g => {
|
|
499
|
+
g.implicits.delete(r)
|
|
500
|
+
})
|
|
501
|
+
}
|
|
362
502
|
}
|
|
503
|
+
this.activeGroup.addImplicit(...el)
|
|
363
504
|
}
|
|
364
505
|
|
|
365
506
|
protected excludeElement(...excludes: Element[]) {
|
|
@@ -368,14 +509,12 @@ export class ComputeCtx {
|
|
|
368
509
|
this.explicits.delete(el)
|
|
369
510
|
this.implicits.delete(el)
|
|
370
511
|
}
|
|
512
|
+
this.__rootGroup.excludeElement(...excludes)
|
|
513
|
+
this.groups.forEach(g => {
|
|
514
|
+
g.excludeElement(...excludes)
|
|
515
|
+
})
|
|
371
516
|
}
|
|
372
517
|
|
|
373
|
-
// protected excludeImplicit(...excludes: Element[]) {
|
|
374
|
-
// for (const el of excludes) {
|
|
375
|
-
// this.implicits.delete(el)
|
|
376
|
-
// }
|
|
377
|
-
// }
|
|
378
|
-
|
|
379
518
|
protected excludeRelation(...relations: Relation[]) {
|
|
380
519
|
if (relations.length === 0) {
|
|
381
520
|
return
|
|
@@ -407,11 +546,15 @@ export class ComputeCtx {
|
|
|
407
546
|
const remaining = this.includedElements
|
|
408
547
|
if (remaining.size === 0) {
|
|
409
548
|
this.implicits.clear()
|
|
549
|
+
this.__rootGroup.implicits.clear()
|
|
550
|
+
this.groups.forEach(g => g.implicits.clear())
|
|
410
551
|
return
|
|
411
552
|
}
|
|
412
553
|
for (const el of excludedImplicits) {
|
|
413
554
|
if (!remaining.has(el)) {
|
|
414
555
|
this.implicits.delete(el)
|
|
556
|
+
this.__rootGroup.implicits.delete(el)
|
|
557
|
+
this.groups.forEach(g => g.implicits.delete(el))
|
|
415
558
|
}
|
|
416
559
|
}
|
|
417
560
|
}
|
|
@@ -420,6 +563,10 @@ export class ComputeCtx {
|
|
|
420
563
|
this.explicits.clear()
|
|
421
564
|
this.implicits.clear()
|
|
422
565
|
this.ctxEdges = []
|
|
566
|
+
// Reset groups
|
|
567
|
+
this.__rootGroup = NodesGroup.root()
|
|
568
|
+
this.groups = []
|
|
569
|
+
this.activeGroupStack = []
|
|
423
570
|
}
|
|
424
571
|
|
|
425
572
|
// Filter out edges if there are edges between descendants
|
|
@@ -478,9 +625,45 @@ export class ComputeCtx {
|
|
|
478
625
|
}, [] as ComputeCtx.Edge[])
|
|
479
626
|
}
|
|
480
627
|
|
|
481
|
-
protected
|
|
628
|
+
protected cleanGroupElements() {
|
|
629
|
+
const unprocessed = new Set<Element>(difference(
|
|
630
|
+
[...this.includedElements],
|
|
631
|
+
[...this.__rootGroup.explicits]
|
|
632
|
+
))
|
|
633
|
+
// Step 1 - Process explicits
|
|
634
|
+
for (const group of this.groups) {
|
|
635
|
+
const explicits = [...group.explicits]
|
|
636
|
+
group.explicits.clear()
|
|
637
|
+
for (const el of explicits) {
|
|
638
|
+
if (unprocessed.delete(el)) {
|
|
639
|
+
group.explicits.add(el)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Step 2 - Process implicits
|
|
644
|
+
for (const group of this.groups) {
|
|
645
|
+
for (const el of group.implicits) {
|
|
646
|
+
if (unprocessed.delete(el)) {
|
|
647
|
+
group.explicits.add(el)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
group.implicits.clear()
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
protected processPredicates(viewRules: Array<ViewRulePredicate | ViewRuleGroup>): this {
|
|
482
655
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
483
656
|
for (const rule of viewRules) {
|
|
657
|
+
if (isViewRuleGroup(rule)) {
|
|
658
|
+
const parent = first(this.activeGroupStack)
|
|
659
|
+
const groupId = `@gr${this.groups.length + 1}` as NodeId
|
|
660
|
+
const group = new NodesGroup(groupId, rule, parent?.id ?? null)
|
|
661
|
+
this.groups.push(group)
|
|
662
|
+
this.activeGroupStack.unshift(group)
|
|
663
|
+
this.processPredicates(rule.groupRules)
|
|
664
|
+
this.activeGroupStack.shift()
|
|
665
|
+
continue
|
|
666
|
+
}
|
|
484
667
|
const isInclude = 'include' in rule
|
|
485
668
|
const exprs = rule.include ?? rule.exclude
|
|
486
669
|
for (const expr of exprs) {
|
|
@@ -97,7 +97,9 @@ export function includeWildcardRef(this: ComputeCtx, _expr: Expr.WildcardExpr, w
|
|
|
97
97
|
]
|
|
98
98
|
|
|
99
99
|
for (const el of children) {
|
|
100
|
-
this.addEdges(this.graph.anyEdgesBetween(el, neighbours))
|
|
100
|
+
this.addEdges(this.graph.anyEdgesBetween(el, neighbours)).forEach(edge => {
|
|
101
|
+
this.addImplicit(edge.source, edge.target)
|
|
102
|
+
})
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
// If root has no children
|
|
@@ -331,8 +333,9 @@ export function includeIncomingExpr(this: ComputeCtx, expr: Expr.IncomingExpr, w
|
|
|
331
333
|
if (edges.length === 0) {
|
|
332
334
|
return
|
|
333
335
|
}
|
|
334
|
-
this.addEdges(edges)
|
|
335
|
-
|
|
336
|
+
this.addEdges(edges).forEach(edge => {
|
|
337
|
+
this.addImplicit(edge.target)
|
|
338
|
+
})
|
|
336
339
|
}
|
|
337
340
|
export function excludeIncomingExpr(this: ComputeCtx, expr: Expr.IncomingExpr, where?: RelationPredicateFn) {
|
|
338
341
|
let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where)
|
|
@@ -375,8 +378,9 @@ export function includeOutgoingExpr(this: ComputeCtx, expr: Expr.OutgoingExpr, w
|
|
|
375
378
|
if (edges.length === 0) {
|
|
376
379
|
return
|
|
377
380
|
}
|
|
378
|
-
this.addEdges(edges)
|
|
379
|
-
|
|
381
|
+
this.addEdges(edges).forEach(edge => {
|
|
382
|
+
this.addImplicit(edge.source)
|
|
383
|
+
})
|
|
380
384
|
}
|
|
381
385
|
export function excludeOutgoingExpr(this: ComputeCtx, expr: Expr.OutgoingExpr, where?: RelationPredicateFn) {
|
|
382
386
|
const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where)
|
|
@@ -484,7 +488,9 @@ export function includeRelationExpr(this: ComputeCtx, expr: Expr.RelationExpr, w
|
|
|
484
488
|
if (expr.isBidirectional === true) {
|
|
485
489
|
edges.push(...this.graph.edgesBetween(targets, sources))
|
|
486
490
|
}
|
|
487
|
-
this.addEdges(filterEdges(edges, where))
|
|
491
|
+
this.addEdges(filterEdges(edges, where)).forEach(edge => {
|
|
492
|
+
this.activeGroup.addImplicit(edge.source, edge.target)
|
|
493
|
+
})
|
|
488
494
|
}
|
|
489
495
|
|
|
490
496
|
export function excludeRelationExpr(this: ComputeCtx, expr: Expr.RelationExpr, where?: RelationPredicateFn) {
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Expr } from '@likec4/core'
|
|
1
|
+
import { ComputedNode, type Expression, type ViewRule } from '@likec4/core'
|
|
2
|
+
import { Expr, isViewRuleGroup, isViewRulePredicate } from '@likec4/core'
|
|
3
3
|
import { isEmpty, isNullish, omitBy } from 'remeda'
|
|
4
|
+
import { NodesGroup } from '../compute-view/compute'
|
|
4
5
|
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
6
|
|
|
7
|
+
export function flattenGroupRules<T extends Expression>(guard: (expr: Expression) => expr is T) {
|
|
8
|
+
return (rule: ViewRule): Array<T> => {
|
|
9
|
+
if (isViewRuleGroup(rule)) {
|
|
10
|
+
return rule.groupRules.flatMap(flattenGroupRules(guard))
|
|
11
|
+
}
|
|
12
|
+
if (isViewRulePredicate(rule)) {
|
|
13
|
+
return 'include' in rule ? rule.include.filter(guard) : []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
|
|
7
|
-
const rules = _rules.flatMap(
|
|
21
|
+
const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomElement))
|
|
8
22
|
if (rules.length === 0) {
|
|
9
23
|
return _nodes
|
|
10
24
|
}
|
|
@@ -18,7 +32,7 @@ export function applyCustomElementProperties(_rules: ViewRule[], _nodes: Compute
|
|
|
18
32
|
const notEmpty = !isEmpty(rest)
|
|
19
33
|
const satisfies = elementExprToPredicate(expr)
|
|
20
34
|
nodes.forEach((node, i) => {
|
|
21
|
-
if (!satisfies(node)) {
|
|
35
|
+
if (ComputedNode.isNodesGroup(node) || !satisfies(node)) {
|
|
22
36
|
return
|
|
23
37
|
}
|
|
24
38
|
if (notEmpty) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ComputedEdge, ComputedNode, Element, ViewRule } from '@likec4/core'
|
|
2
2
|
import { Expr, nonexhaustive } from '@likec4/core'
|
|
3
3
|
import { isNullish, omitBy } from 'remeda'
|
|
4
|
+
import { flattenGroupRules } from './applyCustomElementProperties'
|
|
4
5
|
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
6
|
|
|
6
7
|
function relationExpressionToPredicates(
|
|
@@ -39,7 +40,7 @@ export function applyCustomRelationProperties(
|
|
|
39
40
|
nodes: ComputedNode[],
|
|
40
41
|
_edges: Iterable<ComputedEdge>
|
|
41
42
|
): ComputedEdge[] {
|
|
42
|
-
const rules = _rules.flatMap(
|
|
43
|
+
const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr))
|
|
43
44
|
const edges = Array.from(_edges)
|
|
44
45
|
if (rules.length === 0 || edges.length === 0) {
|
|
45
46
|
return edges
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ComputedNode, type ViewRule } from '@likec4/core'
|
|
2
2
|
import { Expr, isViewRuleStyle } from '@likec4/core'
|
|
3
|
-
import { anyPass, filter, isDefined } from 'remeda'
|
|
3
|
+
import { anyPass, filter, forEach, isDefined, isNot, pipe } from 'remeda'
|
|
4
4
|
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
5
|
|
|
6
6
|
type Predicate<T> = (x: T) => boolean
|
|
@@ -17,31 +17,36 @@ export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
|
|
|
17
17
|
predicates.push(() => true)
|
|
18
18
|
break
|
|
19
19
|
}
|
|
20
|
-
predicates.push(elementExprToPredicate(target))
|
|
20
|
+
predicates.push(elementExprToPredicate(target) as Predicate<ComputedNode>)
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
let styleOverride: ComputedNode['style'] | undefined
|
|
32
|
-
if (isDefined(rule.style.border)) {
|
|
33
|
-
styleOverride = { border: rule.style.border }
|
|
34
|
-
}
|
|
35
|
-
if (isDefined(rule.style.opacity)) {
|
|
36
|
-
styleOverride = { ...styleOverride, opacity: rule.style.opacity }
|
|
37
|
-
}
|
|
38
|
-
if (styleOverride) {
|
|
39
|
-
n.style = {
|
|
40
|
-
...n.style,
|
|
41
|
-
...styleOverride
|
|
22
|
+
pipe(
|
|
23
|
+
nodes,
|
|
24
|
+
filter(isNot(ComputedNode.isNodesGroup)),
|
|
25
|
+
filter(anyPass(predicates)),
|
|
26
|
+
forEach(n => {
|
|
27
|
+
n.shape = rule.style.shape ?? n.shape
|
|
28
|
+
n.color = rule.style.color ?? n.color
|
|
29
|
+
if (isDefined(rule.style.icon)) {
|
|
30
|
+
n.icon = rule.style.icon
|
|
42
31
|
}
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
if (isDefined(rule.notation)) {
|
|
33
|
+
n.notation = rule.notation
|
|
34
|
+
}
|
|
35
|
+
let styleOverride: ComputedNode['style'] | undefined
|
|
36
|
+
if (isDefined(rule.style.border)) {
|
|
37
|
+
styleOverride = { border: rule.style.border }
|
|
38
|
+
}
|
|
39
|
+
if (isDefined(rule.style.opacity)) {
|
|
40
|
+
styleOverride = { ...styleOverride, opacity: rule.style.opacity }
|
|
41
|
+
}
|
|
42
|
+
if (styleOverride) {
|
|
43
|
+
n.style = {
|
|
44
|
+
...n.style,
|
|
45
|
+
...styleOverride
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
)
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
return nodes
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type { ComputedNode, Element, Fqn } from '@likec4/core'
|
|
2
|
-
import {
|
|
1
|
+
import type { ComputedNode, Element, Fqn, NodeId } from '@likec4/core'
|
|
2
|
+
import {
|
|
3
|
+
compareByFqnHierarchically,
|
|
4
|
+
DefaultElementShape,
|
|
5
|
+
DefaultThemeColor,
|
|
6
|
+
ElementKind,
|
|
7
|
+
nonNullable,
|
|
8
|
+
parentFqn
|
|
9
|
+
} from '@likec4/core'
|
|
10
|
+
import { NodesGroup } from '../compute-view/compute'
|
|
3
11
|
|
|
4
12
|
function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, ComputedNode>) {
|
|
5
13
|
let parentNd
|
|
@@ -14,48 +22,92 @@ function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, Comp
|
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
export function buildComputeNodes(elements: Iterable<Element
|
|
25
|
+
export function buildComputeNodes(elements: Iterable<Element>, groups?: NodesGroup[]) {
|
|
26
|
+
const nodesMap = new Map<Fqn, ComputedNode>()
|
|
27
|
+
|
|
28
|
+
const elementToGroup = new Map<Fqn, NodeId>()
|
|
29
|
+
|
|
30
|
+
groups?.forEach(({ id, parent, viewRule, explicits }) => {
|
|
31
|
+
if (parent) {
|
|
32
|
+
nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id)
|
|
33
|
+
}
|
|
34
|
+
nodesMap.set(id, {
|
|
35
|
+
id,
|
|
36
|
+
parent,
|
|
37
|
+
kind: ElementKind.Group,
|
|
38
|
+
title: viewRule.title ?? '',
|
|
39
|
+
color: viewRule.color ?? 'muted',
|
|
40
|
+
shape: 'rectangle',
|
|
41
|
+
children: [],
|
|
42
|
+
inEdges: [],
|
|
43
|
+
outEdges: [],
|
|
44
|
+
level: 0,
|
|
45
|
+
depth: 0,
|
|
46
|
+
description: null,
|
|
47
|
+
technology: null,
|
|
48
|
+
tags: null,
|
|
49
|
+
links: null,
|
|
50
|
+
style: {
|
|
51
|
+
border: viewRule.border ?? 'dashed',
|
|
52
|
+
opacity: viewRule.opacity ?? 0
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
for (const e of explicits) {
|
|
56
|
+
elementToGroup.set(e.id, id)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
18
60
|
return (
|
|
19
61
|
Array.from(elements)
|
|
20
62
|
// Sort from Top to Bottom
|
|
21
63
|
// So we can ensure that parent nodes are created before child nodes
|
|
22
64
|
.sort(compareByFqnHierarchically)
|
|
23
|
-
.reduce((map, { id, color, shape, style, ...el }) => {
|
|
65
|
+
.reduce((map, { id, color, shape, style, kind, title, ...el }) => {
|
|
24
66
|
let parent = parentFqn(id)
|
|
25
67
|
let level = 0
|
|
68
|
+
let parentNd: ComputedNode | undefined
|
|
26
69
|
// Find the first ancestor that is already in the map
|
|
27
70
|
while (parent) {
|
|
28
|
-
|
|
71
|
+
parentNd = map.get(parent)
|
|
29
72
|
if (parentNd) {
|
|
30
|
-
// if parent has no children and we are about to add first one
|
|
31
|
-
// we need to set its depth to 1
|
|
32
|
-
if (parentNd.children.length == 0) {
|
|
33
|
-
parentNd.depth = 1
|
|
34
|
-
// go up the tree and update depth of all parents
|
|
35
|
-
updateDepthOfAncestors(parentNd, map)
|
|
36
|
-
}
|
|
37
|
-
parentNd.children.push(id)
|
|
38
|
-
level = parentNd.level + 1
|
|
39
73
|
break
|
|
40
74
|
}
|
|
41
75
|
parent = parentFqn(parent)
|
|
42
76
|
}
|
|
77
|
+
// If parent is not found in the map, check if it is in a group
|
|
78
|
+
if (!parentNd && elementToGroup.has(id)) {
|
|
79
|
+
parent = elementToGroup.get(id)!
|
|
80
|
+
parentNd = map.get(parent)!
|
|
81
|
+
}
|
|
82
|
+
if (parentNd) {
|
|
83
|
+
// if parent has no children and we are about to add first one
|
|
84
|
+
// we need to set its depth to 1
|
|
85
|
+
if (parentNd.children.length == 0) {
|
|
86
|
+
parentNd.depth = 1
|
|
87
|
+
// go up the tree and update depth of all parents
|
|
88
|
+
updateDepthOfAncestors(parentNd, map)
|
|
89
|
+
}
|
|
90
|
+
parentNd.children.push(id)
|
|
91
|
+
level = parentNd.level + 1
|
|
92
|
+
}
|
|
43
93
|
const node: ComputedNode = {
|
|
44
|
-
...el,
|
|
45
94
|
id,
|
|
46
95
|
parent,
|
|
47
|
-
|
|
96
|
+
kind,
|
|
97
|
+
title,
|
|
48
98
|
color: color ?? DefaultThemeColor,
|
|
49
99
|
shape: shape ?? DefaultElementShape,
|
|
50
100
|
children: [],
|
|
51
101
|
inEdges: [],
|
|
52
102
|
outEdges: [],
|
|
103
|
+
level,
|
|
104
|
+
...el,
|
|
53
105
|
style: {
|
|
54
106
|
...style
|
|
55
107
|
}
|
|
56
108
|
}
|
|
57
109
|
map.set(id, node)
|
|
58
110
|
return map
|
|
59
|
-
},
|
|
111
|
+
}, nodesMap) as ReadonlyMap<Fqn, ComputedNode>
|
|
60
112
|
)
|
|
61
113
|
}
|