@likec4/language-server 1.21.1 → 1.22.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 (113) hide show
  1. package/README.md +4 -1
  2. package/bin/likec4-language-server.mjs +5 -2
  3. package/dist/LikeC4FileSystem.js +2 -2
  4. package/dist/browser.d.ts +3 -3
  5. package/dist/browser.js +17 -2
  6. package/dist/bundled.d.ts +8 -0
  7. package/dist/bundled.js +25 -0
  8. package/dist/bundled.mjs +2555 -4022
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +23 -2
  11. package/dist/logger.d.ts +9 -3
  12. package/dist/logger.js +35 -55
  13. package/dist/model/fqn-computation.js +2 -2
  14. package/dist/model/model-builder.js +13 -14
  15. package/dist/model-change/ModelChanges.js +2 -2
  16. package/dist/module.js +1 -4
  17. package/dist/references/scope-provider.js +3 -3
  18. package/dist/view-utils/manual-layout.js +2 -2
  19. package/dist/views/configurable-layouter.js +4 -4
  20. package/dist/views/likec4-views.d.ts +2 -1
  21. package/dist/views/likec4-views.js +2 -2
  22. package/package.json +14 -12
  23. package/dist/test/setup.d.ts +0 -1
  24. package/dist/test/setup.js +0 -7
  25. package/src/LikeC4FileSystem.ts +0 -38
  26. package/src/Rpc.ts +0 -134
  27. package/src/ast.ts +0 -556
  28. package/src/browser.ts +0 -35
  29. package/src/documentation/documentation-provider.ts +0 -52
  30. package/src/documentation/index.ts +0 -1
  31. package/src/formatting/LikeC4Formatter.ts +0 -639
  32. package/src/formatting/utils.ts +0 -26
  33. package/src/generated/ast.ts +0 -3735
  34. package/src/generated/grammar.ts +0 -10
  35. package/src/generated/module.ts +0 -33
  36. package/src/generated-lib/icons.ts +0 -1538
  37. package/src/index.ts +0 -30
  38. package/src/like-c4.langium +0 -901
  39. package/src/likec4lib.ts +0 -6
  40. package/src/logger.ts +0 -80
  41. package/src/lsp/CodeLensProvider.ts +0 -50
  42. package/src/lsp/CompletionProvider.ts +0 -147
  43. package/src/lsp/DocumentHighlightProvider.ts +0 -12
  44. package/src/lsp/DocumentLinkProvider.ts +0 -65
  45. package/src/lsp/DocumentSymbolProvider.ts +0 -313
  46. package/src/lsp/HoverProvider.ts +0 -92
  47. package/src/lsp/RenameProvider.ts +0 -8
  48. package/src/lsp/SemanticTokenProvider.ts +0 -383
  49. package/src/lsp/index.ts +0 -8
  50. package/src/model/deployments-index.ts +0 -209
  51. package/src/model/fqn-computation.ts +0 -83
  52. package/src/model/fqn-index.ts +0 -138
  53. package/src/model/index.ts +0 -6
  54. package/src/model/model-builder.ts +0 -724
  55. package/src/model/model-locator.ts +0 -146
  56. package/src/model/model-parser-where.ts +0 -84
  57. package/src/model/model-parser.ts +0 -86
  58. package/src/model/parser/Base.ts +0 -113
  59. package/src/model/parser/DeploymentModelParser.ts +0 -192
  60. package/src/model/parser/DeploymentViewParser.ts +0 -122
  61. package/src/model/parser/FqnRefParser.ts +0 -143
  62. package/src/model/parser/GlobalsParser.ts +0 -96
  63. package/src/model/parser/ModelParser.ts +0 -170
  64. package/src/model/parser/PredicatesParser.ts +0 -315
  65. package/src/model/parser/SpecificationParser.ts +0 -133
  66. package/src/model/parser/ViewsParser.ts +0 -428
  67. package/src/model-change/ModelChanges.ts +0 -101
  68. package/src/model-change/changeElementStyle.ts +0 -172
  69. package/src/model-change/changeViewLayout.ts +0 -47
  70. package/src/model-change/saveManualLayout.ts +0 -41
  71. package/src/module.ts +0 -255
  72. package/src/protocol.ts +0 -93
  73. package/src/references/index.ts +0 -3
  74. package/src/references/name-provider.ts +0 -37
  75. package/src/references/scope-computation.ts +0 -364
  76. package/src/references/scope-provider.ts +0 -201
  77. package/src/shared/NodeKindProvider.ts +0 -121
  78. package/src/shared/WorkspaceManager.ts +0 -48
  79. package/src/shared/WorkspaceSymbolProvider.ts +0 -3
  80. package/src/shared/index.ts +0 -3
  81. package/src/test/index.ts +0 -1
  82. package/src/test/setup.ts +0 -8
  83. package/src/test/testServices.ts +0 -152
  84. package/src/utils/disposable.ts +0 -30
  85. package/src/utils/elementRef.ts +0 -26
  86. package/src/utils/fqnRef.ts +0 -56
  87. package/src/utils/index.ts +0 -2
  88. package/src/utils/printDocs.ts +0 -3
  89. package/src/utils/stringHash.ts +0 -6
  90. package/src/validation/_shared.ts +0 -29
  91. package/src/validation/deployment-checks.ts +0 -131
  92. package/src/validation/dynamic-view-rule.ts +0 -23
  93. package/src/validation/dynamic-view-step.ts +0 -36
  94. package/src/validation/element.ts +0 -56
  95. package/src/validation/index.ts +0 -171
  96. package/src/validation/property-checks.ts +0 -52
  97. package/src/validation/relation.ts +0 -63
  98. package/src/validation/specification.ts +0 -205
  99. package/src/validation/view-predicates/element-with.ts +0 -36
  100. package/src/validation/view-predicates/expanded-element.ts +0 -16
  101. package/src/validation/view-predicates/expression-v2.ts +0 -101
  102. package/src/validation/view-predicates/incoming.ts +0 -20
  103. package/src/validation/view-predicates/index.ts +0 -6
  104. package/src/validation/view-predicates/outgoing.ts +0 -20
  105. package/src/validation/view-predicates/relation-with.ts +0 -17
  106. package/src/validation/view.ts +0 -37
  107. package/src/view-utils/assignNavigateTo.ts +0 -31
  108. package/src/view-utils/index.ts +0 -2
  109. package/src/view-utils/manual-layout.ts +0 -116
  110. package/src/view-utils/resolve-relative-paths.ts +0 -90
  111. package/src/views/configurable-layouter.ts +0 -65
  112. package/src/views/index.ts +0 -1
  113. package/src/views/likec4-views.ts +0 -139
