@likec4/language-server 1.8.1 → 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 (85) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +21 -0
  3. package/dist/browser.d.cts +22 -0
  4. package/dist/browser.d.mts +22 -0
  5. package/dist/browser.d.ts +22 -0
  6. package/dist/browser.mjs +19 -0
  7. package/dist/index.cjs +10 -0
  8. package/dist/index.d.cts +18 -0
  9. package/dist/index.d.mts +18 -0
  10. package/dist/index.d.ts +18 -0
  11. package/dist/index.mjs +1 -0
  12. package/dist/likec4lib.cjs +961 -0
  13. package/dist/likec4lib.d.cts +6 -0
  14. package/dist/likec4lib.d.mts +6 -0
  15. package/dist/likec4lib.d.ts +6 -0
  16. package/dist/likec4lib.mjs +957 -0
  17. package/dist/model-graph/index.cjs +10 -0
  18. package/dist/model-graph/index.d.cts +79 -0
  19. package/dist/model-graph/index.d.mts +79 -0
  20. package/dist/model-graph/index.d.ts +79 -0
  21. package/dist/model-graph/index.mjs +1 -0
  22. package/dist/node.cjs +18 -0
  23. package/dist/node.d.cts +20 -0
  24. package/dist/node.d.mts +20 -0
  25. package/dist/node.d.ts +20 -0
  26. package/dist/node.mjs +16 -0
  27. package/dist/protocol.cjs +25 -0
  28. package/dist/protocol.d.cts +43 -0
  29. package/dist/protocol.d.mts +43 -0
  30. package/dist/protocol.d.ts +43 -0
  31. package/dist/protocol.mjs +17 -0
  32. package/dist/shared/language-server.CjFzaJwI.d.cts +1223 -0
  33. package/dist/shared/language-server.CtKHXJDD.d.ts +1223 -0
  34. package/dist/shared/language-server.D-84I33F.d.mts +1223 -0
  35. package/dist/shared/language-server.DBJJUUgF.mjs +5737 -0
  36. package/dist/shared/language-server.DtBRb9os.mjs +1656 -0
  37. package/dist/shared/language-server.DwyCJvXm.cjs +1669 -0
  38. package/dist/shared/language-server.JWkqVjGv.cjs +5748 -0
  39. package/package.json +36 -20
  40. package/src/ast.ts +48 -36
  41. package/src/browser.ts +0 -3
  42. package/src/elementRef.ts +1 -1
  43. package/src/formatting/LikeC4Formatter.ts +388 -0
  44. package/src/formatting/utils.ts +26 -0
  45. package/src/generated/ast.ts +170 -12
  46. package/src/generated/grammar.ts +1 -1
  47. package/src/generated-lib/icons.ts +1 -1
  48. package/src/like-c4.langium +49 -8
  49. package/src/likec4lib.ts +2 -3
  50. package/src/logger.ts +9 -1
  51. package/src/lsp/DocumentLinkProvider.ts +27 -15
  52. package/src/lsp/RenameProvider.ts +8 -0
  53. package/src/lsp/SemanticTokenProvider.ts +20 -2
  54. package/src/lsp/index.ts +1 -0
  55. package/src/model/fqn-computation.ts +33 -23
  56. package/src/model/fqn-index.ts +5 -21
  57. package/src/model/model-builder.ts +180 -112
  58. package/src/model/model-locator.ts +1 -1
  59. package/src/model/model-parser-where.ts +3 -2
  60. package/src/model/model-parser.ts +99 -39
  61. package/src/model-graph/LikeC4ModelGraph.ts +42 -21
  62. package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
  63. package/src/model-graph/compute-view/compute.ts +110 -81
  64. package/src/model-graph/compute-view/predicates.ts +6 -8
  65. package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
  66. package/src/model-graph/dynamic-view/compute.ts +98 -61
  67. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  68. package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
  69. package/src/model-graph/utils/sortNodes.ts +2 -6
  70. package/src/module.ts +21 -4
  71. package/src/protocol.ts +4 -5
  72. package/src/references/scope-computation.ts +10 -1
  73. package/src/references/scope-provider.ts +2 -1
  74. package/src/shared/NodeKindProvider.ts +73 -34
  75. package/src/test/setup.ts +3 -8
  76. package/src/test/testServices.ts +27 -7
  77. package/src/utils/graphlib.ts +11 -0
  78. package/src/validation/index.ts +2 -1
  79. package/src/validation/property-checks.ts +13 -1
  80. package/src/validation/specification.ts +3 -3
  81. package/src/view-utils/manual-layout.ts +1 -1
  82. package/src/view-utils/resolve-extended-views.ts +19 -10
  83. package/src/view-utils/resolve-relative-paths.ts +19 -24
  84. package/src/view-utils/view-hash.ts +1 -1
  85. package/src/reset.d.ts +0 -2
