@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.
- 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 +2555 -4022
- 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 -12
- 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 -1538
- 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,428 +0,0 @@
|
|
|
1
|
-
import type * as c4 from '@likec4/core'
|
|
2
|
-
import { invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
3
|
-
import { isArray, isDefined, isNonNullish, isTruthy } from 'remeda'
|
|
4
|
-
import type { Writable } from 'type-fest'
|
|
5
|
-
import {
|
|
6
|
-
ast,
|
|
7
|
-
type ParsedAstDynamicView,
|
|
8
|
-
type ParsedAstElementView,
|
|
9
|
-
toAutoLayout,
|
|
10
|
-
toColor,
|
|
11
|
-
toElementStyle,
|
|
12
|
-
ViewOps
|
|
13
|
-
} from '../../ast'
|
|
14
|
-
import type { NotationProperty } from '../../generated/ast'
|
|
15
|
-
import { logger, logWarnError } from '../../logger'
|
|
16
|
-
import { stringHash } from '../../utils'
|
|
17
|
-
import { elementRef } from '../../utils/elementRef'
|
|
18
|
-
import { parseViewManualLayout } from '../../view-utils/manual-layout'
|
|
19
|
-
import { removeIndent, toSingleLine } from './Base'
|
|
20
|
-
import type { WithDeploymentView } from './DeploymentViewParser'
|
|
21
|
-
import type { WithPredicates } from './PredicatesParser'
|
|
22
|
-
|
|
23
|
-
export type WithViewsParser = ReturnType<typeof ViewsParser>
|
|
24
|
-
|
|
25
|
-
export function ViewsParser<TBase extends WithPredicates & WithDeploymentView>(B: TBase) {
|
|
26
|
-
return class ViewsParser extends B {
|
|
27
|
-
parseViews() {
|
|
28
|
-
const isValid = this.isValid
|
|
29
|
-
for (const viewBlock of this.doc.parseResult.value.views) {
|
|
30
|
-
const localStyles = viewBlock.styles.flatMap(s => {
|
|
31
|
-
try {
|
|
32
|
-
return isValid(s) ? this.parseViewRuleStyleOrGlobalRef(s) : []
|
|
33
|
-
} catch (e) {
|
|
34
|
-
logWarnError(e)
|
|
35
|
-
return []
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
for (const view of viewBlock.views) {
|
|
40
|
-
try {
|
|
41
|
-
if (!isValid(view)) {
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
switch (true) {
|
|
45
|
-
case ast.isElementView(view):
|
|
46
|
-
this.doc.c4Views.push(this.parseElementView(view, localStyles))
|
|
47
|
-
break
|
|
48
|
-
case ast.isDynamicView(view):
|
|
49
|
-
this.doc.c4Views.push(this.parseDynamicElementView(view, localStyles))
|
|
50
|
-
break
|
|
51
|
-
case ast.isDeploymentView(view):
|
|
52
|
-
this.doc.c4Views.push(this.parseDeploymentView(view))
|
|
53
|
-
break
|
|
54
|
-
default:
|
|
55
|
-
nonexhaustive(view)
|
|
56
|
-
}
|
|
57
|
-
} catch (e) {
|
|
58
|
-
logWarnError(e)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
parseElementView(astNode: ast.ElementView, additionalStyles: c4.ViewRuleStyleOrGlobalRef[]): ParsedAstElementView {
|
|
65
|
-
const body = astNode.body
|
|
66
|
-
invariant(body, 'ElementView body is not defined')
|
|
67
|
-
const astPath = this.getAstNodePath(astNode)
|
|
68
|
-
|
|
69
|
-
let viewOf = null as c4.Fqn | null
|
|
70
|
-
if ('viewOf' in astNode) {
|
|
71
|
-
const viewOfEl = elementRef(astNode.viewOf)
|
|
72
|
-
const _viewOf = viewOfEl && this.resolveFqn(viewOfEl)
|
|
73
|
-
if (!_viewOf) {
|
|
74
|
-
logger.warn('viewOf is not resolved: ' + astNode.$cstNode?.text)
|
|
75
|
-
} else {
|
|
76
|
-
viewOf = _viewOf
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let id = astNode.name
|
|
81
|
-
if (!id) {
|
|
82
|
-
id = 'view_' + stringHash(
|
|
83
|
-
this.doc.uri.toString(),
|
|
84
|
-
astPath,
|
|
85
|
-
viewOf ?? ''
|
|
86
|
-
) as c4.ViewId
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const title = toSingleLine(body.props.find(p => p.key === 'title')?.value) ?? null
|
|
90
|
-
const description = removeIndent(body.props.find(p => p.key === 'description')?.value) ?? null
|
|
91
|
-
|
|
92
|
-
const tags = this.convertTags(body)
|
|
93
|
-
const links = this.convertLinks(body)
|
|
94
|
-
|
|
95
|
-
const manualLayout = parseViewManualLayout(astNode)
|
|
96
|
-
|
|
97
|
-
const view: ParsedAstElementView = {
|
|
98
|
-
__: 'element',
|
|
99
|
-
id: id as c4.ViewId,
|
|
100
|
-
astPath,
|
|
101
|
-
title,
|
|
102
|
-
description,
|
|
103
|
-
tags,
|
|
104
|
-
links: isNonEmptyArray(links) ? links : null,
|
|
105
|
-
rules: [
|
|
106
|
-
...additionalStyles,
|
|
107
|
-
...body.rules.flatMap(n => {
|
|
108
|
-
try {
|
|
109
|
-
return this.isValid(n) ? this.parseViewRule(n) : []
|
|
110
|
-
} catch (e) {
|
|
111
|
-
logWarnError(e)
|
|
112
|
-
return []
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
],
|
|
116
|
-
...(viewOf && { viewOf }),
|
|
117
|
-
...(manualLayout && { manualLayout })
|
|
118
|
-
}
|
|
119
|
-
ViewOps.writeId(astNode, view.id)
|
|
120
|
-
|
|
121
|
-
if ('extends' in astNode) {
|
|
122
|
-
const extendsView = astNode.extends.view.ref
|
|
123
|
-
invariant(extendsView?.name, 'view extends is not resolved: ' + astNode.$cstNode?.text)
|
|
124
|
-
return Object.assign(view, {
|
|
125
|
-
extends: extendsView.name as c4.ViewId
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return view
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
parseViewRule(astRule: ast.ViewRule): c4.ViewRule {
|
|
133
|
-
if (ast.isViewRulePredicate(astRule)) {
|
|
134
|
-
return this.parseViewRulePredicate(astRule)
|
|
135
|
-
}
|
|
136
|
-
if (ast.isViewRuleGlobalPredicateRef(astRule)) {
|
|
137
|
-
return this.parseViewRuleGlobalPredicateRef(astRule)
|
|
138
|
-
}
|
|
139
|
-
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
140
|
-
return this.parseViewRuleStyleOrGlobalRef(astRule)
|
|
141
|
-
}
|
|
142
|
-
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
143
|
-
return toAutoLayout(astRule)
|
|
144
|
-
}
|
|
145
|
-
if (ast.isViewRuleGroup(astRule)) {
|
|
146
|
-
return this.parseViewRuleGroup(astRule)
|
|
147
|
-
}
|
|
148
|
-
nonexhaustive(astRule)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
parseViewRulePredicate(astNode: ast.ViewRulePredicate): c4.ViewRulePredicate {
|
|
152
|
-
const exprs = [] as c4.Expression[]
|
|
153
|
-
let predicate = astNode.predicates
|
|
154
|
-
while (predicate) {
|
|
155
|
-
const { value, prev } = predicate
|
|
156
|
-
try {
|
|
157
|
-
if (isTruthy(value) && this.isValid(value as any)) {
|
|
158
|
-
exprs.unshift(this.parsePredicate(value))
|
|
159
|
-
}
|
|
160
|
-
} catch (e) {
|
|
161
|
-
logWarnError(e)
|
|
162
|
-
}
|
|
163
|
-
if (!prev) {
|
|
164
|
-
break
|
|
165
|
-
}
|
|
166
|
-
predicate = prev
|
|
167
|
-
}
|
|
168
|
-
return ast.isIncludePredicate(astNode) ? { include: exprs } : { exclude: exprs }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
parseViewRuleGlobalPredicateRef(
|
|
172
|
-
astRule: ast.ViewRuleGlobalPredicateRef | ast.DynamicViewGlobalPredicateRef
|
|
173
|
-
): c4.ViewRuleGlobalPredicateRef {
|
|
174
|
-
return {
|
|
175
|
-
predicateId: astRule.predicate.$refText as c4.GlobalPredicateId
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
parseViewRuleStyleOrGlobalRef(astRule: ast.ViewRuleStyleOrGlobalRef): c4.ViewRuleStyleOrGlobalRef {
|
|
180
|
-
if (ast.isViewRuleStyle(astRule)) {
|
|
181
|
-
return this.parseViewRuleStyle(astRule)
|
|
182
|
-
}
|
|
183
|
-
if (ast.isViewRuleGlobalStyle(astRule)) {
|
|
184
|
-
return this.parseViewRuleGlobalStyle(astRule)
|
|
185
|
-
}
|
|
186
|
-
nonexhaustive(astRule)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
parseViewRuleGroup(astNode: ast.ViewRuleGroup): c4.ViewRuleGroup {
|
|
190
|
-
const groupRules = [] as c4.ViewRuleGroup['groupRules']
|
|
191
|
-
for (const rule of astNode.groupRules) {
|
|
192
|
-
try {
|
|
193
|
-
if (!this.isValid(rule)) {
|
|
194
|
-
continue
|
|
195
|
-
}
|
|
196
|
-
if (ast.isViewRulePredicate(rule)) {
|
|
197
|
-
groupRules.push(this.parseViewRulePredicate(rule))
|
|
198
|
-
continue
|
|
199
|
-
}
|
|
200
|
-
if (ast.isViewRuleGroup(rule)) {
|
|
201
|
-
groupRules.push(this.parseViewRuleGroup(rule))
|
|
202
|
-
continue
|
|
203
|
-
}
|
|
204
|
-
nonexhaustive(rule)
|
|
205
|
-
} catch (e) {
|
|
206
|
-
logWarnError(e)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
title: toSingleLine(astNode.title) ?? null,
|
|
211
|
-
groupRules,
|
|
212
|
-
...toElementStyle(astNode.props, this.isValid)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
parseViewRuleStyle(astRule: ast.ViewRuleStyle | ast.GlobalStyle): c4.ViewRuleStyle {
|
|
217
|
-
const styleProps = astRule.props.filter(ast.isStyleProperty)
|
|
218
|
-
const targets = astRule.target
|
|
219
|
-
const notation = astRule.props.find(ast.isNotationProperty)
|
|
220
|
-
return this.parseRuleStyle(styleProps, targets, notation)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
parseRuleStyle(
|
|
224
|
-
styleProperties: ast.StyleProperty[],
|
|
225
|
-
elementExpressionsIterator: ast.ElementExpressionsIterator,
|
|
226
|
-
notationProperty?: NotationProperty
|
|
227
|
-
): c4.ViewRuleStyle {
|
|
228
|
-
const styleProps = toElementStyle(styleProperties, this.isValid)
|
|
229
|
-
const notation = removeIndent(notationProperty?.value)
|
|
230
|
-
const targets = this.parseElementExpressionsIterator(elementExpressionsIterator)
|
|
231
|
-
return {
|
|
232
|
-
targets,
|
|
233
|
-
...(notation && { notation }),
|
|
234
|
-
style: {
|
|
235
|
-
...styleProps
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
parseViewRuleGlobalStyle(astRule: ast.ViewRuleGlobalStyle): c4.ViewRuleGlobalStyle {
|
|
241
|
-
return {
|
|
242
|
-
styleId: astRule.style.$refText as c4.GlobalStyleID
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
parseDynamicElementView(
|
|
247
|
-
astNode: ast.DynamicView,
|
|
248
|
-
additionalStyles: c4.ViewRuleStyleOrGlobalRef[]
|
|
249
|
-
): ParsedAstDynamicView {
|
|
250
|
-
const body = astNode.body
|
|
251
|
-
invariant(body, 'DynamicElementView body is not defined')
|
|
252
|
-
// only valid props
|
|
253
|
-
const isValid = this.isValid
|
|
254
|
-
const props = body.props.filter(isValid)
|
|
255
|
-
const astPath = this.getAstNodePath(astNode)
|
|
256
|
-
|
|
257
|
-
let id = astNode.name
|
|
258
|
-
if (!id) {
|
|
259
|
-
id = 'dynamic_' + stringHash(
|
|
260
|
-
this.doc.uri.toString(),
|
|
261
|
-
astPath
|
|
262
|
-
) as c4.ViewId
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const title = toSingleLine(props.find(p => p.key === 'title')?.value) ?? null
|
|
266
|
-
const description = removeIndent(props.find(p => p.key === 'description')?.value) ?? null
|
|
267
|
-
|
|
268
|
-
const tags = this.convertTags(body)
|
|
269
|
-
const links = this.convertLinks(body)
|
|
270
|
-
|
|
271
|
-
ViewOps.writeId(astNode, id as c4.ViewId)
|
|
272
|
-
|
|
273
|
-
const manualLayout = parseViewManualLayout(astNode)
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
__: 'dynamic',
|
|
277
|
-
id: id as c4.ViewId,
|
|
278
|
-
astPath,
|
|
279
|
-
title,
|
|
280
|
-
description,
|
|
281
|
-
tags,
|
|
282
|
-
links: isNonEmptyArray(links) ? links : null,
|
|
283
|
-
rules: [
|
|
284
|
-
...additionalStyles,
|
|
285
|
-
...body.rules.flatMap(n => {
|
|
286
|
-
try {
|
|
287
|
-
return isValid(n) ? this.parseDynamicViewRule(n) : []
|
|
288
|
-
} catch (e) {
|
|
289
|
-
logWarnError(e)
|
|
290
|
-
return []
|
|
291
|
-
}
|
|
292
|
-
}, [] as Array<c4.DynamicViewRule>)
|
|
293
|
-
],
|
|
294
|
-
steps: body.steps.reduce((acc, n) => {
|
|
295
|
-
try {
|
|
296
|
-
if (isValid(n)) {
|
|
297
|
-
if (ast.isDynamicViewParallelSteps(n)) {
|
|
298
|
-
acc.push(this.parseDynamicParallelSteps(n))
|
|
299
|
-
} else {
|
|
300
|
-
acc.push(this.parseDynamicStep(n))
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
} catch (e) {
|
|
304
|
-
logWarnError(e)
|
|
305
|
-
}
|
|
306
|
-
return acc
|
|
307
|
-
}, [] as c4.DynamicViewStepOrParallel[]),
|
|
308
|
-
...(manualLayout && { manualLayout })
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
parseDynamicViewRule(astRule: ast.DynamicViewRule): c4.DynamicViewRule {
|
|
313
|
-
if (ast.isDynamicViewIncludePredicate(astRule)) {
|
|
314
|
-
return this.parseDynamicViewIncludePredicate(astRule)
|
|
315
|
-
}
|
|
316
|
-
if (ast.isDynamicViewGlobalPredicateRef(astRule)) {
|
|
317
|
-
return this.parseViewRuleGlobalPredicateRef(astRule)
|
|
318
|
-
}
|
|
319
|
-
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
320
|
-
return this.parseViewRuleStyleOrGlobalRef(astRule)
|
|
321
|
-
}
|
|
322
|
-
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
323
|
-
return toAutoLayout(astRule)
|
|
324
|
-
}
|
|
325
|
-
nonexhaustive(astRule)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
parseDynamicViewIncludePredicate(astRule: ast.DynamicViewIncludePredicate): c4.DynamicViewIncludeRule {
|
|
329
|
-
const include = [] as c4.ElementPredicateExpression[]
|
|
330
|
-
let iter: ast.DynamicViewPredicateIterator | undefined = astRule.predicates
|
|
331
|
-
while (iter) {
|
|
332
|
-
try {
|
|
333
|
-
if (isNonNullish(iter.value) && this.isValid(iter.value as any)) {
|
|
334
|
-
const c4expr = this.parseElementPredicate(iter.value)
|
|
335
|
-
include.unshift(c4expr)
|
|
336
|
-
}
|
|
337
|
-
} catch (e) {
|
|
338
|
-
logWarnError(e)
|
|
339
|
-
}
|
|
340
|
-
iter = iter.prev
|
|
341
|
-
}
|
|
342
|
-
return { include }
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
parseDynamicParallelSteps(node: ast.DynamicViewParallelSteps): c4.DynamicViewParallelSteps {
|
|
346
|
-
return {
|
|
347
|
-
__parallel: node.steps.map(step => this.parseDynamicStep(step))
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
parseDynamicStep(node: ast.DynamicViewStep): c4.DynamicViewStep {
|
|
352
|
-
const sourceEl = elementRef(node.source)
|
|
353
|
-
if (!sourceEl) {
|
|
354
|
-
throw new Error('Invalid reference to source')
|
|
355
|
-
}
|
|
356
|
-
const targetEl = elementRef(node.target)
|
|
357
|
-
if (!targetEl) {
|
|
358
|
-
throw new Error('Invalid reference to target')
|
|
359
|
-
}
|
|
360
|
-
let source = this.resolveFqn(sourceEl)
|
|
361
|
-
let target = this.resolveFqn(targetEl)
|
|
362
|
-
const title = removeIndent(node.title) ?? null
|
|
363
|
-
|
|
364
|
-
let step: Writable<c4.DynamicViewStep> = {
|
|
365
|
-
source,
|
|
366
|
-
target,
|
|
367
|
-
title
|
|
368
|
-
}
|
|
369
|
-
if (node.isBackward) {
|
|
370
|
-
step = {
|
|
371
|
-
source: target,
|
|
372
|
-
target: source,
|
|
373
|
-
title,
|
|
374
|
-
isBackward: true
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (!isArray(node.custom?.props)) {
|
|
378
|
-
return step
|
|
379
|
-
}
|
|
380
|
-
for (const prop of node.custom.props) {
|
|
381
|
-
try {
|
|
382
|
-
switch (true) {
|
|
383
|
-
case ast.isRelationNavigateToProperty(prop): {
|
|
384
|
-
const viewId = prop.value.view.ref?.name
|
|
385
|
-
if (isTruthy(viewId)) {
|
|
386
|
-
step.navigateTo = viewId as c4.ViewId
|
|
387
|
-
}
|
|
388
|
-
break
|
|
389
|
-
}
|
|
390
|
-
case ast.isRelationStringProperty(prop):
|
|
391
|
-
case ast.isNotationProperty(prop):
|
|
392
|
-
case ast.isNotesProperty(prop): {
|
|
393
|
-
if (isDefined(prop.value)) {
|
|
394
|
-
step[prop.key] = removeIndent(prop.value) ?? ''
|
|
395
|
-
}
|
|
396
|
-
break
|
|
397
|
-
}
|
|
398
|
-
case ast.isArrowProperty(prop): {
|
|
399
|
-
if (isDefined(prop.value)) {
|
|
400
|
-
step[prop.key] = prop.value
|
|
401
|
-
}
|
|
402
|
-
break
|
|
403
|
-
}
|
|
404
|
-
case ast.isColorProperty(prop): {
|
|
405
|
-
const value = toColor(prop)
|
|
406
|
-
if (isDefined(value)) {
|
|
407
|
-
step[prop.key] = value
|
|
408
|
-
}
|
|
409
|
-
break
|
|
410
|
-
}
|
|
411
|
-
case ast.isLineProperty(prop): {
|
|
412
|
-
if (isDefined(prop.value)) {
|
|
413
|
-
step[prop.key] = prop.value
|
|
414
|
-
}
|
|
415
|
-
break
|
|
416
|
-
}
|
|
417
|
-
default:
|
|
418
|
-
nonexhaustive(prop)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
catch (e) {
|
|
422
|
-
logWarnError(e)
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return step
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { invariant, nonexhaustive } from '@likec4/core'
|
|
2
|
-
import { logger } from '@likec4/log'
|
|
3
|
-
import { Location, Range, TextEdit } from 'vscode-languageserver-types'
|
|
4
|
-
import { type ParsedLikeC4LangiumDocument } from '../ast'
|
|
5
|
-
import type { LikeC4ModelLocator } from '../model'
|
|
6
|
-
import type { LikeC4Services } from '../module'
|
|
7
|
-
import type { ChangeViewRequestParams } from '../protocol'
|
|
8
|
-
import { changeElementStyle } from './changeElementStyle'
|
|
9
|
-
import { changeViewLayout } from './changeViewLayout'
|
|
10
|
-
import { saveManualLayout } from './saveManualLayout'
|
|
11
|
-
|
|
12
|
-
export class LikeC4ModelChanges {
|
|
13
|
-
private locator: LikeC4ModelLocator
|
|
14
|
-
|
|
15
|
-
constructor(private services: LikeC4Services) {
|
|
16
|
-
this.locator = services.likec4.ModelLocator
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public async applyChange(changeView: ChangeViewRequestParams): Promise<Location | null> {
|
|
20
|
-
const lspConnection = this.services.shared.lsp.Connection
|
|
21
|
-
invariant(lspConnection, 'LSP Connection not available')
|
|
22
|
-
let result: Location | null = null
|
|
23
|
-
try {
|
|
24
|
-
await this.services.shared.workspace.WorkspaceLock.write(async () => {
|
|
25
|
-
const { doc, edits, modifiedRange } = this.convertToTextEdit(changeView)
|
|
26
|
-
const textDocument = {
|
|
27
|
-
uri: doc.textDocument.uri,
|
|
28
|
-
version: doc.textDocument.version
|
|
29
|
-
}
|
|
30
|
-
if (!edits.length) {
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
const applyResult = await lspConnection.workspace.applyEdit({
|
|
34
|
-
label: `LikeC4 - change view ${changeView.viewId}`,
|
|
35
|
-
edit: {
|
|
36
|
-
changes: {
|
|
37
|
-
[textDocument.uri]: edits
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
if (!applyResult.applied) {
|
|
42
|
-
lspConnection.window.showErrorMessage(`Failed to apply changes ${applyResult.failureReason}`)
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
result = {
|
|
46
|
-
uri: textDocument.uri,
|
|
47
|
-
range: modifiedRange
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
} catch (e) {
|
|
51
|
-
logger.error(`Failed to apply change ${changeView.change.op} ${changeView.viewId}`, e)
|
|
52
|
-
}
|
|
53
|
-
return result
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
protected convertToTextEdit({ viewId, change }: ChangeViewRequestParams): {
|
|
57
|
-
doc: ParsedLikeC4LangiumDocument
|
|
58
|
-
modifiedRange: Range
|
|
59
|
-
edits: TextEdit[]
|
|
60
|
-
} {
|
|
61
|
-
const lookup = this.locator.locateViewAst(viewId)
|
|
62
|
-
if (!lookup) {
|
|
63
|
-
throw new Error(`LikeC4ModelChanges: view not found: ${viewId}`)
|
|
64
|
-
}
|
|
65
|
-
switch (change.op) {
|
|
66
|
-
case 'change-element-style': {
|
|
67
|
-
return {
|
|
68
|
-
doc: lookup.doc,
|
|
69
|
-
...changeElementStyle(this.services, {
|
|
70
|
-
...lookup,
|
|
71
|
-
targets: change.targets,
|
|
72
|
-
style: change.style
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
case 'change-autolayout': {
|
|
77
|
-
const edit = changeViewLayout(this.services, {
|
|
78
|
-
...lookup,
|
|
79
|
-
layout: change.layout
|
|
80
|
-
})
|
|
81
|
-
return {
|
|
82
|
-
doc: lookup.doc,
|
|
83
|
-
modifiedRange: edit.range,
|
|
84
|
-
edits: [edit]
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
case 'save-manual-layout':
|
|
88
|
-
const edit = saveManualLayout(this.services, {
|
|
89
|
-
...lookup,
|
|
90
|
-
layout: change.layout
|
|
91
|
-
})
|
|
92
|
-
return {
|
|
93
|
-
doc: lookup.doc,
|
|
94
|
-
modifiedRange: edit.range,
|
|
95
|
-
edits: [edit]
|
|
96
|
-
}
|
|
97
|
-
default:
|
|
98
|
-
nonexhaustive(change)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { type Fqn, invariant, isAncestor, type NonEmptyArray, nonNullable, type ViewChange } from '@likec4/core'
|
|
2
|
-
import { GrammarUtils } from 'langium'
|
|
3
|
-
import { entries, filter, findLast, isTruthy, last } from 'remeda'
|
|
4
|
-
import { type Range, TextEdit } from 'vscode-languageserver-types'
|
|
5
|
-
import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast'
|
|
6
|
-
import type { FqnIndex } from '../model'
|
|
7
|
-
import type { LikeC4Services } from '../module'
|
|
8
|
-
|
|
9
|
-
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils
|
|
10
|
-
|
|
11
|
-
const asViewStyleRule = (target: string, style: ViewChange.ChangeElementStyle['style'], indent = 0) => {
|
|
12
|
-
const indentStr = indent > 0 ? ' '.repeat(indent) : ''
|
|
13
|
-
return [
|
|
14
|
-
indentStr + `style ${target} {`,
|
|
15
|
-
...entries(style).map(([key, value]) =>
|
|
16
|
-
indentStr + ` ${key} ${key === 'opacity' ? value.toString() + '%' : value}`
|
|
17
|
-
),
|
|
18
|
-
indentStr + `}`
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type ChangeElementStyleArg = {
|
|
23
|
-
view: ParsedAstView
|
|
24
|
-
doc: ParsedLikeC4LangiumDocument
|
|
25
|
-
viewAst: ast.LikeC4View
|
|
26
|
-
targets: NonEmptyArray<Fqn>
|
|
27
|
-
style: ViewChange.ChangeElementStyle['style']
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* - is ViewRuleStyle
|
|
32
|
-
* - has exactly one target
|
|
33
|
-
* - the target is an ElementRef to the given fqn
|
|
34
|
-
*/
|
|
35
|
-
const isMatchingViewRule =
|
|
36
|
-
(fqn: string, index: FqnIndex) => (rule: ast.ViewRule | ast.DynamicViewRule): rule is ast.ViewRuleStyle => {
|
|
37
|
-
if (!ast.isViewRuleStyle(rule)) {
|
|
38
|
-
return false
|
|
39
|
-
}
|
|
40
|
-
const target = rule.target.value
|
|
41
|
-
if (!target || isTruthy(rule.target.prev) || !ast.isElementRef(target)) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
44
|
-
const ref = target.el.ref
|
|
45
|
-
const _fqn = ref ? index.getFqn(ref) : null
|
|
46
|
-
return _fqn === fqn
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function changeElementStyle(services: LikeC4Services, {
|
|
50
|
-
view,
|
|
51
|
-
viewAst,
|
|
52
|
-
targets,
|
|
53
|
-
style
|
|
54
|
-
}: ChangeElementStyleArg): {
|
|
55
|
-
modifiedRange: Range
|
|
56
|
-
edits: TextEdit[]
|
|
57
|
-
} {
|
|
58
|
-
// Should never happen
|
|
59
|
-
invariant(viewAst.body, `View ${view.id} has no body`)
|
|
60
|
-
|
|
61
|
-
const viewCstNode = viewAst.$cstNode
|
|
62
|
-
invariant(viewCstNode, 'viewCstNode')
|
|
63
|
-
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end
|
|
64
|
-
?? viewAst.body.$cstNode?.range.end
|
|
65
|
-
invariant(insertPos, 'insertPos is not defined')
|
|
66
|
-
const indent = viewCstNode.range.start.character + 2
|
|
67
|
-
const fqnIndex = services.likec4.FqnIndex
|
|
68
|
-
const styleRules = filter(viewAst.body.rules, ast.isViewRuleStyle)
|
|
69
|
-
const viewOf = view.__ === 'element' ? view.viewOf : null
|
|
70
|
-
// Find existing rules
|
|
71
|
-
const existing = [] as Array<{ fqn: Fqn; rule: ast.ViewRuleStyle }>
|
|
72
|
-
const insert = [] as Array<{ fqn: Fqn }>
|
|
73
|
-
// const existingRules = [] as Array<{ fqn: Fqn, rule: ast.ViewRuleStyle }>
|
|
74
|
-
targets.forEach(target => {
|
|
75
|
-
const rule = findLast(styleRules, isMatchingViewRule(target, fqnIndex))
|
|
76
|
-
// remove viewOf from the target to shorten the fqn
|
|
77
|
-
const fqn = (viewOf && isAncestor(viewOf, target) ? target.substring(viewOf.length + 1) : target) as Fqn
|
|
78
|
-
if (rule) {
|
|
79
|
-
existing.push({ fqn, rule })
|
|
80
|
-
} else {
|
|
81
|
-
insert.push({ fqn })
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const modifiedRange = {
|
|
86
|
-
start: insertPos,
|
|
87
|
-
end: insertPos
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const includeRange = (range: Range) => {
|
|
91
|
-
if (range.start.line <= modifiedRange.start.line) {
|
|
92
|
-
if (range.start.line == modifiedRange.start.line) {
|
|
93
|
-
modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character)
|
|
94
|
-
} else {
|
|
95
|
-
modifiedRange.start = range.start
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (range.end.line >= modifiedRange.end.line) {
|
|
99
|
-
if (range.end.line == modifiedRange.end.line) {
|
|
100
|
-
modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character)
|
|
101
|
-
} else {
|
|
102
|
-
modifiedRange.end = range.end
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const edits = [] as TextEdit[]
|
|
108
|
-
|
|
109
|
-
if (insert.length > 0) {
|
|
110
|
-
const linesToInsert = insert.flatMap(({ fqn }) => asViewStyleRule(fqn, style, indent))
|
|
111
|
-
edits.push(
|
|
112
|
-
TextEdit.insert(
|
|
113
|
-
insertPos,
|
|
114
|
-
'\n' + linesToInsert.join('\n')
|
|
115
|
-
)
|
|
116
|
-
)
|
|
117
|
-
modifiedRange.start = {
|
|
118
|
-
line: insertPos.line + 1,
|
|
119
|
-
character: indent
|
|
120
|
-
}
|
|
121
|
-
modifiedRange.end = {
|
|
122
|
-
line: insertPos.line + linesToInsert.length,
|
|
123
|
-
character: (last(linesToInsert)?.length ?? 0)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (existing.length > 0) {
|
|
128
|
-
for (const { rule } of existing) {
|
|
129
|
-
const ruleCstNode = rule.$cstNode
|
|
130
|
-
invariant(ruleCstNode, 'RuleCstNode not found')
|
|
131
|
-
for (const [key, _value] of entries(style)) {
|
|
132
|
-
const value = key === 'opacity' ? _value.toString() + '%' : _value
|
|
133
|
-
const ruleProp = rule.props.find(p => p.key === key)
|
|
134
|
-
// replace existing property
|
|
135
|
-
if (ruleProp && ruleProp.$cstNode) {
|
|
136
|
-
const { range: { start, end } } = ruleProp.$cstNode
|
|
137
|
-
includeRange({
|
|
138
|
-
start,
|
|
139
|
-
end
|
|
140
|
-
})
|
|
141
|
-
edits.push(TextEdit.replace({ start, end }, key + ' ' + value))
|
|
142
|
-
continue
|
|
143
|
-
}
|
|
144
|
-
// insert new style property right after the opening brace
|
|
145
|
-
const insertPos = findNodeForKeyword(ruleCstNode, '{')?.range.end
|
|
146
|
-
invariant(insertPos, 'Opening brace not found')
|
|
147
|
-
const indentStr = ' '.repeat(2 + ruleCstNode.range.start.character)
|
|
148
|
-
const insertKeyValue = indentStr + key + ' ' + value
|
|
149
|
-
edits.push(
|
|
150
|
-
TextEdit.insert(
|
|
151
|
-
insertPos,
|
|
152
|
-
'\n' + insertKeyValue
|
|
153
|
-
)
|
|
154
|
-
)
|
|
155
|
-
includeRange({
|
|
156
|
-
start: {
|
|
157
|
-
line: insertPos.line + 1,
|
|
158
|
-
character: indentStr.length
|
|
159
|
-
},
|
|
160
|
-
end: {
|
|
161
|
-
line: insertPos.line + 1,
|
|
162
|
-
character: insertKeyValue.length
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
modifiedRange,
|
|
170
|
-
edits
|
|
171
|
-
}
|
|
172
|
-
}
|