@likec4/language-server 1.9.0 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contrib/likec4.tmLanguage.json +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +3 -4
- package/dist/browser.d.mts +3 -4
- package/dist/browser.d.ts +3 -4
- package/dist/browser.mjs +1 -1
- 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 +1 -1
- package/dist/model-graph/index.cjs +1 -1
- package/dist/model-graph/index.mjs +1 -1
- package/dist/node.cjs +1 -1
- package/dist/node.d.cts +3 -4
- package/dist/node.d.mts +3 -4
- package/dist/node.d.ts +3 -4
- package/dist/node.mjs +1 -1
- package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.BFBeyvV8.mjs} +486 -108
- package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.BGy3FJPJ.d.cts} +43 -14
- package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.Bfc-5M8A.cjs} +482 -104
- package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.CbqwHp7Q.mjs} +184 -120
- package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CnVuAxDh.d.ts} +43 -14
- package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.DEK39RmI.d.mts} +43 -14
- package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DJhoJBWh.cjs} +180 -116
- package/package.json +13 -11
- package/src/ast.ts +8 -6
- package/src/formatting/LikeC4Formatter.ts +390 -0
- package/src/formatting/utils.ts +26 -0
- package/src/generated/ast.ts +203 -11
- package/src/generated/grammar.ts +2 -2
- package/src/generated/module.ts +1 -1
- package/src/like-c4.langium +34 -7
- package/src/lsp/CompletionProvider.ts +1 -1
- package/src/lsp/DocumentLinkProvider.ts +27 -15
- package/src/lsp/SemanticTokenProvider.ts +1 -1
- package/src/lsp/index.ts +1 -1
- package/src/model/fqn-index.ts +0 -1
- package/src/model/model-builder.ts +43 -32
- package/src/model/model-parser.ts +43 -21
- package/src/model-graph/compute-view/compute.ts +111 -80
- package/src/model-graph/compute-view/predicates.ts +3 -5
- package/src/model-graph/dynamic-view/compute.ts +96 -60
- package/src/model-graph/utils/buildElementNotations.ts +1 -1
- package/src/model-graph/utils/uniqueTags.test.ts +42 -0
- package/src/model-graph/utils/uniqueTags.ts +19 -0
- package/src/module.ts +6 -9
- package/src/test/testServices.ts +27 -7
- package/src/validation/index.ts +2 -1
- package/src/validation/property-checks.ts +13 -1
- package/src/validation/specification.ts +3 -3
- package/src/view-utils/resolve-relative-paths.ts +14 -17
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
RelationshipKind,
|
|
14
14
|
RelationshipLineType,
|
|
15
15
|
Tag,
|
|
16
|
-
|
|
16
|
+
ViewID,
|
|
17
17
|
ViewRulePredicate
|
|
18
18
|
} from '@likec4/core'
|
|
19
19
|
import {
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
parentFqn,
|
|
31
31
|
whereOperatorAsPredicate
|
|
32
32
|
} from '@likec4/core'
|
|
33
|
-
import {
|
|
33
|
+
import { filter, hasAtLeast, isNonNull, isTruthy, map, omit, only, pipe, reduce, sort, unique } from 'remeda'
|
|
34
34
|
import { calcViewLayoutHash } from '../../view-utils/view-hash'
|
|
35
35
|
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
36
36
|
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
@@ -39,6 +39,7 @@ import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
|
|
|
39
39
|
import { buildComputeNodes } from '../utils/buildComputeNodes'
|
|
40
40
|
import { buildElementNotations } from '../utils/buildElementNotations'
|
|
41
41
|
import { sortNodes } from '../utils/sortNodes'
|
|
42
|
+
import { uniqueTags } from '../utils/uniqueTags'
|
|
42
43
|
import {
|
|
43
44
|
type ElementPredicateFn,
|
|
44
45
|
excludeElementKindOrTag,
|
|
@@ -192,7 +193,7 @@ export class ComputeCtx {
|
|
|
192
193
|
protected computeEdges(): ComputedEdge[] {
|
|
193
194
|
return this.ctxEdges.map((e): ComputedEdge => {
|
|
194
195
|
invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
|
|
195
|
-
const relations = e.relations
|
|
196
|
+
const relations = sort(e.relations, compareRelations)
|
|
196
197
|
const source = e.source.id
|
|
197
198
|
const target = e.target.id
|
|
198
199
|
|
|
@@ -205,70 +206,85 @@ export class ComputeCtx {
|
|
|
205
206
|
relations: relations.map(r => r.id)
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
let relation:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
relation = relations.find(r => r.source === source && r.target === target)
|
|
227
|
-
// relation ??= relations.toReversed().find(r => r.source === source || r.target === target)
|
|
228
|
-
}
|
|
209
|
+
let relation: {
|
|
210
|
+
// TODO refactor with type-fest
|
|
211
|
+
title: string
|
|
212
|
+
description?: string | undefined
|
|
213
|
+
technology?: string | undefined
|
|
214
|
+
kind?: RelationshipKind | undefined
|
|
215
|
+
color?: Color | undefined
|
|
216
|
+
line?: RelationshipLineType | undefined
|
|
217
|
+
head?: RelationshipArrowType | undefined
|
|
218
|
+
tail?: RelationshipArrowType | undefined
|
|
219
|
+
tags?: NonEmptyArray<Tag>
|
|
220
|
+
navigateTo?: ViewID | undefined
|
|
221
|
+
} | undefined
|
|
222
|
+
relation = only(relations) ?? pipe(
|
|
223
|
+
relations,
|
|
224
|
+
filter(r => r.source === source && r.target === target),
|
|
225
|
+
only()
|
|
226
|
+
)
|
|
229
227
|
|
|
230
228
|
// This edge represents mutliple relations
|
|
231
229
|
// We use label if only it is the same for all relations
|
|
232
230
|
if (!relation) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
231
|
+
const allprops = pipe(
|
|
232
|
+
relations,
|
|
233
|
+
reduce((acc, r) => {
|
|
234
|
+
if (isTruthy(r.title) && !acc.title.includes(r.title)) {
|
|
235
|
+
acc.title.push(r.title)
|
|
236
|
+
}
|
|
237
|
+
if (isTruthy(r.description) && !acc.description.includes(r.description)) {
|
|
238
|
+
acc.description.push(r.description)
|
|
239
|
+
}
|
|
240
|
+
if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
|
|
241
|
+
acc.technology.push(r.technology)
|
|
242
|
+
}
|
|
243
|
+
if (isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
|
|
244
|
+
acc.kind.push(r.kind)
|
|
245
|
+
}
|
|
246
|
+
if (isTruthy(r.color) && !acc.color.includes(r.color)) {
|
|
247
|
+
acc.color.push(r.color)
|
|
248
|
+
}
|
|
249
|
+
if (isTruthy(r.line) && !acc.line.includes(r.line)) {
|
|
250
|
+
acc.line.push(r.line)
|
|
251
|
+
}
|
|
252
|
+
if (isTruthy(r.head) && !acc.head.includes(r.head)) {
|
|
253
|
+
acc.head.push(r.head)
|
|
254
|
+
}
|
|
255
|
+
if (isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
|
|
256
|
+
acc.tail.push(r.tail)
|
|
257
|
+
}
|
|
258
|
+
if (isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
|
|
259
|
+
acc.navigateTo.push(r.navigateTo)
|
|
260
|
+
}
|
|
261
|
+
return acc
|
|
262
|
+
}, {
|
|
263
|
+
title: [] as string[],
|
|
264
|
+
description: [] as string[],
|
|
265
|
+
technology: [] as string[],
|
|
266
|
+
kind: [] as RelationshipKind[],
|
|
267
|
+
head: [] as RelationshipArrowType[],
|
|
268
|
+
tail: [] as RelationshipArrowType[],
|
|
269
|
+
color: [] as Color[],
|
|
270
|
+
line: [] as RelationshipLineType[],
|
|
271
|
+
navigateTo: [] as ViewID[]
|
|
272
|
+
})
|
|
273
|
+
)
|
|
274
|
+
relation = {
|
|
275
|
+
title: only(allprops.title) ?? '[...]',
|
|
276
|
+
description: only(allprops.description),
|
|
277
|
+
technology: only(allprops.technology),
|
|
278
|
+
kind: only(allprops.kind),
|
|
279
|
+
head: only(allprops.head),
|
|
280
|
+
tail: only(allprops.tail),
|
|
281
|
+
color: only(allprops.color),
|
|
282
|
+
line: only(allprops.line),
|
|
283
|
+
navigateTo: only(allprops.navigateTo)
|
|
284
|
+
}
|
|
269
285
|
}
|
|
270
286
|
|
|
271
|
-
const tags =
|
|
287
|
+
const tags = uniqueTags(relations)
|
|
272
288
|
|
|
273
289
|
return Object.assign(
|
|
274
290
|
edge,
|
|
@@ -280,7 +296,8 @@ export class ComputeCtx {
|
|
|
280
296
|
relation.line && { line: relation.line },
|
|
281
297
|
relation.head && { head: relation.head },
|
|
282
298
|
relation.tail && { tail: relation.tail },
|
|
283
|
-
|
|
299
|
+
relation.navigateTo && { navigateTo: relation.navigateTo },
|
|
300
|
+
tags && { tags }
|
|
284
301
|
)
|
|
285
302
|
})
|
|
286
303
|
}
|
|
@@ -349,29 +366,40 @@ export class ComputeCtx {
|
|
|
349
366
|
}
|
|
350
367
|
}
|
|
351
368
|
|
|
352
|
-
protected excludeImplicit(...excludes: Element[]) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
369
|
+
// protected excludeImplicit(...excludes: Element[]) {
|
|
370
|
+
// for (const el of excludes) {
|
|
371
|
+
// this.implicits.delete(el)
|
|
372
|
+
// }
|
|
373
|
+
// }
|
|
357
374
|
|
|
358
375
|
protected excludeRelation(...relations: Relation[]) {
|
|
376
|
+
if (relations.length === 0) {
|
|
377
|
+
return
|
|
378
|
+
}
|
|
359
379
|
const excludedImplicits = new Set<Element>()
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
380
|
+
const ctxEdges = pipe(
|
|
381
|
+
this.ctxEdges,
|
|
382
|
+
map(edge => {
|
|
383
|
+
const edgerelations = edge.relations.filter(r => !relations.includes(r))
|
|
384
|
+
if (edgerelations.length === 0) {
|
|
364
385
|
excludedImplicits.add(edge.source)
|
|
365
386
|
excludedImplicits.add(edge.target)
|
|
366
|
-
|
|
367
|
-
continue
|
|
387
|
+
return null
|
|
368
388
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
389
|
+
if (edgerelations.length !== edge.relations.length) {
|
|
390
|
+
return {
|
|
391
|
+
...edge,
|
|
392
|
+
relations: edgerelations
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return edge
|
|
396
|
+
}),
|
|
397
|
+
filter(isNonNull)
|
|
398
|
+
)
|
|
372
399
|
if (excludedImplicits.size === 0) {
|
|
373
400
|
return
|
|
374
401
|
}
|
|
402
|
+
this.ctxEdges = ctxEdges
|
|
375
403
|
const remaining = this.includedElements
|
|
376
404
|
if (remaining.size === 0) {
|
|
377
405
|
this.implicits.clear()
|
|
@@ -541,9 +569,12 @@ export class ComputeCtx {
|
|
|
541
569
|
}
|
|
542
570
|
|
|
543
571
|
protected getEdgeLabel(
|
|
544
|
-
relation: {
|
|
545
|
-
|
|
546
|
-
|
|
572
|
+
relation: {
|
|
573
|
+
title: string
|
|
574
|
+
technology?: string | undefined
|
|
575
|
+
}
|
|
576
|
+
) {
|
|
577
|
+
const labelParts: string[] = []
|
|
547
578
|
|
|
548
579
|
if (isTruthy(relation.title)) {
|
|
549
580
|
labelParts.push(relation.title)
|
|
@@ -553,6 +584,6 @@ export class ComputeCtx {
|
|
|
553
584
|
labelParts.push(`[${relation.technology}]`)
|
|
554
585
|
}
|
|
555
586
|
|
|
556
|
-
return labelParts.length > 0
|
|
587
|
+
return labelParts.length > 0 ? { label: labelParts.join('\n') } : {}
|
|
557
588
|
}
|
|
558
589
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Element, Relation } from '@likec4/core'
|
|
2
2
|
import { Expr, isAncestor, nonexhaustive, parentFqn } from '@likec4/core'
|
|
3
|
-
import { allPass, filter as remedaFilter, flatMap, isNullish as isNil, map, pipe } from 'remeda'
|
|
3
|
+
import { allPass, filter as remedaFilter, flatMap, isNullish as isNil, map, pipe, unique } from 'remeda'
|
|
4
4
|
import { elementExprToPredicate } from '../utils/elementExpressionToPredicate'
|
|
5
5
|
import type { ComputeCtx } from './compute'
|
|
6
6
|
|
|
@@ -318,13 +318,11 @@ const filterEdges = (edges: ReadonlyArray<ComputeCtx.Edge>, where?: RelationPred
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
const filterRelations = (edges: ComputeCtx.Edge[], where?: RelationPredicateFn) => {
|
|
321
|
-
if (!where) {
|
|
322
|
-
return edges.flatMap(e => e.relations)
|
|
323
|
-
}
|
|
324
321
|
return pipe(
|
|
325
322
|
edges,
|
|
326
323
|
flatMap(e => e.relations),
|
|
327
|
-
remedaFilter(where)
|
|
324
|
+
where ? remedaFilter(where) : Identity,
|
|
325
|
+
unique()
|
|
328
326
|
)
|
|
329
327
|
}
|
|
330
328
|
|
|
@@ -3,13 +3,14 @@ import type {
|
|
|
3
3
|
ComputedDynamicView,
|
|
4
4
|
ComputedEdge,
|
|
5
5
|
DynamicView,
|
|
6
|
+
DynamicViewStep,
|
|
6
7
|
Element,
|
|
7
8
|
NonEmptyArray,
|
|
8
9
|
RelationID,
|
|
9
10
|
RelationshipArrowType,
|
|
10
11
|
RelationshipLineType,
|
|
11
12
|
Tag,
|
|
12
|
-
|
|
13
|
+
ViewID
|
|
13
14
|
} from '@likec4/core'
|
|
14
15
|
import {
|
|
15
16
|
ancestorsFqn,
|
|
@@ -18,12 +19,13 @@ import {
|
|
|
18
19
|
DefaultLineStyle,
|
|
19
20
|
DefaultRelationshipColor,
|
|
20
21
|
isDynamicViewIncludeRule,
|
|
22
|
+
isDynamicViewParallelSteps,
|
|
21
23
|
isViewRuleAutoLayout,
|
|
22
24
|
nonNullable,
|
|
23
25
|
parentFqn,
|
|
24
26
|
StepEdgeId
|
|
25
27
|
} from '@likec4/core'
|
|
26
|
-
import { hasAtLeast, isTruthy, map, omit, unique } from 'remeda'
|
|
28
|
+
import { filter, flatMap, hasAtLeast, isTruthy, map, omit, only, pipe, unique } from 'remeda'
|
|
27
29
|
import { calcViewLayoutHash } from '../../view-utils/view-hash'
|
|
28
30
|
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
29
31
|
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
@@ -34,6 +36,7 @@ import { elementExprToPredicate } from '../utils/elementExpressionToPredicate'
|
|
|
34
36
|
|
|
35
37
|
export namespace DynamicViewComputeCtx {
|
|
36
38
|
export interface Step {
|
|
39
|
+
id: StepEdgeId
|
|
37
40
|
source: Element
|
|
38
41
|
target: Element
|
|
39
42
|
title: string | null
|
|
@@ -45,6 +48,7 @@ export namespace DynamicViewComputeCtx {
|
|
|
45
48
|
tail?: RelationshipArrowType
|
|
46
49
|
relations: RelationID[]
|
|
47
50
|
isBackward: boolean
|
|
51
|
+
navigateTo?: ViewID
|
|
48
52
|
tags?: NonEmptyArray<Tag>
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -63,6 +67,47 @@ export class DynamicViewComputeCtx {
|
|
|
63
67
|
protected graph: LikeC4ModelGraph
|
|
64
68
|
) {}
|
|
65
69
|
|
|
70
|
+
private addStep(
|
|
71
|
+
{
|
|
72
|
+
source: stepSource,
|
|
73
|
+
target: stepTarget,
|
|
74
|
+
title: stepTitle,
|
|
75
|
+
isBackward,
|
|
76
|
+
navigateTo: stepNavigateTo,
|
|
77
|
+
...step
|
|
78
|
+
}: DynamicViewStep,
|
|
79
|
+
index: number,
|
|
80
|
+
parent?: number
|
|
81
|
+
) {
|
|
82
|
+
const id = parent ? StepEdgeId(parent, index) : StepEdgeId(index)
|
|
83
|
+
const source = this.graph.element(stepSource)
|
|
84
|
+
const target = this.graph.element(stepTarget)
|
|
85
|
+
|
|
86
|
+
this.explicits.add(source)
|
|
87
|
+
this.explicits.add(target)
|
|
88
|
+
|
|
89
|
+
const {
|
|
90
|
+
title,
|
|
91
|
+
relations,
|
|
92
|
+
tags,
|
|
93
|
+
navigateTo: derivedNavigateTo
|
|
94
|
+
} = this.findRelations(source, target)
|
|
95
|
+
|
|
96
|
+
const navigateTo = isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo
|
|
97
|
+
|
|
98
|
+
this.steps.push({
|
|
99
|
+
id,
|
|
100
|
+
...step,
|
|
101
|
+
source,
|
|
102
|
+
target,
|
|
103
|
+
title: stepTitle ?? title,
|
|
104
|
+
relations: relations ?? [],
|
|
105
|
+
isBackward: isBackward ?? false,
|
|
106
|
+
...(navigateTo ? { navigateTo } : {}),
|
|
107
|
+
...(tags ? { tags } : {})
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
66
111
|
protected compute(): ComputedDynamicView {
|
|
67
112
|
const {
|
|
68
113
|
docUri: _docUri, // exclude docUri
|
|
@@ -71,32 +116,21 @@ export class DynamicViewComputeCtx {
|
|
|
71
116
|
...view
|
|
72
117
|
} = this.view
|
|
73
118
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const { title, relations, tags } = this.findRelations(source, target)
|
|
90
|
-
|
|
91
|
-
this.steps.push({
|
|
92
|
-
...step,
|
|
93
|
-
source,
|
|
94
|
-
target,
|
|
95
|
-
title: isTruthy(stepTitle) ? stepTitle : title,
|
|
96
|
-
relations: relations ?? [],
|
|
97
|
-
isBackward: isBackward ?? false,
|
|
98
|
-
...(tags ? { tags } : {})
|
|
99
|
-
})
|
|
119
|
+
let stepNum = 1
|
|
120
|
+
for (const step of viewSteps) {
|
|
121
|
+
if (isDynamicViewParallelSteps(step)) {
|
|
122
|
+
if (step.__parallel.length === 0) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
if (step.__parallel.length === 1) {
|
|
126
|
+
this.addStep(step.__parallel[0]!, stepNum)
|
|
127
|
+
} else {
|
|
128
|
+
step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum))
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
this.addStep(step, stepNum)
|
|
132
|
+
}
|
|
133
|
+
stepNum++
|
|
100
134
|
}
|
|
101
135
|
|
|
102
136
|
for (const rule of rules) {
|
|
@@ -111,12 +145,10 @@ export class DynamicViewComputeCtx {
|
|
|
111
145
|
const elements = [...this.explicits]
|
|
112
146
|
const nodesMap = buildComputeNodes(elements)
|
|
113
147
|
|
|
114
|
-
const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }
|
|
148
|
+
const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
|
|
115
149
|
const sourceNode = nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`)
|
|
116
150
|
const targetNode = nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`)
|
|
117
|
-
const stepNum = index + 1
|
|
118
151
|
const edge: ComputedEdge = {
|
|
119
|
-
id: StepEdgeId(stepNum),
|
|
120
152
|
parent: commonAncestor(source.id, target.id),
|
|
121
153
|
source: source.id,
|
|
122
154
|
target: target.id,
|
|
@@ -183,49 +215,53 @@ export class DynamicViewComputeCtx {
|
|
|
183
215
|
title: string | null
|
|
184
216
|
tags: NonEmptyArray<Tag> | null
|
|
185
217
|
relations: NonEmptyArray<RelationID> | null
|
|
218
|
+
navigateTo: ViewID | null
|
|
186
219
|
} {
|
|
187
220
|
const relationships = unique(this.graph.edgesBetween(source, target).flatMap(e => e.relations))
|
|
188
|
-
const alltags = unique(relationships.flatMap(r => r.tags ?? []))
|
|
189
|
-
const tags = hasAtLeast(alltags, 1) ? alltags : null
|
|
190
|
-
|
|
191
|
-
const relations = hasAtLeast(relationships, 1) ? map(relationships, r => r.id) : null
|
|
192
221
|
if (relationships.length === 0) {
|
|
193
222
|
return {
|
|
194
223
|
title: null,
|
|
195
|
-
tags,
|
|
196
|
-
relations
|
|
224
|
+
tags: null,
|
|
225
|
+
relations: null,
|
|
226
|
+
navigateTo: null
|
|
197
227
|
}
|
|
198
228
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
229
|
+
const alltags = pipe(
|
|
230
|
+
relationships,
|
|
231
|
+
flatMap(r => r.tags),
|
|
232
|
+
filter(isTruthy),
|
|
233
|
+
unique()
|
|
234
|
+
)
|
|
235
|
+
const tags = hasAtLeast(alltags, 1) ? alltags : null
|
|
236
|
+
const relations = hasAtLeast(relationships, 1) ? map(relationships, r => r.id) : null
|
|
205
237
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
title: relation.title,
|
|
209
|
-
tags,
|
|
210
|
-
relations
|
|
211
|
-
}
|
|
212
|
-
}
|
|
238
|
+
// Most closest relation
|
|
239
|
+
const relation = only(relationships) || relationships.find(r => r.source === source.id && r.target === target.id)
|
|
213
240
|
|
|
214
241
|
// This edge represents mutliple relations
|
|
215
242
|
// We use label if only it is the same for all relations
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
243
|
+
const title = isTruthy(relation?.title) ? relation.title : pipe(
|
|
244
|
+
relationships,
|
|
245
|
+
map(r => r.title),
|
|
246
|
+
filter(isTruthy),
|
|
247
|
+
unique(),
|
|
248
|
+
only()
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : pipe(
|
|
252
|
+
relationships,
|
|
253
|
+
map(r => r.navigateTo),
|
|
254
|
+
filter(isTruthy),
|
|
255
|
+
filter(v => v !== this.view.id),
|
|
256
|
+
unique(),
|
|
257
|
+
only()
|
|
258
|
+
)
|
|
224
259
|
|
|
225
260
|
return {
|
|
226
|
-
title: null,
|
|
261
|
+
title: title ?? null,
|
|
227
262
|
tags,
|
|
228
|
-
relations
|
|
263
|
+
relations,
|
|
264
|
+
navigateTo: navigateTo ?? null
|
|
229
265
|
}
|
|
230
266
|
}
|
|
231
267
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { compareNatural } from '@likec4/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { uniqueTags } from './uniqueTags'
|
|
4
|
+
|
|
5
|
+
describe('uniqueTags function', () => {
|
|
6
|
+
it('returns unique tags from an array of elements', () => {
|
|
7
|
+
const input = [
|
|
8
|
+
{ tags: ['tag1', 'tag2', 'tag3'] },
|
|
9
|
+
{ tags: ['tag2', 'tag3', 'tag4'] },
|
|
10
|
+
{ tags: ['tag3', 'tag4', 'tag5'] }
|
|
11
|
+
] as const
|
|
12
|
+
const result = uniqueTags(input)
|
|
13
|
+
expect(result).toEqual(['tag1', 'tag2', 'tag3', 'tag4', 'tag5'])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should return unique tags naturally sorted', () => {
|
|
17
|
+
const input = [
|
|
18
|
+
{ tags: ['tag1', 'tag20', 'tag30'] },
|
|
19
|
+
{ tags: ['tag2', 'tag23', 'tag34'] },
|
|
20
|
+
{ tags: ['tag3'] }
|
|
21
|
+
] as const
|
|
22
|
+
const result = uniqueTags(input)
|
|
23
|
+
expect(result).toEqual([
|
|
24
|
+
'tag1',
|
|
25
|
+
'tag2',
|
|
26
|
+
'tag3',
|
|
27
|
+
'tag20',
|
|
28
|
+
'tag23',
|
|
29
|
+
'tag30',
|
|
30
|
+
'tag34'
|
|
31
|
+
].sort(compareNatural))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('returns null if the tags array is null', () => {
|
|
35
|
+
const input = [
|
|
36
|
+
{ tags: null },
|
|
37
|
+
{}
|
|
38
|
+
]
|
|
39
|
+
const result = uniqueTags(input)
|
|
40
|
+
expect(result).toBeNull()
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { compareNatural, hasAtLeast, type NonEmptyReadonlyArray, type Tag } from '@likec4/core'
|
|
2
|
+
import { flatMap, pipe, sort, unique } from 'remeda'
|
|
3
|
+
import type { LiteralUnion } from 'type-fest'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts unique tags from an array of elements.
|
|
7
|
+
* and sort in natural order; returns null if no tags are present.
|
|
8
|
+
*/
|
|
9
|
+
export function uniqueTags<T extends { tags?: NonEmptyReadonlyArray<LiteralUnion<Tag, string>> | null }>(
|
|
10
|
+
elements: ReadonlyArray<T>
|
|
11
|
+
) {
|
|
12
|
+
const tags = pipe(
|
|
13
|
+
elements,
|
|
14
|
+
flatMap(e => e.tags ?? []),
|
|
15
|
+
unique(),
|
|
16
|
+
sort(compareNatural)
|
|
17
|
+
)
|
|
18
|
+
return hasAtLeast(tags, 1) ? tags : null
|
|
19
|
+
}
|
package/src/module.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
LikeC4DocumentLinkProvider,
|
|
18
18
|
LikeC4DocumentSymbolProvider,
|
|
19
19
|
LikeC4HoverProvider,
|
|
20
|
-
LikeC4RenameProvider,
|
|
21
20
|
LikeC4SemanticTokenProvider
|
|
22
21
|
} from './lsp'
|
|
23
22
|
import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model'
|
|
@@ -26,6 +25,7 @@ import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references'
|
|
|
26
25
|
import { Rpc } from './Rpc'
|
|
27
26
|
import { LikeC4WorkspaceManager, NodeKindProvider, WorkspaceSymbolProvider } from './shared'
|
|
28
27
|
import { registerValidationChecks } from './validation'
|
|
28
|
+
import { LikeC4Formatter } from './formatting/LikeC4Formatter'
|
|
29
29
|
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
31
|
type Constructor<T, Arguments extends unknown[] = any[]> = new(...arguments_: Arguments) => T
|
|
@@ -70,7 +70,7 @@ export interface LikeC4AddedServices {
|
|
|
70
70
|
ModelChanges: LikeC4ModelChanges
|
|
71
71
|
}
|
|
72
72
|
lsp: {
|
|
73
|
-
RenameProvider: LikeC4RenameProvider
|
|
73
|
+
// RenameProvider: LikeC4RenameProvider
|
|
74
74
|
CompletionProvider: LikeC4CompletionProvider
|
|
75
75
|
DocumentHighlightProvider: LikeC4DocumentHighlightProvider
|
|
76
76
|
DocumentSymbolProvider: LikeC4DocumentSymbolProvider
|
|
@@ -103,14 +103,15 @@ export const LikeC4Module: Module<LikeC4Services, PartialLangiumServices & LikeC
|
|
|
103
103
|
ModelLocator: bind(LikeC4ModelLocator)
|
|
104
104
|
},
|
|
105
105
|
lsp: {
|
|
106
|
-
RenameProvider: bind(LikeC4RenameProvider),
|
|
106
|
+
// RenameProvider: bind(LikeC4RenameProvider),
|
|
107
107
|
CompletionProvider: bind(LikeC4CompletionProvider),
|
|
108
108
|
DocumentHighlightProvider: bind(LikeC4DocumentHighlightProvider),
|
|
109
109
|
DocumentSymbolProvider: bind(LikeC4DocumentSymbolProvider),
|
|
110
110
|
SemanticTokenProvider: bind(LikeC4SemanticTokenProvider),
|
|
111
111
|
HoverProvider: bind(LikeC4HoverProvider),
|
|
112
112
|
CodeLensProvider: bind(LikeC4CodeLensProvider),
|
|
113
|
-
DocumentLinkProvider: bind(LikeC4DocumentLinkProvider)
|
|
113
|
+
DocumentLinkProvider: bind(LikeC4DocumentLinkProvider),
|
|
114
|
+
Formatter: bind(LikeC4Formatter)
|
|
114
115
|
},
|
|
115
116
|
references: {
|
|
116
117
|
ScopeComputation: bind(LikeC4ScopeComputation),
|
|
@@ -177,11 +178,7 @@ export function createLanguageServices(context: LanguageServicesContext = {}): {
|
|
|
177
178
|
shared.ServiceRegistry.register(likec4)
|
|
178
179
|
registerValidationChecks(likec4)
|
|
179
180
|
|
|
180
|
-
if (
|
|
181
|
-
// We don't run inside a language server
|
|
182
|
-
// Therefore, initialize the configuration provider instantly
|
|
183
|
-
shared.workspace.ConfigurationProvider.initialized({})
|
|
184
|
-
} else {
|
|
181
|
+
if (context.connection) {
|
|
185
182
|
likec4.Rpc.init()
|
|
186
183
|
}
|
|
187
184
|
|