@likec4/language-server 1.2.2 → 1.4.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 (36) hide show
  1. package/package.json +19 -8
  2. package/src/ast.ts +2 -0
  3. package/src/generated/ast.ts +157 -123
  4. package/src/generated/grammar.ts +2 -2
  5. package/src/generated/module.ts +1 -1
  6. package/src/like-c4.langium +53 -34
  7. package/src/logger.ts +21 -7
  8. package/src/lsp/CompletionProvider.ts +7 -0
  9. package/src/lsp/SemanticTokenProvider.ts +78 -17
  10. package/src/lsp/index.ts +1 -0
  11. package/src/model/model-builder.ts +3 -39
  12. package/src/model/model-parser.ts +19 -4
  13. package/src/model-change/ModelChanges.ts +58 -53
  14. package/src/model-change/changeElementStyle.ts +5 -6
  15. package/src/model-change/saveManualLayout.ts +43 -0
  16. package/src/model-graph/LikeC4ModelGraph.ts +304 -0
  17. package/src/model-graph/compute-view/__test__/fixture.ts +438 -0
  18. package/src/model-graph/compute-view/compute.ts +430 -0
  19. package/src/model-graph/compute-view/index.ts +33 -0
  20. package/src/model-graph/compute-view/predicates.ts +404 -0
  21. package/src/model-graph/dynamic-view/__test__/fixture.ts +56 -0
  22. package/src/model-graph/dynamic-view/compute.ts +198 -0
  23. package/src/model-graph/dynamic-view/index.ts +29 -0
  24. package/src/model-graph/index.ts +3 -0
  25. package/src/model-graph/utils/applyElementCustomProperties.ts +49 -0
  26. package/src/model-graph/utils/applyViewRuleStyles.ts +68 -0
  27. package/src/model-graph/utils/buildComputeNodes.ts +61 -0
  28. package/src/model-graph/utils/sortNodes.ts +105 -0
  29. package/src/module.ts +3 -0
  30. package/src/protocol.ts +3 -18
  31. package/src/references/scope-computation.ts +29 -11
  32. package/src/references/scope-provider.ts +22 -16
  33. package/src/validation/view.ts +9 -4
  34. package/src/view-utils/manual-layout.ts +93 -0
  35. package/contrib/likec4.monarch.ts +0 -41
  36. package/src/lsp/DocumentLinkProvider.test.ts +0 -66
@@ -21,7 +21,11 @@ RelationshipKind:
21
21
 
22
22
  SpecificationRule:
23
23
  name='specification' '{'
24
- (elements+=SpecificationElementKind | tags+=SpecificationTag | relationships+=SpecificationRelationshipKind)*
24
+ (
25
+ elements+=SpecificationElementKind |
26
+ tags+=SpecificationTag |
27
+ relationships+=SpecificationRelationshipKind
28
+ )*
25
29
  '}';
26
30
 
27
31
  SpecificationElementKind:
@@ -47,7 +51,8 @@ Model:
47
51
  ExplicitRelation |
48
52
  Element
49
53
  )*
50
- '}';
54
+ '}'
55
+ ;
51
56
 
52
57
  Element:
53
58
  (
@@ -61,16 +66,17 @@ Element:
61
66
  )?
62
67
  )?
63
68
  )?
64
- ('{' body=ElementBody '}')?
69
+ body=ElementBody?
65
70
  ;
66
71
 
67
- ElementBody:
72
+ ElementBody: '{'
68
73
  tags=Tags?
69
74
  props+=ElementProperty*
70
75
  elements+=(
71
76
  Relation |
72
77
  Element
73
78
  )*
79
+ '}'
74
80
  ;
75
81
 
76
82
  ElementProperty:
@@ -80,37 +86,37 @@ ElementStringProperty:
80
86
  key=('title' | 'technology' | 'description') Colon? value=String SemiColon?;
81
87
 
82
88
  ExtendElement:
