@likec4/language-server 1.9.0 → 1.10.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 +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- 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 +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.mjs +1 -1
- package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.CjFzaJwI.d.cts} +42 -13
- package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CtKHXJDD.d.ts} +42 -13
- package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.D-84I33F.d.mts} +42 -13
- package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.DBJJUUgF.mjs} +485 -108
- package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.DtBRb9os.mjs} +166 -116
- package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DwyCJvXm.cjs} +164 -114
- package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.JWkqVjGv.cjs} +481 -104
- package/package.json +6 -5
- package/src/ast.ts +8 -6
- package/src/formatting/LikeC4Formatter.ts +388 -0
- package/src/formatting/utils.ts +26 -0
- package/src/generated/ast.ts +104 -10
- package/src/generated/grammar.ts +1 -1
- package/src/like-c4.langium +34 -7
- 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 +104 -78
- 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/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, flatMap, 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'
|
|
@@ -192,7 +192,7 @@ export class ComputeCtx {
|
|
|
192
192
|
protected computeEdges(): ComputedEdge[] {
|
|
193
193
|
return this.ctxEdges.map((e): ComputedEdge => {
|
|
194
194
|
invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
|
|
195
|
-
const relations = e.relations
|
|
195
|
+
const relations = sort(e.relations, compareRelations)
|
|
196
196
|
const source = e.source.id
|
|
197
197
|
const target = e.target.id
|
|
198
198
|
|
|
@@ -205,67 +205,78 @@ export class ComputeCtx {
|
|
|
205
205
|
relations: relations.map(r => r.id)
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
let relation:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
| undefined
|
|
223
|
-
if (relations.length === 1) {
|
|
224
|
-
relation = relations[0]
|
|
225
|
-
} else {
|
|
226
|
-
relation = relations.find(r => r.source === source && r.target === target)
|
|
227
|
-
// relation ??= relations.toReversed().find(r => r.source === source || r.target === target)
|
|
228
|
-
}
|
|
208
|
+
let relation: {
|
|
209
|
+
// TODO refactor with type-fest
|
|
210
|
+
title: string
|
|
211
|
+
description?: string | undefined
|
|
212
|
+
technology?: string | undefined
|
|
213
|
+
kind?: RelationshipKind | undefined
|
|
214
|
+
color?: Color | undefined
|
|
215
|
+
line?: RelationshipLineType | undefined
|
|
216
|
+
head?: RelationshipArrowType | undefined
|
|
217
|
+
tail?: RelationshipArrowType | undefined
|
|
218
|
+
tags?: NonEmptyArray<Tag>
|
|
219
|
+
navigateTo?: ViewID | undefined
|
|
220
|
+
} | undefined
|
|
221
|
+
relation = relations.length === 1 ? relations[0] : relations.find(r => r.source === source && r.target === target)
|
|
229
222
|
|
|
230
223
|
// This edge represents mutliple relations
|
|
231
224
|
// We use label if only it is the same for all relations
|
|
232
225
|
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
|
-
|
|
226
|
+
const allprops = pipe(
|
|
227
|
+
relations,
|
|
228
|
+
reduce((acc, r) => {
|
|
229
|
+
if (isTruthy(r.title) && !acc.title.includes(r.title)) {
|
|
230
|
+
acc.title.push(r.title)
|
|
231
|
+
}
|
|
232
|
+
if (isTruthy(r.description) && !acc.description.includes(r.description)) {
|
|
233
|
+
acc.description.push(r.description)
|
|
234
|
+
}
|
|
235
|
+
if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
|
|
236
|
+
acc.technology.push(r.technology)
|
|
237
|
+
}
|
|
238
|
+
if (isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
|
|
239
|
+
acc.kind.push(r.kind)
|
|
240
|
+
}
|
|
241
|
+
if (isTruthy(r.color) && !acc.color.includes(r.color)) {
|
|
242
|
+
acc.color.push(r.color)
|
|
243
|
+
}
|
|
244
|
+
if (isTruthy(r.line) && !acc.line.includes(r.line)) {
|
|
245
|
+
acc.line.push(r.line)
|
|
246
|
+
}
|
|
247
|
+
if (isTruthy(r.head) && !acc.head.includes(r.head)) {
|
|
248
|
+
acc.head.push(r.head)
|
|
249
|
+
}
|
|
250
|
+
if (isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
|
|
251
|
+
acc.tail.push(r.tail)
|
|
252
|
+
}
|
|
253
|
+
if (isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
|
|
254
|
+
acc.navigateTo.push(r.navigateTo)
|
|
255
|
+
}
|
|
256
|
+
return acc
|
|
257
|
+
}, {
|
|
258
|
+
title: [] as string[],
|
|
259
|
+
description: [] as string[],
|
|
260
|
+
technology: [] as string[],
|
|
261
|
+
kind: [] as RelationshipKind[],
|
|
262
|
+
head: [] as RelationshipArrowType[],
|
|
263
|
+
tail: [] as RelationshipArrowType[],
|
|
264
|
+
color: [] as Color[],
|
|
265
|
+
line: [] as RelationshipLineType[],
|
|
266
|
+
navigateTo: [] as ViewID[]
|
|
267
|
+
})
|
|
268
|
+
)
|
|
269
|
+
relation = {
|
|
270
|
+
title: only(allprops.title) ?? '[...]',
|
|
271
|
+
description: only(allprops.description),
|
|
272
|
+
technology: only(allprops.technology),
|
|
273
|
+
kind: only(allprops.kind),
|
|
274
|
+
head: only(allprops.head),
|
|
275
|
+
tail: only(allprops.tail),
|
|
276
|
+
color: only(allprops.color),
|
|
277
|
+
line: only(allprops.line),
|
|
278
|
+
navigateTo: only(allprops.navigateTo)
|
|
279
|
+
}
|
|
269
280
|
}
|
|
270
281
|
|
|
271
282
|
const tags = unique(flatMap(relations, r => r.tags ?? []))
|
|
@@ -280,6 +291,7 @@ export class ComputeCtx {
|
|
|
280
291
|
relation.line && { line: relation.line },
|
|
281
292
|
relation.head && { head: relation.head },
|
|
282
293
|
relation.tail && { tail: relation.tail },
|
|
294
|
+
relation.navigateTo && { navigateTo: relation.navigateTo },
|
|
283
295
|
hasAtLeast(tags, 1) && { tags }
|
|
284
296
|
)
|
|
285
297
|
})
|
|
@@ -349,29 +361,40 @@ export class ComputeCtx {
|
|
|
349
361
|
}
|
|
350
362
|
}
|
|
351
363
|
|
|
352
|
-
protected excludeImplicit(...excludes: Element[]) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
364
|
+
// protected excludeImplicit(...excludes: Element[]) {
|
|
365
|
+
// for (const el of excludes) {
|
|
366
|
+
// this.implicits.delete(el)
|
|
367
|
+
// }
|
|
368
|
+
// }
|
|
357
369
|
|
|
358
370
|
protected excludeRelation(...relations: Relation[]) {
|
|
371
|
+
if (relations.length === 0) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
359
374
|
const excludedImplicits = new Set<Element>()
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
375
|
+
const ctxEdges = pipe(
|
|
376
|
+
this.ctxEdges,
|
|
377
|
+
map(edge => {
|
|
378
|
+
const edgerelations = edge.relations.filter(r => !relations.includes(r))
|
|
379
|
+
if (edgerelations.length === 0) {
|
|
364
380
|
excludedImplicits.add(edge.source)
|
|
365
381
|
excludedImplicits.add(edge.target)
|
|
366
|
-
|
|
367
|
-
continue
|
|
382
|
+
return null
|
|
368
383
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
384
|
+
if (edgerelations.length !== edge.relations.length) {
|
|
385
|
+
return {
|
|
386
|
+
...edge,
|
|
387
|
+
relations: edgerelations
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return edge
|
|
391
|
+
}),
|
|
392
|
+
filter(isNonNull)
|
|
393
|
+
)
|
|
372
394
|
if (excludedImplicits.size === 0) {
|
|
373
395
|
return
|
|
374
396
|
}
|
|
397
|
+
this.ctxEdges = ctxEdges
|
|
375
398
|
const remaining = this.includedElements
|
|
376
399
|
if (remaining.size === 0) {
|
|
377
400
|
this.implicits.clear()
|
|
@@ -541,9 +564,12 @@ export class ComputeCtx {
|
|
|
541
564
|
}
|
|
542
565
|
|
|
543
566
|
protected getEdgeLabel(
|
|
544
|
-
relation: {
|
|
545
|
-
|
|
546
|
-
|
|
567
|
+
relation: {
|
|
568
|
+
title: string
|
|
569
|
+
technology?: string | undefined
|
|
570
|
+
}
|
|
571
|
+
) {
|
|
572
|
+
const labelParts: string[] = []
|
|
547
573
|
|
|
548
574
|
if (isTruthy(relation.title)) {
|
|
549
575
|
labelParts.push(relation.title)
|
|
@@ -553,6 +579,6 @@ export class ComputeCtx {
|
|
|
553
579
|
labelParts.push(`[${relation.technology}]`)
|
|
554
580
|
}
|
|
555
581
|
|
|
556
|
-
return labelParts.length > 0
|
|
582
|
+
return labelParts.length > 0 ? { label: labelParts.join('\n') } : {}
|
|
557
583
|
}
|
|
558
584
|
}
|
|
@@ -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
|
}
|
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
|
|
package/src/test/testServices.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocumentState, EmptyFileSystem } from 'langium'
|
|
1
|
+
import { DocumentState, EmptyFileSystem, TextDocument } from 'langium'
|
|
2
2
|
import * as assert from 'node:assert'
|
|
3
3
|
import stripIndent from 'strip-indent'
|
|
4
4
|
import { type Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'
|
|
@@ -13,6 +13,7 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
13
13
|
const documentBuilder = services.shared.workspace.DocumentBuilder
|
|
14
14
|
const modelBuilder = services.likec4.ModelBuilder
|
|
15
15
|
const workspaceUri = URI.parse(workspace)
|
|
16
|
+
const formatter = services.lsp.Formatter
|
|
16
17
|
const workspaceFolder = {
|
|
17
18
|
name: 'test',
|
|
18
19
|
uri: workspaceUri.toString()
|
|
@@ -27,11 +28,13 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
27
28
|
return
|
|
28
29
|
}
|
|
29
30
|
isInitialized = true
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
services.shared.workspace.WorkspaceManager.initialize({
|
|
32
|
+
capabilities: {},
|
|
33
|
+
processId: null,
|
|
34
|
+
rootUri: null,
|
|
35
|
+
workspaceFolders: [workspaceFolder]
|
|
34
36
|
})
|
|
37
|
+
await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder])
|
|
35
38
|
})
|
|
36
39
|
}
|
|
37
40
|
const docUri = Utils.resolvePath(
|
|
@@ -66,12 +69,28 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
const format = async (input: string | LikeC4LangiumDocument, uri?: string) => {
|
|
73
|
+
const document = typeof input === 'string' ? await parse(input, uri) : input
|
|
74
|
+
await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
|
|
75
|
+
await documentBuilder.build([document], { validation: false })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const edits = await services.lsp.Formatter?.formatDocument(
|
|
79
|
+
document,
|
|
80
|
+
{
|
|
81
|
+
options: {tabSize: 2, insertSpaces: true},
|
|
82
|
+
textDocument: { uri: document.uri.toString() }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return TextDocument.applyEdits(document.textDocument, edits ?? []);
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
type ValidateAllResult = {
|
|
70
89
|
diagnostics: Diagnostic[]
|
|
71
90
|
errors: string[]
|
|
72
91
|
warnings: string[]
|
|
73
92
|
}
|
|
74
|
-
|
|
93
|
+
|
|
75
94
|
const validateAll = async () => {
|
|
76
95
|
await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
|
|
77
96
|
const docs = langiumDocuments.all.toArray()
|
|
@@ -110,7 +129,8 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
110
129
|
validate,
|
|
111
130
|
validateAll,
|
|
112
131
|
buildModel,
|
|
113
|
-
resetState
|
|
132
|
+
resetState,
|
|
133
|
+
format
|
|
114
134
|
}
|
|
115
135
|
}
|
|
116
136
|
|
package/src/validation/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { LikeC4Services } from '../module'
|
|
|
4
4
|
import { dynamicViewRulePredicate } from './dynamic-view-rule'
|
|
5
5
|
import { dynamicViewStep } from './dynamic-view-step'
|
|
6
6
|
import { elementChecks } from './element'
|
|
7
|
-
import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
|
|
7
|
+
import { iconPropertyRuleChecks, notesPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
|
|
8
8
|
import { relationBodyChecks, relationChecks } from './relation'
|
|
9
9
|
import {
|
|
10
10
|
elementKindChecks,
|
|
@@ -27,6 +27,7 @@ export function registerValidationChecks(services: LikeC4Services) {
|
|
|
27
27
|
logger.info('registerValidationChecks')
|
|
28
28
|
const registry = services.validation.ValidationRegistry
|
|
29
29
|
registry.register<ast.LikeC4AstType>({
|
|
30
|
+
NotesProperty: notesPropertyRuleChecks(services),
|
|
30
31
|
OpacityProperty: opacityPropertyRuleChecks(services),
|
|
31
32
|
IconProperty: iconPropertyRuleChecks(services),
|
|
32
33
|
SpecificationRule: specificationRuleChecks(services),
|