@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,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
- }