83
- 'extend' element=FqnElementRef '{'
84
- body=ExtendElementBody
85
- '}'
89
+ 'extend' element=FqnElementRef body=ExtendElementBody
86
90
  ;
87
91
 
88
- ExtendElementBody:
92
+ ExtendElementBody: '{'
89
93
  elements+=(
90
94
  ExplicitRelation |
91
95
  Element
92
96
  )*
97
+ '}'
93
98
  ;
94
99
 
95
100
  //
96
101
  FqnElementRef:
97
- el=[Element] ({infer FqnElementRef.parent=current} Dot el=[Element])*;
102
+ el=[Element] ({infer FqnElementRef.parent=current} dot=StickyDot el=[Element])*;
98
103
 
99
104
  ElementRef:
100
- el=[Element] ({infer ElementRef.parent=current} Dot el=[Element])*;
105
+ el=[Element] ({infer ElementRef.parent=current} dot=StickyDot el=[Element])*;
101
106
 
102
107
  Tags:
103
- value+=[Tag:TagId] (Comma? value+=[Tag:TagId])* (Comma | SemiColon)?;
108
+ (value+=[Tag:TagId] comma+=(Comma | SemiColon)?)+
109
+ ;
104
110
 
105
111
  Relation:
106
112
  ExplicitRelation | ImplicitRelation;
107
113
 
108
114
  fragment RelationFragment:
109
- ('->' | '-[' kind=[RelationshipKind] ']->')
115
+ ('->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId] )
110
116
  target=ElementRef
111
117
  title=String?
112
118
  tags=Tags?
113
- ('{' body=RelationBody '}')?
119
+ body=RelationBody?
114
120
  ;
115
121
 
116
122
  ExplicitRelation:
@@ -119,9 +125,10 @@ ExplicitRelation:
119
125
  ImplicitRelation:
120
126
  ('this' | 'it')? RelationFragment;
121
127
 
122
- RelationBody:
128
+ RelationBody: '{'
123
129
  tags=Tags?
124
130
  props+=RelationProperty*
131
+ '}'
125
132
  ;
126
133
 
127
134
  RelationProperty:
@@ -142,8 +149,8 @@ RelationStyleProperty:
142
149
  ModelViews:
143
150
  name='views' '{'
144
151
  views+=(
145
- ElementView |
146
- DynamicView
152
+ DynamicView |
153
+ ElementView
147
154
  )*
148
155
  '}';
149
156
 
@@ -154,19 +161,18 @@ ElementView:
154
161
  'extends' extends=ElementViewRef |
155
162
  'of' viewOf=ElementRef
156
163
  )?
157
- '{' body=ElementViewBody '}'
164
+ body=ElementViewBody
158
165
  ;
159
166
 
160
167
  DynamicView:
161
- 'dynamic' 'view' name=Id '{'
162
- body=DynamicViewBody
163
- '}'
168
+ 'dynamic' 'view' name=Id body=DynamicViewBody
164
169
  ;
165
170
 
166
- DynamicViewBody:
171
+ DynamicViewBody: '{'
167
172
  tags=Tags?
168
173
  props+=ViewProperty*
169
174
  (steps+=DynamicViewStep | rules+=DynamicViewRule)*
175
+ '}'
170
176
  ;
171
177
 
172
178
  ViewRef:
@@ -175,12 +181,15 @@ ViewRef:
175
181
  ElementViewRef:
176
182
  view=[ElementView];
177
183
 
178
- ElementViewBody:
184
+ ElementViewBody: '{'
179
185
  tags=Tags?
180
186
  props+=ViewProperty*
181
187
  rules+=ViewRule*
188
+ '}'
182
189
  ;
183
190
 
191
+ type StringProperty = ElementStringProperty| ViewStringProperty;
192
+
184
193
  ViewProperty:
185
194
  ViewStringProperty | LinkProperty
186
195
  ;
@@ -209,15 +218,15 @@ DynamicViewStep:
209
218
 
210
219
  ViewRulePredicate:
211
220
  ({infer IncludePredicate} 'include' | {infer ExcludePredicate} 'exclude')