@@ -1,724 +0,0 @@
1
- import type * as c4 from '@likec4/core'
2
- import {
3
- type CustomColorDefinitions,
4
- type ViewId,
5
- compareRelations,
6
- computeColorValues,
7
- DeploymentElement,
8
- isNonEmptyArray,
9
- isScopedElementView,
10
- LikeC4Model,
11
- parentFqn,
12
- sortByFqnHierarchically,
13
- } from '@likec4/core'
14
- import { resolveRulesExtendedViews } from '@likec4/core/compute-view'
15
- import { deepEqual as eq } from 'fast-equals'
16
- import type { Cancellation, LangiumDocument, LangiumDocuments, URI, WorkspaceCache } from 'langium'
17
- import { Disposable, DocumentState, interruptAndCheck } from 'langium'
18
- import {
19
- filter,
20
- flatMap,
21
- groupBy,
22
- hasAtLeast,
23
- indexBy,
24
- isBoolean,
25
- isDefined,
26
- isEmpty,
27
- isNonNullish,
28
- isNullish,
29
- isNumber,
30
- isTruthy,
31
- map,
32
- mapToObj,
33
- mapValues,
34
- omit,
35
- pipe,
36
- prop,
37
- reduce,
38
- reverse,
39
- sort,
40
- unique,
41
- values,
42
- } from 'remeda'
43
- import type {
44
- ParsedAstDeploymentRelation,
45
- ParsedAstElement,
46
- ParsedAstExtendElement,
47
- ParsedAstRelation,
48
- ParsedAstSpecification,
49
- ParsedAstView,
50
- ParsedLikeC4LangiumDocument,
51
- ParsedLink,
52
- } from '../ast'
53
- import { isParsedLikeC4LangiumDocument } from '../ast'
54
- import { logger, logWarnError } from '../logger'
55
- import type { LikeC4Services } from '../module'
56
- import { ADisposable } from '../utils'
57
- import { assignNavigateTo, resolveRelativePaths } from '../view-utils'
58
-
59
- function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
60
- // Merge specifications and globals from all documents
61
- const c4Specification: ParsedAstSpecification = {
62
- tags: new Set(),
63
- deployments: {},
64
- elements: {},
65
- relationships: {},
66
- colors: {},
67
- }
68
- const globals: c4.ModelGlobals = {
69
- predicates: {},
70
- dynamicPredicates: {},
71
- styles: {},
72
- }
73
- for (const doc of docs) {
74
- const {
75
- c4Specification: spec,
76
- c4Globals,
77
- } = doc
78
-
79
- spec.tags.forEach(t => c4Specification.tags.add(t))
80
- Object.assign(c4Specification.elements, spec.elements)
81
- Object.assign(c4Specification.relationships, spec.relationships)
82
- Object.assign(c4Specification.colors, spec.colors)
83
- Object.assign(c4Specification.deployments, spec.deployments)
84
- Object.assign(globals.predicates, c4Globals.predicates)
85
- Object.assign(globals.dynamicPredicates, c4Globals.dynamicPredicates)
86
- Object.assign(globals.styles, c4Globals.styles)
87
- }
88
-
89
- function resolveLinks(doc: LangiumDocument, links: c4.NonEmptyArray<ParsedLink>) {
90
- return map(
91
- links,
92
- (link): c4.Link => {
93
- try {
94
- const relative = services.lsp.DocumentLinkProvider.relativeLink(doc, link.url)
95
- if (relative && relative !== link.url) {
96
- return {
97
- ...link,
98
- relative,
99
- }
100
- }
101
- } catch (e) {
102
- logWarnError(e)
103
- }
104
- return link
105
- },
106
- )
107
- }
108
-
109
- const customColorDefinitions: CustomColorDefinitions = mapValues(
110
- c4Specification.colors,
111
- c => computeColorValues(c.color),
112
- )
113
-
114
- function toModelElement(doc: LangiumDocument) {
115
- return ({
116
- tags,
117
- links: unresolvedLinks,
118
- style: {
119
- color,
120
- shape,
121
- icon,
122
- opacity,
123
- border,
124
- size,
125
- multiple,
126
- padding,
127
- textSize,
128
- },
129
- id,
130
- kind,
131
- title,
132
- description,
133
- technology,
134
- metadata,
135
- }: ParsedAstElement): c4.Element | null => {
136
- try {
137
- const __kind = c4Specification.elements[kind]
138
- if (!__kind) {
139
- logger.warn(`No kind '${kind}' found for ${id}`)
140
- return null
141
- }
142
- const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
143
- color ??= __kind.style.color
144
- shape ??= __kind.style.shape
145
- icon ??= __kind.style.icon
146
- opacity ??= __kind.style.opacity
147
- border ??= __kind.style.border
148
- technology ??= __kind.technology
149
- multiple ??= __kind.style.multiple
150
- size ??= __kind.style.size
151
- padding ??= __kind.style.padding
152
- textSize ??= __kind.style.textSize
153
- return {
154
- ...(color && { color }),
155
- ...(shape && { shape }),
156
- ...(icon && { icon }),
157
- ...(metadata && !isEmpty(metadata) && { metadata }),
158
- ...(__kind.notation && { notation: __kind.notation }),
159
- style: {
160
- ...(border && { border }),
161
- ...(size && { size }),
162
- ...(padding && { padding }),
163
- ...(textSize && { textSize }),
164
- ...(isBoolean(multiple) && { multiple }),
165
- ...(isNumber(opacity) && { opacity }),
166
- },
167
- links,
168
- tags: tags ?? null,
169
- technology: technology ?? null,
170
- description: description ?? null,
171
- title,
172
- kind,
173
- id,
174
- }
175
- } catch (e) {
176
- logWarnError(e)
177
- }
178
- return null
179
- }
180
- }
181
-
182
- const elementsExtendData = new Map<string, Pick<c4.Element, 'links' | 'tags' | 'metadata'>>()
183
- function mergeAllC4ExtendElements(doc: ParsedLikeC4LangiumDocument) {
184
- for (const el of doc.c4ExtendElements) {
185
- let links = el.links ? resolveLinks(doc, el.links) : null
186
- const existing = elementsExtendData.get(el.id)
187
- if (existing) {
188
- links = existing.links ? [...existing.links, ...(links ?? [])] : links
189
-
190
- let tags: c4.Tag[] | null = [...(existing.tags ?? []), ...(el.tags ?? [])]
191
- if (!hasAtLeast(tags, 1)) {
192
- tags = null
193
- }
194
-
195
- elementsExtendData.set(el.id, {
196
- tags,
197
- links,
198
- metadata: { ...existing.metadata, ...el.metadata },
199
- })
200
- } else {
201
- elementsExtendData.set(el.id, {
202
- tags: el.tags ?? null,
203
- links,
204
- metadata: { ...el.metadata },
205
- })
206
- }
207
- }
208
- }
209
- function withExtendElementData(el: c4.Element): c4.Element {
210
- const extendData = elementsExtendData.get(el.id)
211
- if (extendData) {
212
- const links = [...(el.links ?? []), ...(extendData.links ?? [])]
213
- const tags = unique([...(el.tags ?? []), ...(extendData.tags ?? [])])
214
- const metadata = { ...el.metadata, ...extendData.metadata }
215
- return {
216
- ...el,
217
- tags: hasAtLeast(tags, 1) ? tags : null,
218
- links: hasAtLeast(links, 1) ? links : null,
219
- ...(!isEmpty(metadata) && { metadata }),
220
- }
221
- }
222
- return el
223
- }
224
-
225
- const elements = pipe(
226
- docs,
227
- flatMap(d => {
228
- mergeAllC4ExtendElements(d)
229
- return map(d.c4Elements, toModelElement(d))
230
- }),
231
- filter(isTruthy),
232
- // sort from root elements to nested, so that parent is always present
233
- // Import to preserve the order from the source
234
- sortByFqnHierarchically,
235
- reduce(
236
- (acc, el) => {
237
- const parent = parentFqn(el.id)
238
- if (parent && isNullish(acc[parent])) {
239
- logWarnError(`No parent found for ${el.id}`)
240
- return acc
241
- }
242
- acc[el.id] = withExtendElementData(el)
243
- return acc
244
- },
245
- {} as c4.ParsedLikeC4Model['elements'],
246
- ),
247
- )
248
-
249
- function toModelRelation(doc: LangiumDocument) {
250
- return ({
251
- astPath,
252
- source,
253
- target,
254
- kind,
255
- links: unresolvedLinks,
256
- id,
257
- ...model
258
- }: ParsedAstRelation): c4.ModelRelation | null => {
259
- if (isNullish(elements[source]) || isNullish(elements[target])) {
260
- logger.warn(
261
- `Invalid relation ${id} at ${doc.uri.path} ${astPath}, source: ${source}(${!!elements[
262
- source
263
- ]}), target: ${target}(${!!elements[target]})`,
264
- )
265
- return null
266
- }
267
- const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
268
-
269
- if (isNonNullish(kind) && kind in c4Specification.relationships) {
270
- return {
271
- ...c4Specification.relationships[kind],
272
- ...model,
273
- ...(links && { links }),
274
- source,
275
- target,
276
- kind,
277
- id,
278
- } satisfies c4.ModelRelation
279
- }
280
- return {
281
- ...(links && { links }),
282
- ...model,
283
- source,
284
- target,
285
- id,
286
- } satisfies c4.ModelRelation
287
- }
288
- }
289
-
290
- const relations = pipe(
291
- docs,
292
- flatMap(d => map(d.c4Relations, toModelRelation(d))),
293
- filter(isTruthy),
294
- sort(compareRelations),
295
- reverse(),
296
- indexBy(prop('id')),
297
- )
298
-
299
- function toDeploymentElement(doc: LangiumDocument) {
300
- return (parsed: c4.DeploymentElement): c4.DeploymentElement | null => {
301
- if (!DeploymentElement.isDeploymentNode(parsed)) {
302
- if (!parsed.links || parsed.links.length === 0) {
303
- return parsed
304
- }
305
- const links = resolveLinks(doc, parsed.links)
306
- return {
307
- ...parsed,
308
- links,
309
- }
310
- }
311
- try {
312
- const __kind = c4Specification.deployments[parsed.kind]
313
- if (!__kind) {
314
- logger.warn(`No kind '${parsed.kind}' found for ${parsed.id}`)
315
- return null
316
- }
317
- let {
318
- technology = __kind.technology,
319
- notation = __kind.notation,
320
- links,
321
- style,
322
- } = parsed
323
- return {
324
- ...parsed,
325
- ...(notation && { notation }),
326
- ...(technology && { technology }),
327
- style: {
328
- border: 'dashed',
329
- opacity: 10,
330
- ...__kind.style,
331
- ...style,
332
- },
333
- links: links ? resolveLinks(doc, links) : null,
334
- }
335
- } catch (e) {
336
- logWarnError(e)
337
- }
338
- return null
339
- }
340
- }
341
-
342
- const deploymentElements = pipe(
343
- docs,
344
- flatMap(d => map(d.c4Deployments, toDeploymentElement(d))),
345
- filter(isTruthy),
346
- // sort from root elements to nested, so that parent is always present
347
- // Import to preserve the order from the source
348
- sortByFqnHierarchically,
349
- reduce(
350
- (acc, el) => {
351
- const parent = parentFqn(el.id)
352
- if (parent && isNullish(acc[parent])) {
353
- logWarnError(`No parent found for deployment element ${el.id}`)
354
- return acc
355
- }
356
- acc[el.id] = el
357
- return acc
358
- },
359
- {} as c4.ParsedLikeC4Model['deployments']['elements'],
360
- ),
361
- )
362
-
363
- function toDeploymentRelation(doc: LangiumDocument) {
364
- return ({
365
- astPath,
366
- source,
367
- target,
368
- kind,
369
- links: unresolvedLinks,
370
- id,
371
- ...model
372
- }: ParsedAstDeploymentRelation): c4.DeploymentRelation | null => {
373
- if (isNullish(deploymentElements[source.id]) || isNullish(deploymentElements[target.id])) {
374
- logger.warn(
375
- `Invalid deployment relation ${id} at ${doc.uri.path} ${astPath}, source: ${source.id}(${!!deploymentElements[
376
- source.id
377
- ]}), target: ${target.id}(${!!deploymentElements[target.id]})`,
378
- )
379
- return null
380
- }
381
- const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
382
-
383
- if (isNonNullish(kind) && kind in c4Specification.relationships) {
384
- return {
385
- ...c4Specification.relationships[kind],
386
- ...model,
387
- ...(links && { links }),
388
- source,
389
- target,
390
- kind,
391
- id,
392
- } satisfies c4.DeploymentRelation
393
- }
394
- return {
395
- ...(links && { links }),
396
- ...model,
397
- source,
398
- target,
399
- id,
400
- } satisfies c4.DeploymentRelation
401
- }
402
- }
403
-
404
- const deploymentRelations = pipe(
405
- docs,
406
- flatMap(d => map(d.c4DeploymentRelations, toDeploymentRelation(d))),
407
- filter(isTruthy),
408
- reduce(
409
- (acc, el) => {
410
- if (isDefined(acc[el.id])) {
411
- logWarnError(`Duplicate deployment relation ${el.id}`)
412
- return acc
413
- }
414
- acc[el.id] = el
415
- return acc
416
- },
417
- {} as c4.ParsedLikeC4Model['deployments']['relations'],
418
- ),
419
- )
420
-
421
- function toC4View(doc: LangiumDocument) {
422
- const docUri = doc.uri.toString()
423
- return (parsedAstView: ParsedAstView): c4.LikeC4View => {
424
- let {
425
- id,
426
- title,
427
- description,
428
- tags,
429
- links: unresolvedLinks,
430
- // ignore this property
431
- astPath: _ignore,
432
- // model should include discriminant __
433
- ...model
434
- } = parsedAstView
435
-
436
- if (parsedAstView.__ === 'element' && isNullish(title) && 'viewOf' in parsedAstView) {
437
- title = elements[parsedAstView.viewOf]?.title ?? null
438
- }
439
-
440
- if (isNullish(title) && id === 'index') {
441
- title = 'Landscape view'
442
- }
443
-
444
- const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
445
-
446
- return {
447
- ...model,
448
- customColorDefinitions,
449
- tags,
450
- links,
451
- docUri,
452
- description,
453
- title,
454
- id,
455
- }
456
- }
457
- }
458
-
459
- const parsedViews = pipe(
460
- docs,
461
- flatMap(d => map(d.c4Views, toC4View(d))),
462
- // Resolve relative paths and sort by
463
- resolveRelativePaths,
464
- )
465
- // Add index view if not present
466
- if (!parsedViews.some(v => v.id === 'index')) {
467
- parsedViews.unshift({
468
- __: 'element',
469
- id: 'index' as ViewId,
470
- title: 'Landscape view',
471
- description: null,
472
- tags: null,
473
- links: null,
474
- customColorDefinitions: customColorDefinitions,
475
- rules: [
476
- {
477
- include: [
478
- {
479
- wildcard: true,
480
- },
481
- ],
482
- },
483
- ],
484
- })
485
- }
486
-
487
- const views = pipe(
488
- parsedViews,
489
- indexBy(prop('id')),
490
- resolveRulesExtendedViews,
491
- )
492
-
493
- return {
494
- specification: {
495
- tags: Array.from(c4Specification.tags),
496
- elements: c4Specification.elements,
497
- relationships: c4Specification.relationships,
498
- deployments: c4Specification.deployments,
499
- },
500
- elements,
501
- relations,
502
- globals,
503
- views,
504
- deployments: {
505
- elements: deploymentElements,
506
- relations: deploymentRelations,
507
- },
508
- }
509
- }
510
-
511
- const CACHE_KEY_PARSED_MODEL = 'ParsedLikeC4Model'
512
- const CACHE_KEY_COMPUTED_MODEL = 'ComputedLikeC4Model'
513
-
514
- type ModelParsedListener = (docs: URI[]) => void
515
-
516
- export class LikeC4ModelBuilder extends ADisposable {
517
- private langiumDocuments: LangiumDocuments
518
- private listeners: ModelParsedListener[] = []
519
-
520
- constructor(private services: LikeC4Services) {
521
- super()
522
- this.langiumDocuments = services.shared.workspace.LangiumDocuments
523
- const parser = services.likec4.ModelParser
524
-
525
- this.onDispose(
526
- services.shared.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
527
- if (deleted.length > 0) {
528
- this.notifyListeners(deleted)
529
- }
530
- }),
531
- )
532
- this.onDispose(
533
- services.shared.workspace.DocumentBuilder.onBuildPhase(
534
- DocumentState.Validated,
535
- async (docs, _cancelToken) => {
536
- let parsed = [] as URI[]
537
- logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)`)
538
- for (const doc of docs) {
539
- try {
540
- parsed.push(parser.parse(doc).uri)
541
- } catch (e) {
542
- logWarnError(e)
543
- }
544
- }
545
- await interruptAndCheck(_cancelToken)
546
- if (parsed.length > 0) {
547
- this.notifyListeners(parsed)
548
- }
549
- },
550
- ),
551
- )
552
- logger.debug(`[ModelBuilder] Created`)
553
- }
554
-
555
- /**
556
- * WARNING:
557
- * This method is internal and should to be called only when all documents are known to be parsed.
558
- * Otherwise, the model may be incomplete.
559
- */
560
- public unsafeSyncBuildModel(): c4.ParsedLikeC4Model | null {
561
- const docs = this.documents()
562
- if (docs.length === 0) {
563
- logger.debug('[ModelBuilder] No documents to build model from')
564
- return null
565
- }
566
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
567
- return cache.get(CACHE_KEY_PARSED_MODEL, () => {
568
- logger.debug(`[ModelBuilder] buildModel (${docs.length} docs)`)
569
- return buildModel(this.services, docs)
570
- })
571
- }
572
-
573
- public async buildModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null> {
574
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
575
- const cached = cache.get(CACHE_KEY_PARSED_MODEL)
576
- if (cached) {
577
- return cached
578
- }
579
- return await this.services.shared.workspace.WorkspaceLock.read(async () => {
580
- if (cancelToken) {
581
- await interruptAndCheck(cancelToken)
582
- }
583
- return this.unsafeSyncBuildModel()
584
- })
585
- }
586
-
587
- private previousViews: Record<ViewId, c4.ComputedView> = {}
588
-
589
- /**
590
- * WARNING:
591
- * This method is internal and should to be called only when all documents are known to be parsed.
592
- * Otherwise, the model may be incomplete.
593
- */
594
- public unsafeSyncBuildComputedModel(model: c4.ParsedLikeC4Model): c4.ComputedLikeC4Model {
595
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model>
596
- const viewsCache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
597
- return cache.get(CACHE_KEY_COMPUTED_MODEL, () => {
598
- const computeView = LikeC4Model.makeCompute(model)
599
- const allViews = [] as c4.ComputedView[]
600
- for (const view of values(model.views)) {
601
- const result = computeView(view)
602
- if (!result.isSuccess) {
603
- logWarnError(result.error)
604
- continue
605
- }
606
- allViews.push(result.view)
607
- }
608
- assignNavigateTo(allViews)
609
- const views = mapToObj(allViews, v => {
610
- const previous = this.previousViews[v.id]
611
- const view = previous && eq(v, previous) ? previous : v
612
- viewsCache.set(computedViewKey(v.id), view)
613
- return [v.id, view] as const
614
- })
615
- this.previousViews = { ...views }
616
- return {
617
- ...omit(model, ['views']),
618
- views,
619
- }
620
- })
621
- }
622
-
623
- public async buildComputedModel(
624
- cancelToken?: Cancellation.CancellationToken,
625
- ): Promise<c4.ComputedLikeC4Model | null> {
626
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model | null>
627
- if (cache.has(CACHE_KEY_COMPUTED_MODEL)) {
628
- return cache.get(CACHE_KEY_COMPUTED_MODEL)!
629
- }
630
- return await this.services.shared.workspace.WorkspaceLock.read(async () => {
631
- if (cancelToken) {
632
- await interruptAndCheck(cancelToken)
633
- }
634
- const model = this.unsafeSyncBuildModel()
635
- if (!model) {
636
- return null
637
- }
638
- return this.unsafeSyncBuildComputedModel(model)
639
- })
640
- }
641
-
642
- public async computeView(
643
- viewId: ViewId,
644
- cancelToken?: Cancellation.CancellationToken,
645
- ): Promise<c4.ComputedView | null> {
646
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
647
- const cacheKey = computedViewKey(viewId)
648
- if (cache.has(cacheKey)) {
649
- return cache.get(cacheKey)!
650
- }
651
- return await this.services.shared.workspace.WorkspaceLock.read(async () => {
652
- if (cancelToken) {
653
- await interruptAndCheck(cancelToken)
654
- }
655
- return cache.get(cacheKey, () => {
656
- const model = this.unsafeSyncBuildModel()
657
- const view = model?.views[viewId]
658
- if (!view) {
659
- logger.warn(`[ModelBuilder] Cannot find view ${viewId}`)
660
- return null
661
- }
662
- const result = LikeC4Model.makeCompute(model)(view)
663
- if (!result.isSuccess) {
664
- logWarnError(result.error)
665
- return null
666
- }
667
- let computedView = result.view
668
-
669
- const allElementViews = pipe(
670
- model.views,
671
- values(),
672
- filter(isScopedElementView),
673
- filter(v => v.id !== viewId),
674
- groupBy(v => v.viewOf),
675
- )
676
-
677
- for (const node of computedView.nodes) {
678
- if (!node.navigateTo) {
679
- const viewsOfNode = allElementViews[node.id]
680
- if (viewsOfNode) {
681
- node.navigateTo = viewsOfNode[0].id
682
- }
683
- }
684
- }
685
-
686
- const previous = this.previousViews[viewId]
687
- if (previous && eq(computedView, previous)) {
688
- computedView = previous
689
- } else {
690
- this.previousViews[viewId] = computedView
691
- }
692
-
693
- return computedView
694
- })
695
- })
696
- }
697
-
698
- public onModelParsed(callback: ModelParsedListener): Disposable {
699
- this.listeners.push(callback)
700
- return Disposable.create(() => {
701
- const index = this.listeners.indexOf(callback)
702
- if (index >= 0) {
703
- this.listeners.splice(index, 1)
704
- }
705
- })
706
- }
707
-
708
- private documents() {
709
- return this.langiumDocuments.all.filter(isParsedLikeC4LangiumDocument).toArray()
710
- }
711
-
712
- private notifyListeners(docs: URI[]) {
713
- for (const listener of this.listeners) {
714
- try {
715
- listener(docs)
716
- } catch (e) {
717
- logWarnError(e)
718
- }
719
- }
720
- }
721
- }
722
- function computedViewKey(viewId: string): string {
723
- return `computed-view-${viewId}`
724
- }