@likec4/language-server 1.21.0 → 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.
- package/README.md +4 -1
- package/bin/likec4-language-server.mjs +5 -2
- package/dist/LikeC4FileSystem.js +2 -2
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +17 -2
- package/dist/bundled.d.ts +8 -0
- package/dist/bundled.js +25 -0
- package/dist/bundled.mjs +2587 -4306
- package/dist/generated-lib/icons.js +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +23 -2
- package/dist/logger.d.ts +9 -3
- package/dist/logger.js +35 -55
- package/dist/model/fqn-computation.js +2 -2
- package/dist/model/model-builder.js +13 -14
- package/dist/model-change/ModelChanges.js +2 -2
- package/dist/module.js +1 -4
- package/dist/references/scope-provider.js +3 -3
- package/dist/view-utils/manual-layout.js +2 -2
- package/dist/views/configurable-layouter.js +4 -4
- package/dist/views/likec4-views.d.ts +2 -1
- package/dist/views/likec4-views.js +2 -2
- package/package.json +14 -14
- package/dist/test/setup.d.ts +0 -1
- package/dist/test/setup.js +0 -7
- package/src/LikeC4FileSystem.ts +0 -38
- package/src/Rpc.ts +0 -134
- package/src/ast.ts +0 -556
- package/src/browser.ts +0 -35
- package/src/documentation/documentation-provider.ts +0 -52
- package/src/documentation/index.ts +0 -1
- package/src/formatting/LikeC4Formatter.ts +0 -639
- package/src/formatting/utils.ts +0 -26
- package/src/generated/ast.ts +0 -3735
- package/src/generated/grammar.ts +0 -10
- package/src/generated/module.ts +0 -33
- package/src/generated-lib/icons.ts +0 -1537
- package/src/index.ts +0 -30
- package/src/like-c4.langium +0 -901
- package/src/likec4lib.ts +0 -6
- package/src/logger.ts +0 -80
- package/src/lsp/CodeLensProvider.ts +0 -50
- package/src/lsp/CompletionProvider.ts +0 -147
- package/src/lsp/DocumentHighlightProvider.ts +0 -12
- package/src/lsp/DocumentLinkProvider.ts +0 -65
- package/src/lsp/DocumentSymbolProvider.ts +0 -313
- package/src/lsp/HoverProvider.ts +0 -92
- package/src/lsp/RenameProvider.ts +0 -8
- package/src/lsp/SemanticTokenProvider.ts +0 -383
- package/src/lsp/index.ts +0 -8
- package/src/model/deployments-index.ts +0 -209
- package/src/model/fqn-computation.ts +0 -83
- package/src/model/fqn-index.ts +0 -138
- package/src/model/index.ts +0 -6
- package/src/model/model-builder.ts +0 -724
- package/src/model/model-locator.ts +0 -146
- package/src/model/model-parser-where.ts +0 -84
- package/src/model/model-parser.ts +0 -86
- package/src/model/parser/Base.ts +0 -113
- package/src/model/parser/DeploymentModelParser.ts +0 -192
- package/src/model/parser/DeploymentViewParser.ts +0 -122
- package/src/model/parser/FqnRefParser.ts +0 -143
- package/src/model/parser/GlobalsParser.ts +0 -96
- package/src/model/parser/ModelParser.ts +0 -170
- package/src/model/parser/PredicatesParser.ts +0 -315
- package/src/model/parser/SpecificationParser.ts +0 -133
- package/src/model/parser/ViewsParser.ts +0 -428
- package/src/model-change/ModelChanges.ts +0 -101
- package/src/model-change/changeElementStyle.ts +0 -172
- package/src/model-change/changeViewLayout.ts +0 -47
- package/src/model-change/saveManualLayout.ts +0 -41
- package/src/module.ts +0 -255
- package/src/protocol.ts +0 -93
- package/src/references/index.ts +0 -3
- package/src/references/name-provider.ts +0 -37
- package/src/references/scope-computation.ts +0 -364
- package/src/references/scope-provider.ts +0 -201
- package/src/shared/NodeKindProvider.ts +0 -121
- package/src/shared/WorkspaceManager.ts +0 -48
- package/src/shared/WorkspaceSymbolProvider.ts +0 -3
- package/src/shared/index.ts +0 -3
- package/src/test/index.ts +0 -1
- package/src/test/setup.ts +0 -8
- package/src/test/testServices.ts +0 -152
- package/src/utils/disposable.ts +0 -30
- package/src/utils/elementRef.ts +0 -26
- package/src/utils/fqnRef.ts +0 -56
- package/src/utils/index.ts +0 -2
- package/src/utils/printDocs.ts +0 -3
- package/src/utils/stringHash.ts +0 -6
- package/src/validation/_shared.ts +0 -29
- package/src/validation/deployment-checks.ts +0 -131
- package/src/validation/dynamic-view-rule.ts +0 -23
- package/src/validation/dynamic-view-step.ts +0 -36
- package/src/validation/element.ts +0 -56
- package/src/validation/index.ts +0 -171
- package/src/validation/property-checks.ts +0 -52
- package/src/validation/relation.ts +0 -63
- package/src/validation/specification.ts +0 -205
- package/src/validation/view-predicates/element-with.ts +0 -36
- package/src/validation/view-predicates/expanded-element.ts +0 -16
- package/src/validation/view-predicates/expression-v2.ts +0 -101
- package/src/validation/view-predicates/incoming.ts +0 -20
- package/src/validation/view-predicates/index.ts +0 -6
- package/src/validation/view-predicates/outgoing.ts +0 -20
- package/src/validation/view-predicates/relation-with.ts +0 -17
- package/src/validation/view.ts +0 -37
- package/src/view-utils/assignNavigateTo.ts +0 -31
- package/src/view-utils/index.ts +0 -2
- package/src/view-utils/manual-layout.ts +0 -116
- package/src/view-utils/resolve-relative-paths.ts +0 -90
- package/src/views/configurable-layouter.ts +0 -65
- package/src/views/index.ts +0 -1
- 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
|
-
}
|