212
- expressions+=ViewRulePredicateExpr (Comma expressions+=ViewRulePredicateExpr)* Comma?
221
+ expressions+=ViewRulePredicateExpr (commas+=Comma expressions+=ViewRulePredicateExpr)* Comma?
213
222
  ;
214
223
 
215
224
  DynamicViewRulePredicate:
216
- 'include' expressions+=ViewRulePredicateExpr (Comma expressions+=ViewRulePredicateExpr)* Comma?
225
+ 'include' expressions+=ViewRulePredicateExpr (commas+=Comma expressions+=ViewRulePredicateExpr)* Comma?
217
226
  ;
218
227
 
219
228
  ViewRuleStyle:
220
- 'style' targets+=ElementExpr (Comma targets+=ElementExpr)* Comma? '{'
229
+ 'style' targets+=ElementExpr (commas+=Comma targets+=ElementExpr)* Comma? '{'
221
230
  styleprops+=StyleProperty*
222
231
  '}';
223
232
 
@@ -227,7 +236,7 @@ ViewRuleAutoLayout:
227
236
  ViewRulePredicateExpr:
228
237
  InOutExpr |
229
238
  ElementExpr (
230
- {infer CustomElementExpr.target=current} 'with' '{' body=CustomElementExprBody '}' |
239
+ {infer CustomElementExpr.target=current} 'with' body=CustomElementExprBody |
231
240
  {infer RelationExpr.source=current} (isBidirectional?='<->' | '->') target=ElementExpr |
232
241
  {infer OutgoingExpr.from=current} '->'
233
242
  )?
@@ -251,7 +260,7 @@ WildcardExpr:
251
260
  ;
252
261
 
253
262
  ElementSelectorExpr infers ElementExpr:
254
- 'element' Dot (
263
+ 'element' StickyDot (
255
264
  {infer ElementTagExpr} 'tag' IsEqual tag=[Tag:TagId] |
256
265
  {infer ElementKindExpr} 'kind' IsEqual kind=[ElementKind]
257
266
  )
@@ -264,16 +273,17 @@ DescedantsExpr infers ElementExpr:
264
273
  )?
265
274
  ;
266
275
 
267
- CustomElementExprBody:
276
+ CustomElementExprBody: '{'
268
277
  props+=(
269
278
  NavigateToProperty |
270
279
  ElementStringProperty |
271
280
  StyleProperty
272
281
  )*
282
+ '}'
273
283
  ;
274
284
 
275
285
  NavigateToProperty:
276
- 'navigateTo' value=ViewRef;
286
+ key='navigateTo' value=ViewRef;
277
287
 
278
288
  // Common properties -------------------------------------
279
289
 
@@ -323,7 +333,11 @@ RelationshipStyleProperty:
323
333
  ColorProperty | LineProperty | ArrowProperty;
324
334
 
325
335
  LineOptions returns string:
326
- 'solid' | 'dashed' | 'dotted';
336
+ 'solid' |
337
+ 'dashed' |
338
+ 'dotted'
339
+ ;
340
+
327
341
  ArrowType returns string:
328
342
  'none' |
329
343
  'normal' |
@@ -345,7 +359,10 @@ Uri returns string:
345
359
  // Percent returns string: PERCENT;
346
360
 
347
361
  TagId returns string:
348
- TagHash Id;
362
+ Hash Id;
363
+
364
+ DotId returns string:
365
+ Dot Id;
349
366
 
350
367
  Id returns string:
351
368
  IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | 'element' | 'model';
@@ -378,15 +395,17 @@ terminal URI_RELATIVE: /\.{0,2}\/[^\/]\S+/;
378
395
 
379
396
  terminal DotUnderscore: /\b\._/;
380
397
  terminal DotWildcard: /\b\.\*/;
381
- terminal TagHash: /\#\b/;
382
- terminal Dot: /\b\./;
398
+ terminal Hash: '#';
399
+
400
+ // No space allowed before dot
401
+ terminal StickyDot: /\b\./;
402
+ terminal Dot: '.';
383
403
  terminal NotEqual: /\!\={1,2}/;