@@ -1,10 +1,12 @@
1
+ import type * as c4 from '@likec4/core'
1
2
  import {
2
- type c4,
3
- compareByFqnHierarchically,
3
+ compareRelations,
4
+ computeColorValues,
5
+ type CustomColorDefinitions,
4
6
  isElementView,
5
7
  isScopedElementView,
6
8
  parentFqn,
7
- type ScopedElementView,
9
+ sortByFqnHierarchically,
8
10
  type ViewID
9
11
  } from '@likec4/core'
10
12
  import { deepEqual as eq } from 'fast-equals'
@@ -12,17 +14,22 @@ import type { Cancellation, LangiumDocument, LangiumDocuments, URI, WorkspaceCac
12
14
  import { Disposable, DocumentState, interruptAndCheck } from 'langium'
13
15
  import {
14
16
  filter,
15
- find,
16
17
  flatMap,
17
18
  forEach,
19
+ groupBy,
20
+ indexBy,
21
+ isEmpty,
22
+ isNonNullish,
18
23
  isNullish,
19
24
  isNumber,
20
25
  isTruthy,
21
26
  map,
22
27
  mapToObj,
28
+ mapValues,
23
29
  pipe,
24
30
  prop,
25
31
  reduce,
32
+ reverse,
26
33
  sort,
27
34
  values
28
35
  } from 'remeda'
@@ -31,7 +38,8 @@ import type {
31
38
  ParsedAstRelation,
32
39
  ParsedAstSpecification,
33
40
  ParsedAstView,
34
- ParsedLikeC4LangiumDocument
41
+ ParsedLikeC4LangiumDocument,
42
+ ParsedLink
35
43
  } from '../ast'
36
44
  import { isParsedLikeC4LangiumDocument } from '../ast'
37
45
  import { logError, logger, logWarnError } from '../logger'
@@ -40,26 +48,48 @@ import type { LikeC4Services } from '../module'
40
48
  import { printDocs } from '../utils/printDocs'
41
49
  import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
42
50
 
43
- function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.LikeC4Model {
51
+ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
44
52
  const c4Specification: ParsedAstSpecification = {
45
- kinds: {},
46
- relationships: {}
53
+ tags: new Set(),
54
+ elements: {},
55
+ relationships: {},
56
+ colors: {}
47
57
  }
48
58
  forEach(map(docs, prop('c4Specification')), spec => {
49
- Object.assign(c4Specification.kinds, spec.kinds)
59
+ spec.tags.forEach(t => c4Specification.tags.add(t))
60
+ Object.assign(c4Specification.elements, spec.elements)
50
61
  Object.assign(c4Specification.relationships, spec.relationships)
62
+ Object.assign(c4Specification.colors, spec.colors)
51
63
  })
52
- const resolveLinks = (doc: LangiumDocument, links: c4.NonEmptyArray<c4.Link>) => {
53
- return links.map(l => ({
54
- url: services.lsp.DocumentLinkProvider.resolveLink(doc, l.url),
55
- ...(l.title && { title: l.title })
56
- })) as c4.NonEmptyArray<c4.Link>
64
+ function resolveLinks(doc: LangiumDocument, links: c4.NonEmptyArray<ParsedLink>) {
65
+ return map(
66
+ links,
67
+ (link): c4.Link => {
68
+ try {
69
+ const relative = services.lsp.DocumentLinkProvider.relativeLink(doc, link.url)
70
+ if (relative && relative !== link.url) {
71
+ return {
72
+ ...link,
73
+ relative
74
+ }
75
+ }
76
+ } catch (e) {
77
+ logWarnError(e)
78
+ }
79
+ return link
80
+ }
81
+ )
57
82
  }
58
83
 
59
- const toModelElement = (doc: LangiumDocument) => {
84
+ const customColorDefinitions: CustomColorDefinitions = mapValues(
85
+ c4Specification.colors,
86
+ c => computeColorValues(c.color)
87
+ )
88
+
89
+ function toModelElement(doc: LangiumDocument) {
60
90
  return ({
61
91
  tags,
62
- links,
92
+ links: unresolvedLinks,
63
93
  style: {
64
94
  color,
65
95
  shape,
@@ -75,11 +105,12 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
75
105
  metadata
76
106
  }: ParsedAstElement): c4.Element | null => {
77
107
  try {
78
- const __kind = c4Specification.kinds[kind]
108
+ const __kind = c4Specification.elements[kind]
79
109
  if (!__kind) {
80
110
  logger.warn(`No kind '${kind}' found for ${id}`)
81
111
  return null
82
112
  }
113
+ const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
83
114
  color ??= __kind.style.color
84
115
  shape ??= __kind.style.shape
85
116
  icon ??= __kind.style.icon
@@ -90,13 +121,13 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
90
121
  ...(color && { color }),
91
122
  ...(shape && { shape }),
92
123
  ...(icon && { icon }),
93
- ...(metadata && { metadata }),
124
+ ...(metadata && !isEmpty(metadata) && { metadata }),
94
125
  ...(__kind.notation && { notation: __kind.notation }),
95
126
  style: {
96
127
  ...(border && { border }),
97
128
  ...(isNumber(opacity) && { opacity })
98
129
  },
99
- links: links ? resolveLinks(doc, links) : null,
130
+ links,
100
131
  tags: tags ?? null,
101
132
  technology: technology ?? null,
102
133
  description: description ?? null,
@@ -115,7 +146,9 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
115
146
  docs,
116
147
  flatMap(d => map(d.c4Elements, toModelElement(d))),
117
148
  filter(isTruthy),
118
- sort(compareByFqnHierarchically),
149
+ // sort from root elements to nested, so that parent is always present
150
+ // Import to preserve the order from the source
151
+ sortByFqnHierarchically,
119
152
  reduce(
120
153
  (acc, el) => {
121
154
  const parent = parentFqn(el.id)
@@ -123,25 +156,20 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
123
156
  logWarnError(`No parent found for ${el.id}`)
124
157
  return acc
125
158
  }
126
- if (el.id in acc) {
127
- // should not happen, as validated
128
- logWarnError(`Duplicate element id: ${el.id}`)
129
- return acc
130
- }
131
159
  acc[el.id] = el
132
160
  return acc
133
161
  },
134
- {} as c4.LikeC4Model['elements']
162
+ {} as c4.ParsedLikeC4Model['elements']
135
163
  )
136
164
  )
137
165
 
138
- const toModelRelation = (doc: LangiumDocument) => {
166
+ function toModelRelation(doc: LangiumDocument) {
139
167
  return ({
140
168
  astPath,
141
169
  source,
142
170
  target,
143
171
  kind,
144
- links,
172
+ links: unresolvedLinks,
145
173
  id,
146
174
  ...model
147
175
  }: ParsedAstRelation): c4.Relation | null => {
@@ -151,25 +179,26 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
151
179
  )
152
180
  return null
153
181
  }
182
+ const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
154
183
 
155
- if (!isNullish(kind) && kind in c4Specification.relationships) {
184
+ if (isNonNullish(kind) && kind in c4Specification.relationships) {
156
185
  return {
157
- ...(links && { links: resolveLinks(doc, links) }),
158
186
  ...c4Specification.relationships[kind],
159
187
  ...model,
188
+ ...(links && { links }),
160
189
  source,
161
190
  target,
162
191
  kind,
163
192
  id
164
- }
193
+ } satisfies c4.Relation
165
194
  }
166
195
  return {
167
- ...(links && { links: resolveLinks(doc, links) }),
196
+ ...(links && { links }),
168
197
  ...model,
169
198
  source,
170
199
  target,
171
200
  id
172
- }
201
+ } satisfies c4.Relation
173
202
  }
174
203
  }
175
204
 
@@ -177,10 +206,12 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
177
206
  docs,
178
207
  flatMap(d => map(d.c4Relations, toModelRelation(d))),
179
208
  filter(isTruthy),
180
- mapToObj(r => [r.id, r])
209
+ sort(compareRelations),
210
+ reverse(),
211
+ indexBy(prop('id'))
181
212
  )
182
213
 
183
- const toC4View = (doc: LangiumDocument) => {
214
+ function toC4View(doc: LangiumDocument) {
184
215
  const docUri = doc.uri.toString()
185
216
  return (parsedAstView: ParsedAstView): c4.LikeC4View => {
186
217
  let {
@@ -188,11 +219,9 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
188
219
  title,
189
220
  description,
190
221
  tags,
191
- links,
192
-
222
+ links: unresolvedLinks,
193
223
  // ignore this property
194
224
  astPath: _ignore,
195
-
196
225
  // model should include discriminant __
197
226
  ...model
198
227
  } = parsedAstView
@@ -205,34 +234,32 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
205
234
  title = 'Landscape view'
206
235
  }
207
236
 
237
+ const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
238
+
208
239
  return {
209
- id,
210
- title,
211
- description,
240
+ ...model,
212
241
  tags,
213
- links: links ? resolveLinks(doc, links) : null,
242
+ links,
214
243
  docUri,
215
- ...model
244
+ description,
245
+ title,
246
+ id,
247
+ customColorDefinitions
216
248
  }
217
249
  }
218
250
  }
219
251
 
220
- const views = pipe(
221
- docs,
222
- flatMap(d => map(d.c4Views, toC4View(d))),
223
- resolveRelativePaths,
224
- mapToObj(v => [v.id, v]),
225
- resolveRulesExtendedViews
226
- )
227
- // add index view if not present
228
- if (!('index' in views)) {
229
- views['index' as ViewID] = {
252
+ const parsedViews = docs.flatMap(d => map(d.c4Views, toC4View(d)))
253
+ // Add index view if not present
254
+ if (!parsedViews.some(v => v.id === 'index')) {
255
+ parsedViews.unshift({
230
256
  __: 'element',
231
257
  id: 'index' as ViewID,
232
- title: 'Landscape',
258
+ title: 'Landscape view',
233
259
  description: null,
234
260
  tags: null,
235
261
  links: null,
262
+ customColorDefinitions: customColorDefinitions,
236
263
  rules: [
237
264
  {
238
265
  include: [
@@ -242,17 +269,30 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
242
269
  ]
243
270
  }
244
271
  ]
245
- }
272
+ })
246
273
  }
247
274
 
275
+ const views = pipe(
276
+ parsedViews,
277
+ resolveRelativePaths,
278
+ indexBy(prop('id')),
279
+ resolveRulesExtendedViews
280
+ )
281
+
248
282
  return {
283
+ specification: {
284
+ tags: Array.from(c4Specification.tags),
285
+ elements: c4Specification.elements,
286
+ relationships: c4Specification.relationships
287
+ },
249
288
  elements,
250
289
  relations,
251
290
  views
252
291
  }
253
292
  }
254
- const RAW_MODEL_CACHE = 'LikeC4RawModel'
255
- const MODEL_CACHE = 'LikeC4Model'
293
+
294
+ const CACHE_KEY_PARSED_MODEL = 'ParsedLikeC4Model'
295
+ const CACHE_KEY_COMPUTED_MODEL = 'ComputedLikeC4Model'
256
296
 
257
297
  type ModelParsedListener = (docs: URI[]) => void
258
298
 
@@ -274,9 +314,13 @@ export class LikeC4ModelBuilder {
274
314
  DocumentState.Validated,
275
315
  async (docs, _cancelToken) => {
276
316
  let parsed = [] as URI[]
277
- logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)\n${printDocs(docs)}`)
278
- for (const doc of parser.parse(docs)) {
279
- parsed.push(doc.uri)
317
+ try {
318
+ logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)\n${printDocs(docs)}`)
319
+ for (const doc of parser.parse(docs)) {
320
+ parsed.push(doc.uri)
321
+ }
322
+ } catch (e) {
323
+ logWarnError(e)
280
324
  }
281
325
  if (parsed.length > 0) {
282
326
  this.notifyListeners(parsed)
@@ -287,35 +331,82 @@ export class LikeC4ModelBuilder {
287
331
  logger.debug(`[ModelBuilder] Created`)
288
332
  }
289
333
 
290
- public async buildModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.LikeC4Model | null> {
291
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.LikeC4Model | null>
292
- if (cache.has(RAW_MODEL_CACHE)) {
293
- return cache.get(RAW_MODEL_CACHE)!
334
+ /**
335
+ * WARNING:
336
+ * This method is internal and should to be called only when all documents are known to be parsed.
337
+ * Otherwise, the model may be incomplete.
338
+ */
339
+ public unsafeSyncBuildModel(): c4.ParsedLikeC4Model | null {
340
+ const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
341
+ return cache.get(CACHE_KEY_PARSED_MODEL, () => {
342
+ const docs = this.documents()
343
+ if (docs.length === 0) {
344
+ logger.debug('[ModelBuilder] No documents to build model from')
345
+ return null
346
+ }
347
+ logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:\n${printDocs(docs)}`)
348
+ return buildModel(this.services, docs)
349
+ })
350
+ }
351
+
352
+ public async buildModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null> {
353
+ const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
354
+ if (cache.has(CACHE_KEY_PARSED_MODEL)) {
355
+ return cache.get(CACHE_KEY_PARSED_MODEL)!
294
356
  }
295
357
  return await this.services.shared.workspace.WorkspaceLock.read(async () => {
296
358
  if (cancelToken) {
297
359
  await interruptAndCheck(cancelToken)
298
360
  }
299
- return cache.get(RAW_MODEL_CACHE, () => {
300
- const docs = this.documents()
301
- if (docs.length === 0) {
302
- logger.debug('[ModelBuilder] No documents to build model from')
303
- return null
304
- }
305
- logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:\n${printDocs(docs)}`)
306
- return buildModel(this.services, docs)
307
- })
361
+ return this.unsafeSyncBuildModel()
308
362
  })
309
363
  }
310
364
 
311
365
  private previousViews: Record<ViewID, c4.ComputedView> = {}
312
366
 
367
+ /**
368
+ * WARNING:
369
+ * This method is internal and should to be called only when all documents are known to be parsed.
370
+ * Otherwise, the model may be incomplete.
371
+ */
372
+ public unsafeSyncBuildComputedModel(model: c4.ParsedLikeC4Model): c4.ComputedLikeC4Model {
373
+ const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model>
374
+ const viewsCache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
375
+ return cache.get(CACHE_KEY_COMPUTED_MODEL, () => {
376
+ const index = new LikeC4ModelGraph(model)
377
+
378
+ const allViews = [] as c4.ComputedView[]
379
+ for (const view of values(model.views)) {
380
+ const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index)
381
+ if (!result.isSuccess) {
382
+ logWarnError(result.error)
383
+ continue
384
+ }
385
+ allViews.push(result.view)
386
+ }
387
+ assignNavigateTo(allViews)
388
+ const views = mapToObj(allViews, v => {
389
+ const previous = this.previousViews[v.id]
390
+ const view = previous && eq(v, previous) ? previous : v
391
+ viewsCache.set(computedViewKey(v.id), view)
392
+ return [v.id, view] as const
393
+ })
394
+ this.previousViews = { ...views }
395
+ return {
396
+ specification: model.specification,
397
+ elements: model.elements,
398
+ relations: model.relations,
399
+ views
400
+ }
401
+ })
402
+ }
403
+
313
404
  public async buildComputedModel(
314
405
  cancelToken?: Cancellation.CancellationToken
315
- ): Promise<c4.LikeC4ComputedModel | null> {
316
- const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.LikeC4ComputedModel | null>
317
- if (cache.has(MODEL_CACHE)) {
318
- return cache.get(MODEL_CACHE)!
406
+ ): Promise<c4.ComputedLikeC4Model | null> {
407
+ const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model | null>
408
+ if (cache.has(CACHE_KEY_COMPUTED_MODEL)) {
409
+ return cache.get(CACHE_KEY_COMPUTED_MODEL)!
319
410
  }
320
411
  const model = await this.buildModel(cancelToken)
321
412
  if (!model) {
@@ -325,33 +416,7 @@ export class LikeC4ModelBuilder {
325
416
  if (cancelToken) {
326
417
  await interruptAndCheck(cancelToken)
327
418
  }
328
- const viewsCache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
329
- return cache.get(MODEL_CACHE, () => {
330
- const index = new LikeC4ModelGraph(model)
331
-
332
- const allViews = [] as c4.ComputedView[]
333
- for (const view of values(model.views)) {
334
- const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index)
335
- if (!result.isSuccess) {
336
- logWarnError(result.error)
337
- continue
338
- }
339
- allViews.push(result.view)
340
- }
341
- assignNavigateTo(allViews)
342
- const views = mapToObj(allViews, v => {
343
- const previous = this.previousViews[v.id]
344
- const view = previous && eq(v, previous) ? previous : v
345
- viewsCache.set(computedViewKey(v.id), view)
346
- return [v.id, view] as const
347
- })
348
- this.previousViews = { ...views }
349
- return {
350
- elements: model.elements,
351
- relations: model.relations,
352
- views
353
- }
354
- })
419
+ return this.unsafeSyncBuildComputedModel(model)
355
420
  })
356
421
  }
357
422
 
@@ -383,19 +448,22 @@ export class LikeC4ModelBuilder {
383
448
  }
384
449
  let computedView = result.view
385
450
 
386
- const allElementViews = values(model.views).filter(
387
- (v): v is ScopedElementView => isScopedElementView(v) && v.id !== viewId
451
+ const allElementViews = pipe(
452
+ model.views,
453
+ values(),
454
+ filter(isScopedElementView),
455
+ filter(v => v.id !== viewId),
456
+ groupBy(v => v.viewOf)
388
457
  )
389
458
 
390
- computedView.nodes.forEach(node => {
459
+ for (const node of computedView.nodes) {
391
460
  if (!node.navigateTo) {
392
- // find first element view that is not the current one
393
- const navigateTo = find(allElementViews, v => v.viewOf === node.id)
394
- if (navigateTo) {
395
- node.navigateTo = navigateTo.id
461
+ const viewsOfNode = allElementViews[node.id]
462
+ if (viewsOfNode) {
463
+ node.navigateTo = viewsOfNode[0].id
396
464
  }
397
465
  }
398
- })
466
+ }
399
467
 
400
468
  const previous = this.previousViews[viewId]
401
469
  computedView = previous && eq(computedView, previous) ? previous : computedView
@@ -1,4 +1,4 @@
1
- import type { likec4 as c4 } from '@likec4/core'
1
+ import type * as c4 from '@likec4/core'
2
2
  import type { LangiumDocuments } from 'langium'
3
3
  import { AstUtils, GrammarUtils } from 'langium'
4
4
  import type { Location } from 'vscode-languageserver-types'
@@ -1,5 +1,6 @@
1
- import { type c4, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
2
- import { isAndOperator, isOrOperator } from '@likec4/core/types'
1
+ import { invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
2
+ import { isAndOperator, isOrOperator } from '@likec4/core'
3
+ import type * as c4 from '@likec4/core'
3
4
  import { ast } from '../ast'
4
5
 
5
6
  const parseEquals = (