@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.
Files changed (48) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +1 -1
  4. package/dist/browser.d.mts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/browser.mjs +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.mjs +1 -1
  14. package/dist/node.cjs +1 -1
  15. package/dist/node.d.cts +1 -1
  16. package/dist/node.d.mts +1 -1
  17. package/dist/node.d.ts +1 -1
  18. package/dist/node.mjs +1 -1
  19. package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.CjFzaJwI.d.cts} +42 -13
  20. package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CtKHXJDD.d.ts} +42 -13
  21. package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.D-84I33F.d.mts} +42 -13
  22. package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.DBJJUUgF.mjs} +485 -108
  23. package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.DtBRb9os.mjs} +166 -116
  24. package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DwyCJvXm.cjs} +164 -114
  25. package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.JWkqVjGv.cjs} +481 -104
  26. package/package.json +6 -5
  27. package/src/ast.ts +8 -6
  28. package/src/formatting/LikeC4Formatter.ts +388 -0
  29. package/src/formatting/utils.ts +26 -0
  30. package/src/generated/ast.ts +104 -10
  31. package/src/generated/grammar.ts +1 -1
  32. package/src/like-c4.langium +34 -7
  33. package/src/lsp/DocumentLinkProvider.ts +27 -15
  34. package/src/lsp/SemanticTokenProvider.ts +1 -1
  35. package/src/lsp/index.ts +1 -1
  36. package/src/model/fqn-index.ts +0 -1
  37. package/src/model/model-builder.ts +43 -32
  38. package/src/model/model-parser.ts +43 -21
  39. package/src/model-graph/compute-view/compute.ts +104 -78
  40. package/src/model-graph/compute-view/predicates.ts +3 -5
  41. package/src/model-graph/dynamic-view/compute.ts +96 -60
  42. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  43. package/src/module.ts +6 -9
  44. package/src/test/testServices.ts +27 -7
  45. package/src/validation/index.ts +2 -1
  46. package/src/validation/property-checks.ts +13 -1
  47. package/src/validation/specification.ts +3 -3
  48. 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
- ThemeColor,
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 { first, flatMap, hasAtLeast, isTruthy, map, omit, unique } from 'remeda'
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.toSorted(compareRelations)
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
- | Pick<Relation, 'title' | 'kind' | 'description' | 'technology' | 'color' | 'line' | 'head' | 'tail' | 'tags'>
210
- | {
211
- // TODO refactor with type-fest
212
- title: string
213
- description?: string | undefined
214
- technology?: string | undefined
215
- kind?: RelationshipKind | undefined
216
- color?: Color | undefined
217
- line?: RelationshipLineType | undefined
218
- head?: RelationshipArrowType | undefined
219
- tail?: RelationshipArrowType | undefined
220
- tags?: NonEmptyArray<Tag>
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
- relation = relations.reduce((acc, r) => {
234
- if (r.color && acc.color !== r.color) {
235
- acc.color = undefined
236
- }
237
- if (r.kind && acc.kind !== r.kind) {
238
- acc.kind = undefined
239
- }
240
- if (r.head && acc.head !== r.head) {
241
- acc.head = undefined
242
- }
243
- if (r.tail && acc.tail !== r.tail) {
244
- acc.tail = undefined
245
- }
246
- if (r.line && acc.line !== r.line) {
247
- acc.line = undefined
248
- }
249
- if (r.description && acc.description !== r.description) {
250
- acc.description = undefined
251
- }
252
- if (r.technology && acc.technology !== r.technology) {
253
- acc.technology = undefined
254
- }
255
- if (isTruthy(r.title) && acc.title !== r.title) {
256
- acc.title = '[...]'
257
- }
258
- return acc
259
- }, {
260
- title: first(flatMap(relations, r => isTruthy(r.title) ? r.title : [])) ?? '[...]',
261
- description: first(flatMap(relations, r => isTruthy(r.description) ? r.description : [])),
262
- technology: first(flatMap(relations, r => isTruthy(r.technology) ? r.technology : [])),
263
- kind: first(flatMap(relations, r => isTruthy(r.kind) ? r.kind : [])),
264
- head: first(flatMap(relations, r => isTruthy(r.head) ? r.head : [])),
265
- tail: first(flatMap(relations, r => isTruthy(r.tail) ? r.tail : [])),
266
- color: first(flatMap(relations, r => isTruthy(r.color) ? r.color : [])),
267
- line: first(flatMap(relations, r => isTruthy(r.line) ? r.line : []))
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
- for (const el of excludes) {
354
- this.implicits.delete(el)
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
- for (const relation of relations) {
361
- let edge
362
- while ((edge = this.ctxEdges.find(e => e.relations.includes(relation)))) {
363
- if (edge.relations.length === 1) {
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
- this.ctxEdges.splice(this.ctxEdges.indexOf(edge), 1)
367
- continue
382
+ return null
368
383
  }
369
- edge.relations = edge.relations.filter(r => r !== relation)
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: { title: String | undefined; technology?: String | undefined }
545
- ): { label: String } | false {
546
- const labelParts: String[] = []
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 && { label: labelParts.join('\n') }
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
- ThemeColor
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
- for (
75
- let {
76
- source: stepSource,
77
- target: stepTarget,
78
- title: stepTitle,
79
- isBackward,
80
- ...step
81
- } of viewSteps
82
- ) {
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 { 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 }, index) => {
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
- let relation
200
- if (relationships.length === 1) {
201
- relation = relationships[0]
202
- } else {
203
- relation = relationships.find(r => r.source === source.id && r.target === target.id)
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
- if (relation && isTruthy(relation.title)) {
207
- return {
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 labels = unique(relationships.flatMap(r => (isTruthy(r.title) ? r.title : [])))
217
- if (labels.length === 1) {
218
- return {
219
- title: labels[0]!,
220
- tags,
221
- relations
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
  }
@@ -52,8 +52,8 @@ export function buildElementNotations(nodes: ComputedNode[]): ElementNotation[]
52
52
  }))
53
53
  ),
54
54
  sortBy(
55
- prop('title'),
56
55
  prop('shape'),
56
+ prop('title'),
57
57
  [
58
58
  n => n.kinds.length,
59
59
  'desc'
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 (!context.connection) {
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
 
@@ -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
- await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder])
31
- // Workaround to set protected folders property
32
- Object.assign(services.shared.workspace.WorkspaceManager, {
33
- folders: [workspaceFolder]
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
- let previousPromise = Promise.resolve() as Promise<any>
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
 
@@ -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),