384
404
  terminal Eq: /\={1,2}/;
385
405
  terminal Colon: ':';
386
406
  terminal SemiColon: ';';
387
407
  terminal Comma: ',';
388
408
  terminal Percent: /\b\d+%/;
389
- // terminal Percent: ('0'..'9')+ '%';
390
409
 
391
410
  terminal String: /"[^"]*"|'[^']*'/;
392
411
 
package/src/logger.ts CHANGED
@@ -17,17 +17,23 @@ export const logger = {
17
17
  if (isSilent) return
18
18
  console.info(message)
19
19
  },
20
- warn(message: string) {
20
+ warn(message: unknown) {
21
21
  if (isSilent) return
22
- console.warn(message)
22
+ if (typeof message === 'string' || message instanceof Error) {
23
+ console.warn(message)
24
+ return
25
+ }
26
+ const error = normalizeError(message)
27
+ console.warn(`${error.name}: ${error.message}`)
23
28
  },
24
- error(message: any) {
29
+ error(message: unknown) {
25
30
  if (isSilent) return
26
- if (typeof message === 'string') {
31
+ if (typeof message === 'string' || message instanceof Error) {
27
32
  console.error(message)
28
33
  return
29
34
  }
30
- console.error(normalizeError(message))
35
+ const error = normalizeError(message)
36
+ console.error(`${error.name}: ${error.message}`, error)
31
37
  },
32
38
  silent(silent = true) {
33
39
  isSilent = silent
@@ -36,8 +42,16 @@ export const logger = {
36
42
 
37
43
  export type Logger = typeof logger
38
44
 
39
- export function logError(error: unknown): void {
40
- logger.error(error)
45
+ export function logError(err: unknown): void {
46
+ if (typeof err === 'string') {
47
+ logger.error(err)
48
+ return
49
+ }
50
+ if (err instanceof Error) {
51
+ logger.error(err.stack ?? err.message)
52
+ return
53
+ }
54
+ logger.error(normalizeError(err))
41
55
  }
42
56
 
43
57
  export function logWarnError(err: unknown): void {
@@ -0,0 +1,7 @@
1
+ import { type CompletionProviderOptions, DefaultCompletionProvider } from 'langium/lsp'
2
+
3
+ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
4
+ readonly completionOptions = {
5
+ triggerCharacters: ['.']
6
+ } satisfies CompletionProviderOptions
7
+ }
@@ -9,17 +9,22 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
9
9
  node: AstNode,
10
10
  acceptor: SemanticTokenAcceptor
11
11
  ): void | undefined | 'prune' {
12
+ if (ast.isRelationshipKind(node)) {
13
+ return acceptor({
14
+ node,
15
+ property: 'name',
16
+ type: SemanticTokenTypes.function
17
+ })
18
+ }
12
19
  if (ast.isRelation(node) && 'kind' in node) {
13
- acceptor({
20
+ return acceptor({
14
21
  node,
15
22
  property: 'kind',
16
- type: SemanticTokenTypes.type,
17
- modifier: [SemanticTokenModifiers.definition]
23
+ type: SemanticTokenTypes.function
18
24
  })
19
25
  }
20
-
21
26
  if (ast.isElementViewRef(node)) {
22
- acceptor({
27
+ return acceptor({
23
28
  node,
24
29
  property: 'view',
25
30
  type: SemanticTokenTypes.variable
@@ -28,15 +33,24 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
28
33
  if (ast.isDescedantsExpr(node) && node.$cstNode) {
29
34
  acceptor({
30
35
  cst: node.$cstNode,
31
- type: SemanticTokenTypes.variable
36
+ type: SemanticTokenTypes.variable,
37
+ modifier: [
38
+ SemanticTokenModifiers.definition,
39
+ SemanticTokenModifiers.readonly
40
+ ]
32
41
  })
33
42
  return 'prune'
34
43
  }
35
44
  if (ast.isWildcardExpr(node) && node.$cstNode) {
36
45
  acceptor({
37
46
  cst: node.$cstNode,
38
- type: SemanticTokenTypes.variable
47
+ type: SemanticTokenTypes.variable,
48
+ modifier: [
49
+ SemanticTokenModifiers.definition,
50
+ SemanticTokenModifiers.readonly
51
+ ]
39
52
  })
53
+ return 'prune'
40
54
  }
41
55
 
42
56
  if (ast.isElementKindExpr(node)) {
@@ -59,33 +73,50 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
59
73
  acceptor({
60
74
  node,
61
75
  property: 'el',
62
- type: node.parent ? SemanticTokenTypes.property : SemanticTokenTypes.variable
76
+ type: node.parent ? SemanticTokenTypes.property : SemanticTokenTypes.variable,
77
+ modifier: [
78
+ SemanticTokenModifiers.definition,
79
+ SemanticTokenModifiers.readonly
80
+ ]
63
81
  })
82
+ return !node.parent ? 'prune' : undefined
64
83
  }
65
84
  if (ast.isSpecificationElementKind(node) || ast.isSpecificationRelationshipKind(node)) {
66
85
  acceptor({
67
86
  node,
68
87
  property: 'kind',
69
88
  type: SemanticTokenTypes.type,
70
- modifier: [SemanticTokenModifiers.definition]
89
+ modifier: [
90
+ SemanticTokenModifiers.declaration,
91
+ SemanticTokenModifiers.readonly
92
+ ]
71
93
  })
72
94
  }
73
95
  if (ast.isTags(node)) {
74
96
  acceptor({
75
97
  node,
76
98
  property: 'value',
77
- type: SemanticTokenTypes.type,
78
- modifier: [SemanticTokenModifiers.definition]
99
+ type: SemanticTokenTypes.interface
79
100
  })
80
101
  }
81
102
  if (ast.isTag(node)) {
82
- acceptor({
103
+ return acceptor({
83
104
  node,
84
105
  property: 'name',
85
106
  type: SemanticTokenTypes.type,
86
107
  modifier: [SemanticTokenModifiers.definition]
87
108
  })
88
109
  }
110
+ if (
111
+ ast.isRelationStyleProperty(node)
112
+ || (ast.isStyleProperties(node) && ast.isElementBody(node.$container))
113
+ ) {
114
+ acceptor({
115
+ node,
116
+ property: 'key',
117
+ type: SemanticTokenTypes.property
118
+ })
119
+ }
89
120
  if (
90
121
  ast.isColorProperty(node)
91
122
  || ast.isShapeProperty(node)
@@ -93,6 +124,11 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
93
124
  || ast.isLineProperty(node)
94
125
  || ast.isBorderProperty(node)
95
126
  ) {
127
+ acceptor({
128
+ node,
129
+ property: 'key',
130
+ type: SemanticTokenTypes.property
131
+ })
96
132
  acceptor({
97
133
  node,
98
134
  property: 'value',
@@ -100,24 +136,42 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
100
136
  })
101
137
  }
102
138
  if (ast.isOpacityProperty(node)) {
139
+ acceptor({
140
+ node,
141
+ property: 'key',
142
+ type: SemanticTokenTypes.property
143
+ })
103
144
  acceptor({
104
145
  node,
105
146
  property: 'value',
106
147
  type: SemanticTokenTypes.number
107
148
  })
149
+ return
108
150
  }
109
- if (ast.isLinkProperty(node) || ast.isIconProperty(node)) {
151
+ if (
152
+ ast.isLinkProperty(node)
153
+ || ast.isIconProperty(node)
154
+ || ast.isElementStringProperty(node)
155
+ || ast.isRelationStringProperty(node)
156
+ || ast.isViewStringProperty(node)
157
+ ) {
158
+ acceptor({
159
+ node,
160
+ property: 'key',
161
+ type: SemanticTokenTypes.property
162
+ })
110
163
  acceptor({
111
164
  node,
112
165
  property: 'value',
113
166
  type: SemanticTokenTypes.string
114
167
  })
168
+ return
115
169
  }
116
170
  if (ast.isElement(node)) {
117
- this.highlightAstElement(node, acceptor)
171
+ return this.highlightAstElement(node, acceptor)
118
172
  }
119
173
  if (ast.isLikeC4View(node)) {
120
- this.highlightView(node, acceptor)
174
+ return this.highlightView(node, acceptor)
121
175
  }
122
176
  }
123
177
 
@@ -126,7 +180,10 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
126
180
  node,
127
181
  property: 'name',
128
182
  type: SemanticTokenTypes.variable,
129
- modifier: [SemanticTokenModifiers.declaration]
183
+ modifier: [
184
+ SemanticTokenModifiers.declaration,
185
+ SemanticTokenModifiers.readonly
186
+ ]
130
187
  })
131
188
  acceptor({
132
189
  node,
@@ -142,7 +199,11 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
142
199
  node,
143
200
  property: 'name',
144
201
  type: SemanticTokenTypes.variable,
145
- modifier: [SemanticTokenModifiers.declaration]
202
+ modifier: [
203
+ SemanticTokenModifiers.declaration,
204
+ SemanticTokenModifiers.readonly,
205
+ 'local'
206
+ ]
146
207
  })
147
208
  }
148
209
  }
package/src/lsp/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './CodeLensProvider'
2
+ export * from './CompletionProvider'
2
3
  export * from './DocumentHighlightProvider'
3
4
  export * from './DocumentLinkProvider'
4
5
  export * from './DocumentSymbolProvider'
@@ -7,7 +7,6 @@ import {
7
7
  type StrictElementView,
8
8
  type ViewID
9
9
  } from '@likec4/core'
10
- import { computeDynamicView, computeView, LikeC4ModelGraph } from '@likec4/graph'
11
10
  import { deepEqual as eq } from 'fast-equals'
12
11
  import type { URI, WorkspaceCache } from 'langium'
13
12
  import { DocumentState, interruptAndCheck, type LangiumDocument, type LangiumDocuments } from 'langium'
@@ -17,6 +16,7 @@ import {
17
16
  flatMap,
18
17
  forEach,
19
18
  isNullish,
19
+ isNumber,
20
20
  isTruthy,
21
21
  map,
22
22
  mapToObj,
@@ -36,6 +36,7 @@ import type {
36
36
  } from '../ast'
37
37
  import { isParsedLikeC4LangiumDocument } from '../ast'
38
38
  import { logError, logger, logWarnError } from '../logger'
39
+ import { computeDynamicView, computeView, LikeC4ModelGraph } from '../model-graph'
39
40
  import type { LikeC4Services } from '../module'
40
41
  import { printDocs } from '../utils/printDocs'
41
42
  import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
@@ -87,7 +88,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
87
88
  ...(icon && { icon }),
88
89
  style: {
89
90
  ...(border && { border }),
90
- ...(opacity && { opacity })
91
+ ...(isNumber(opacity) && { opacity })
91
92
  },
92
93
  links: links ? resolveLinks(doc, links) : null,
93
94
  tags: tags ?? null,
@@ -169,43 +170,6 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
169
170
  mapToObj(r => [r.id, r])
170
171
  )
171
172
 
172
- // const toElementView = (view: ParsedAstElementView, doc: LangiumDocument): c4.ElementView => {
173
- // let { astPath, rules, title, description, tags, links, id, __, ...model } = view
174
-
175
- // if ('viewOf' in view) {
176
- // title ??= elements[view.viewOf]?.title ?? null
177
- // }
178
- // if (!title && view.id === 'index') {
179
- // title = 'Landscape view'
180
- // }
181
- // return {
182
- // __,
183
- // id,
184
- // ...model,
185
- // title,
186
- // description,
187
- // tags,
188
- // links: links ? resolveLinks(doc, links) : null,
189
- // docUri: '',
190
- // rules
191
- // }
192
- // }
193
-
194
- // const toDynamicView = (view: ParsedAstDynamicView, doc: LangiumDocument): c4.DynamicView => {
195
- // let { rules, steps, title, description, tags, links, id, __ } = view
196
- // return {
197
- // __,
198
- // id,
199
- // title,
200
- // description,
201
- // tags,
202
- // links: links ? resolveLinks(doc, links) : null,
203
- // docUri: '',
204
- // rules,
205
- // steps
206
- // }
207
- // }
208
-
209
173
  const toC4View = (doc: LangiumDocument) => {
210
174
  const docUri = doc.uri.toString()
211
175
  return (parsedAstView: ParsedAstView): c4.View => {
@@ -1,6 +1,6 @@
1
1
  import { type c4, InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
2
2
  import type { AstNode, LangiumDocument } from 'langium'
3
- import { AstUtils } from 'langium'
3
+ import { AstUtils, CstUtils } from 'langium'
4
4
  import { isTruthy } from 'remeda'
5
5
  import stripIndent from 'strip-indent'
6
6
  import type {
@@ -29,6 +29,7 @@ import { elementRef, getFqnElementRef } from '../elementRef'
29
29
  import { logError, logger, logWarnError } from '../logger'
30
30
  import type { LikeC4Services } from '../module'
31
31
  import { stringHash } from '../utils'
32
+ import { deserializeFromComment } from '../view-utils/manual-layout'
32
33
  import type { FqnIndex } from './fqn-index'
33
34
 
34
35
  const { getDocument } = AstUtils
@@ -380,6 +381,14 @@ export class LikeC4ModelParser {
380
381
  nonexhaustive(astRule)
381
382
  }
382
383
 
384
+ private parseViewManualLaout(node: ast.DynamicView | ast.ElementView): c4.ViewManualLayout | undefined {
385
+ const commentNode = CstUtils.findCommentNode(node.$cstNode, ['BLOCK_COMMENT'])
386
+ if (!commentNode) {
387
+ return undefined
388
+ }
389
+ return deserializeFromComment(commentNode.text)
390
+ }
391
+
383
392
  private parseDynamicStep(node: ast.DynamicViewStep): c4.DynamicViewStep {
384
393
  const sourceEl = elementRef(node.source)
385
394
  if (!sourceEl) {
@@ -435,11 +444,12 @@ export class LikeC4ModelParser {
435
444
  const tags = this.convertTags(body)
436
445
  const links = body.props.filter(ast.isLinkProperty).map(p => p.value)
437
446
 
447
+ const manualLayout = this.parseViewManualLaout(astNode)
448
+
438
449
  const view: ParsedAstElementView = {
439
450
  __: 'element',
440
451
  id: id as c4.ViewID,
441
452
  astPath,
442
- ...(viewOf && { viewOf }),
443
453
  title,
444
454
  description,
445
455
  tags,
@@ -451,7 +461,9 @@ export class LikeC4ModelParser {
451
461
  logWarnError(e)
452
462
  return []
453
463
  }
454
- })
464
+ }),
465
+ ...(viewOf && { viewOf }),
466
+ ...(manualLayout && { manualLayout })
455
467
  }
456
468
  ViewOps.writeId(astNode, view.id)
457
469
 
@@ -489,6 +501,8 @@ export class LikeC4ModelParser {
489
501
 
490
502
  ViewOps.writeId(astNode, id as c4.ViewID)
491
503
 
504
+ const manualLayout = this.parseViewManualLaout(astNode)
505
+
492
506
  return {
493
507
  __: 'dynamic',
494
508
  id: id as c4.ViewID,
@@ -553,7 +567,8 @@ export class LikeC4ModelParser {
553
567
  logWarnError(e)
554
568
  }
555
569
  return acc
556
- }, [] as c4.DynamicViewStep[])
570
+ }, [] as c4.DynamicViewStep[]),
571
+ ...(manualLayout && { manualLayout })
557
572
  }
558
573
  }
559
574