@likec4/language-server 1.4.0 → 1.6.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 (45) hide show
  1. package/README.md +1 -1
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/package.json +26 -14
  4. package/src/Rpc.ts +25 -2
  5. package/src/ast.ts +19 -15
  6. package/src/generated/ast.ts +390 -203
  7. package/src/generated/grammar.ts +1 -1
  8. package/src/generated-lib/icons.ts +952 -0
  9. package/src/like-c4.langium +120 -64
  10. package/src/likec4lib.ts +7 -0
  11. package/src/lsp/DocumentSymbolProvider.ts +28 -1
  12. package/src/lsp/SemanticTokenProvider.ts +41 -22
  13. package/src/model/fqn-computation.ts +29 -7
  14. package/src/model/fqn-index.ts +18 -31
  15. package/src/model/model-builder.ts +13 -9
  16. package/src/model/model-locator.ts +7 -7
  17. package/src/model/model-parser.ts +166 -69
  18. package/src/model-change/changeElementStyle.ts +6 -6
  19. package/src/model-graph/compute-view/__test__/fixture.ts +52 -24
  20. package/src/model-graph/compute-view/compute.ts +51 -20
  21. package/src/model-graph/compute-view/predicates.ts +6 -1
  22. package/src/model-graph/dynamic-view/__test__/fixture.ts +2 -2
  23. package/src/model-graph/dynamic-view/compute.ts +2 -2
  24. package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts} +5 -3
  25. package/src/model-graph/utils/applyCustomRelationProperties.ts +50 -0
  26. package/src/model-graph/utils/applyViewRuleStyles.ts +11 -34
  27. package/src/model-graph/utils/elementExpressionToPredicate.ts +32 -0
  28. package/src/references/scope-computation.ts +113 -60
  29. package/src/references/scope-provider.ts +3 -23
  30. package/src/shared/NodeKindProvider.ts +1 -0
  31. package/src/shared/WorkspaceManager.ts +15 -6
  32. package/src/validation/dynamic-view-rule.ts +19 -26
  33. package/src/validation/element.ts +8 -4
  34. package/src/validation/index.ts +9 -6
  35. package/src/validation/property-checks.ts +23 -1
  36. package/src/validation/view-predicates/custom-element-expr.ts +21 -8
  37. package/src/validation/view-predicates/custom-relation-expr.ts +16 -0
  38. package/src/validation/view-predicates/expanded-element.ts +13 -24
  39. package/src/validation/view-predicates/incoming.ts +5 -5
  40. package/src/validation/view-predicates/index.ts +1 -0
  41. package/src/validation/view-predicates/outgoing.ts +5 -5
  42. package/src/view-utils/assignNavigateTo.ts +2 -2
  43. package/src/view-utils/manual-layout.ts +4 -2
  44. package/src/view-utils/resolve-extended-views.ts +2 -2
  45. package/src/view-utils/resolve-relative-paths.ts +3 -3
@@ -4,10 +4,22 @@ entry LikeC4Grammar:
4
4
  (
5
5
  specifications+=SpecificationRule |
6
6
  models+=Model |
7
- views+=ModelViews
7
+ views+=ModelViews |
8
+ likec4lib+=LikeC4Lib
8
9
  )*
9
10
  ;
10
11
 
12
+ // Lib -------------------------------------
13
+ LikeC4Lib:
14
+ 'likec4lib' '{'
15
+ 'icons' '{'
16
+ (icons+=LibIcon)+
17
+ '}'
18
+ '}';
19
+
20
+ LibIcon:
21
+ name=IconId;
22
+
11
23
  // Specification -------------------------------------
12
24
 
13
25
  ElementKind:
@@ -80,10 +92,10 @@ ElementBody: '{'
80
92
  ;
81
93
 
82
94
  ElementProperty:
83
- ElementStringProperty | StyleProperties | LinkProperty;
95
+ ElementStringProperty | StyleProperties | LinkProperty | IconProperty;
84
96
 
85
97
  ElementStringProperty:
86
- key=('title' | 'technology' | 'description') Colon? value=String SemiColon?;
98
+ key=('title' | 'technology' | 'description') ':'? value=String ';'?;
87
99
 
88
100
  ExtendElement:
89
101
  'extend' element=FqnElementRef body=ExtendElementBody
@@ -105,7 +117,7 @@ ElementRef:
105
117
  el=[Element] ({infer ElementRef.parent=current} dot=StickyDot el=[Element])*;
106
118
 
107
119
  Tags:
108
- (value+=[Tag:TagId] comma+=(Comma | SemiColon)?)+
120
+ value+=[Tag:TagId] (','? value+=[Tag:TagId])* (';')?
109
121
  ;
110
122
 
111
123
  Relation:
@@ -135,7 +147,7 @@ RelationProperty:
135
147
  RelationStringProperty | RelationStyleProperty | LinkProperty;
136
148
 
137
149
  RelationStringProperty:
138
- key='title' Colon? value=String SemiColon?;
150
+ key='title' ':'? value=String ';'?;
139
151
 
140
152
  RelationStyleProperty:
141
153
  key='style' '{'
@@ -168,13 +180,6 @@ DynamicView:
168
180
  'dynamic' 'view' name=Id body=DynamicViewBody
169
181
  ;
170
182
 
171
- DynamicViewBody: '{'
172
- tags=Tags?
173
- props+=ViewProperty*
174
- (steps+=DynamicViewStep | rules+=DynamicViewRule)*
175
- '}'
176
- ;
177
-
178
183
  ViewRef:
179
184
  view=[LikeC4View];
180
185
 
@@ -188,14 +193,22 @@ ElementViewBody: '{'
188
193
  '}'
189
194
  ;
190
195
 
191
- type StringProperty = ElementStringProperty| ViewStringProperty;
196
+ DynamicViewBody: '{'
197
+ tags=Tags?
198
+ props+=ViewProperty*
199
+ (steps+=DynamicViewStep | rules+=DynamicViewRule)*
200
+ '}'
201
+ ;
202
+
203
+
204
+ type StringProperty = ElementStringProperty| ViewStringProperty | RelationStringProperty;
192
205
 
193
206
  ViewProperty:
194
207
  ViewStringProperty | LinkProperty
195
208
  ;
196
209
 
197
210
  ViewStringProperty:
198
- key=('title' | 'description') Colon? value=String SemiColon?;
211
+ key=('title' | 'description') ':'? value=String ';'?;
199
212
 
200
213
  ViewLayoutDirection returns string:
201
214
  'TopBottom' | 'LeftRight' | 'BottomTop' | 'RightLeft';
@@ -213,67 +226,95 @@ DynamicViewRule:
213
226
  ;
214
227
 
215
228
  DynamicViewStep:
216
- source=ElementRef (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->') target=ElementRef title=String?
229
+ source=ElementRef (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId] ) target=ElementRef title=String?
217
230
  ;
218
231
 
219
232
  ViewRulePredicate:
220
- ({infer IncludePredicate} 'include' | {infer ExcludePredicate} 'exclude')
221
- expressions+=ViewRulePredicateExpr (commas+=Comma expressions+=ViewRulePredicateExpr)* Comma?
233
+ {infer IncludePredicate} 'include' exprs=Expressions |
234
+ {infer ExcludePredicate} 'exclude' exprs=Expressions
222
235
  ;
223
236
 
224
- DynamicViewRulePredicate:
225
- 'include' expressions+=ViewRulePredicateExpr (commas+=Comma expressions+=ViewRulePredicateExpr)* Comma?
237
+ // IncludePredicate:
238
+ // 'include' expr=WithExpressions ({infer IncludePredicate.prev=current} ',' (expr=WithExpressions)?)*
239
+ // ;
240
+ // ExcludePredicate:
241
+ // 'exclude' expr=WithExpressions ({infer ExcludePredicate.prev=current} ',' (expr=WithExpressions)?)*
242
+ // ;
243
+
244
+ Expressions:
245
+ value=WithExpressions ({infer Expressions.prev=current} ',' (value=WithExpressions)?)*
226
246
  ;
227
247
 
228
- ViewRuleStyle:
229
- 'style' targets+=ElementExpr (commas+=Comma targets+=ElementExpr)* Comma? '{'
230
- styleprops+=StyleProperty*
231
- '}';
248
+ WithExpressions infers Expression:
249
+ RelationExpression ({infer CustomRelationExpression.relation=current} 'with' custom=CustomRelationProperties)? |
250
+ ElementExpression ({infer CustomElementExpression.target=current} 'with' custom=CustomElementProperties)?
251
+ ;
232
252
 
233
- ViewRuleAutoLayout:
234
- 'autoLayout' direction=ViewLayoutDirection;
253
+ ElementExpression:
254
+ ElementSelectorExpression |
255
+ ElementDescedantsExpression
256
+ ;
257
+
258
+ ElementSelectorExpression infers ElementExpression:
259
+ {infer WildcardExpression} isWildcard?='*' |
260
+ {infer ElementTagExpression} 'element.tag' IsEqual tag=[Tag:TagId]? |
261
+ {infer ElementKindExpression} 'element.kind' IsEqual kind=[ElementKind]?
262
+ ;
235
263
 
236
- ViewRulePredicateExpr:
237
- InOutExpr |
238
- ElementExpr (
239
- {infer CustomElementExpr.target=current} 'with' body=CustomElementExprBody |
240
- {infer RelationExpr.source=current} (isBidirectional?='<->' | '->') target=ElementExpr |
241
- {infer OutgoingExpr.from=current} '->'
264
+ ElementDescedantsExpression infers ElementExpression:
265
+ ElementRef (
266
+ {infer ExpandElementExpression.expand=current} DotUnderscore |
267
+ {infer ElementDescedantsExpression.parent=current} DotWildcard
242
268
  )?
243
269
  ;
244
270
 
245
- InOutExpr infers ViewRulePredicateExpr:
246
- IncomingExpr ({infer InOutExpr.inout=current} '->')?;
271
+ RelationExpression:
272
+ InOutRelationExpressions |
273
+ DirectedRelationExpressions
274
+ ;
247
275
 
248
- IncomingExpr:
249
- '->' to=ElementExpr;
276
+ InOutRelationExpressions infers RelationExpression:
277
+ IncomingRelationExpression ({infer InOutRelationExpression.inout=current} '->')?
278
+ ;
250
279
 
280
+ IncomingRelationExpression:
281
+ '->' to=ElementExpression;
251
282
 
252
- ElementExpr:
253
- WildcardExpr |
254
- ElementSelectorExpr |
255
- DescedantsExpr
283
+ DirectedRelationExpressions infers RelationExpression:
284
+ OutgoingRelationExpression ({infer DirectedRelationExpression.source=current} target=ElementExpression)?
256
285
  ;
257
286
 
258
- WildcardExpr:
259
- isWildcard?='*'
287
+ OutgoingRelationExpression:
288
+ from=ElementExpression
289
+ (isBidirectional?='<->' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId])
260
290
  ;
261
291
 
262
- ElementSelectorExpr infers ElementExpr:
263
- 'element' StickyDot (
264
- {infer ElementTagExpr} 'tag' IsEqual tag=[Tag:TagId] |
265
- {infer ElementKindExpr} 'kind' IsEqual kind=[ElementKind]
266
- )
292
+ // Comma-separated list of ElementExpressions
293
+ ElementExpressionsIterator:
294
+ value=ElementExpression ({infer ElementExpressionsIterator.prev=current} ',' (value=ElementExpression)?)*
267
295
  ;
268
296
 
269
- DescedantsExpr infers ElementExpr:
270
- ElementRef (
271
- {infer ExpandElementExpr.parent=current} DotUnderscore |
272
- {infer DescedantsExpr.parent=current} DotWildcard
273
- )?
297
+ DynamicViewRulePredicate:
298
+ 'include' exprs=DynamicViewRulePredicateIterator
299
+ ;
300
+
301
+ DynamicViewRulePredicateIterator:
302
+ value=DynamicViewElementExpressions ({infer DynamicViewRulePredicateIterator.prev=current} ',' (value=DynamicViewElementExpressions)?)*
274
303
  ;
275
304
 
276
- CustomElementExprBody: '{'
305
+ DynamicViewElementExpressions infers DynamicViewElementExpression:
306
+ ElementExpression ({infer CustomElementExpression.target=current} 'with' custom=CustomElementProperties)?
307
+ ;
308
+
309
+ ViewRuleStyle:
310
+ 'style' target=ElementExpressionsIterator '{'
311
+ props+=StyleProperty*
312
+ '}';
313
+
314
+ ViewRuleAutoLayout:
315
+ 'autoLayout' direction=ViewLayoutDirection;
316
+
317
+ CustomElementProperties: '{'
277
318
  props+=(
278
319
  NavigateToProperty |
279
320
  ElementStringProperty |
@@ -282,32 +323,40 @@ CustomElementExprBody: '{'
282
323
  '}'
283
324
  ;
284
325
 
326
+ CustomRelationProperties: '{'
327
+ props+=(
328
+ RelationStringProperty |
329
+ RelationshipStyleProperty
330
+ )*
331
+ '}'
332
+ ;
333
+
285
334
  NavigateToProperty:
286
335
  key='navigateTo' value=ViewRef;
287
336
 
288
337
  // Common properties -------------------------------------
289
338
 
290
339
  LinkProperty:
291
- key='link' Colon? value=Uri SemiColon?;
340
+ key='link' ':'? value=Uri ';'?;
292
341
  ColorProperty:
293
- key='color' Colon? value=ThemeColor SemiColon?;
342
+ key='color' ':'? value=ThemeColor ';'?;
294
343
 
295
344
  OpacityProperty:
296
- key='opacity' Colon? value=Percent SemiColon?;
345
+ key='opacity' ':'? value=Percent ';'?;
297
346
 
298
347
  // Element properties -------------------------------------
299
348
  IconProperty:
300
- key='icon' Colon? value=Uri SemiColon?;
349
+ key='icon' ':'? (libicon=[LibIcon:IconId] | value=Uri) ';'?;
301
350
 
302
351
  ShapeProperty:
303
- key='shape' Colon? value=ElementShape SemiColon?;
352
+ key='shape' ':'? value=ElementShape ';'?;
304
353
 
305
354
 
306
355
  BorderStyleValue returns string:
307
356
  LineOptions | 'none';
308
357
 
309
358
  BorderProperty:
310
- key='border' Colon? value=BorderStyleValue SemiColon?;
359
+ key='border' ':'? value=BorderStyleValue ';'?;
311
360
 
312
361
  StyleProperty:
313
362
  ColorProperty |
@@ -325,9 +374,9 @@ StyleProperties:
325
374
  // -------------------------
326
375
  // Relationship Style Properties
327
376
  LineProperty:
328
- key='line' Colon? value=LineOptions SemiColon?;
377
+ key='line' ':'? value=LineOptions ';'?;
329
378
  ArrowProperty:
330
- key=('head' | 'tail') Colon? value=ArrowType SemiColon?;
379
+ key=('head' | 'tail') ':'? value=ArrowType ';'?;
331
380
 
332
381
  RelationshipStyleProperty:
333
382
  ColorProperty | LineProperty | ArrowProperty;
@@ -342,6 +391,8 @@ ArrowType returns string:
342
391
  'none' |
343
392
  'normal' |
344
393
  'onormal' |
394
+ 'dot' |
395
+ 'odot' |
345
396
  'diamond' |
346
397
  'odiamond' |
347
398
  'crow' |
@@ -353,6 +404,11 @@ ThemeColor returns string:
353
404
  'primary' | 'secondary' | 'muted' | 'slate' | 'blue' | 'indigo' | 'sky' | 'red' | 'gray' | 'green' | 'amber';
354
405
  ElementShape returns string:
355
406
  'rectangle' | 'person' | 'browser' | 'mobile' | 'cylinder' | 'storage' | 'queue';
407
+
408
+ IconId returns string:
409
+ LIB_ICON;
410
+ // ('aws:' | 'gcp:' | 'tech:') IdTerminal;
411
+
356
412
  Uri returns string:
357
413
  URI_WITH_SCHEMA | URI_RELATIVE;
358
414
 
@@ -390,6 +446,9 @@ hidden terminal WS: /\s+/;
390
446
  // Terminals
391
447
  //terminal LineStartWithDash: /(?<=([\r?\n][^\S\r\n]*))-/;
392
448
 
449
+ // LibIcons
450
+ terminal LIB_ICON: /\b(aws|gcp|tech):[-\w]*/;
451
+
393
452
  terminal URI_WITH_SCHEMA: /\w+:\/\/\S+/;
394
453
  terminal URI_RELATIVE: /\.{0,2}\/[^\/]\S+/;
395
454
 
@@ -402,13 +461,10 @@ terminal StickyDot: /\b\./;
402
461
  terminal Dot: '.';
403
462
  terminal NotEqual: /\!\={1,2}/;
404
463
  terminal Eq: /\={1,2}/;
405
- terminal Colon: ':';
406
- terminal SemiColon: ';';
407
- terminal Comma: ',';
408
464
  terminal Percent: /\b\d+%/;
409
465
 
410
466
  terminal String: /"[^"]*"|'[^']*'/;
411
467
 
412
468
  // terminal TagId: HASH LETTER (LETTER | DIGIT | UNDERSCORE | DASH)*;
413
469
  // terminal IdTerminal: (LETTER | UNDERSCORE+ (LETTER | DIGIT)) (LETTER | DIGIT | UNDERSCORE | DASH)*;
414
- terminal IdTerminal: /\b[_]*[a-zA-Z][_-\w]*/;
470
+ terminal IdTerminal: /[_]*[a-zA-Z][-\w]*/;
@@ -0,0 +1,7 @@
1
+ import { LibIcons } from './generated-lib/icons'
2
+
3
+ export const Scheme = 'likec4-lib'
4
+ export const Path = `/icons.c4`
5
+ export const Uri = `${Scheme}://${Path}`
6
+
7
+ export { LibIcons as Content }
@@ -17,10 +17,11 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
17
17
 
18
18
  getSymbols({
19
19
  parseResult: {
20
- value: { specifications, models, views }
20
+ value: { specifications, models, views, likec4lib }
21
21
  }
22
22
  }: LikeC4LangiumDocument): MaybePromise<DocumentSymbol[]> {
23
23
  return [
24
+ ...likec4lib.map(l => () => this.getLikec4LibSymbol(l)),
24
25
  ...specifications.map(s => () => this.getSpecSymbol(s)),
25
26
  ...models.map(s => () => this.getModelSymbol(s)),
26
27
  ...views.map(s => () => this.getModelViewsSymbol(s))
@@ -34,6 +35,22 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
34
35
  })
35
36
  }
36
37
 
38
+ protected getLikec4LibSymbol(astLib: ast.LikeC4Lib): DocumentSymbol[] {
39
+ const cstModel = astLib?.$cstNode
40
+ if (!cstModel) return []
41
+ const children = astLib.icons.map(i => this.getLibIconSymbol(i)).filter(isTruthy)
42
+ if (children.length === 0) return []
43
+ return [
44
+ {
45
+ kind: SymbolKind.Namespace,
46
+ name: 'icons',
47
+ range: cstModel.range,
48
+ selectionRange: GrammarUtils.findNodeForKeyword(cstModel, 'icons')?.range ?? cstModel.range,
49
+ children
50
+ }
51
+ ]
52
+ }
53
+
37
54
  protected getSpecSymbol(astSpec: ast.SpecificationRule): DocumentSymbol[] {
38
55
  const cstModel = astSpec?.$cstNode
39
56
  if (!cstModel) return []
@@ -179,6 +196,16 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
179
196
  }
180
197
  }
181
198
 
199
+ protected getLibIconSymbol(astTag: ast.LibIcon): DocumentSymbol | null {
200
+ if (!astTag.$cstNode || isEmpty(astTag.name)) return null
201
+ return {
202
+ kind: this.symbolKind(astTag),
203
+ name: astTag.name,
204
+ range: astTag.$cstNode.range,
205
+ selectionRange: astTag.$cstNode.range
206
+ }
207
+ }
208
+
182
209
  protected getViewSymbol(astView: ast.LikeC4View): DocumentSymbol[] {
183
210
  const cst = astView?.$cstNode
184
211
  if (!cst) return []
@@ -1,5 +1,6 @@
1
1
  import type { AstNode } from 'langium'
2
2
  import { AbstractSemanticTokenProvider, type SemanticTokenAcceptor } from 'langium/lsp'
3
+ import { isTruthy } from 'remeda'
3
4
  import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-protocol'
4
5
  import { ast } from '../ast'
5
6
 
@@ -16,23 +17,32 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
16
17
  type: SemanticTokenTypes.function
17
18
  })
18
19
  }
20
+ if (ast.isLibIcon(node)) {
21
+ acceptor({
22
+ node,
23
+ property: 'name',
24
+ type: SemanticTokenTypes.type,
25
+ modifier: [SemanticTokenModifiers.definition]
26
+ })
27
+ return 'prune'
28
+ }
29
+
19
30
  if (ast.isRelation(node) && 'kind' in node) {
20
- return acceptor({
31
+ acceptor({
21
32
  node,
22
33
  property: 'kind',
23
34
  type: SemanticTokenTypes.function
24
35
  })
25
36
  }
26
- if (ast.isElementViewRef(node)) {
27
- return acceptor({
37
+ if (ast.isNavigateToProperty(node)) {
38
+ acceptor({
28
39
  node,
29
- property: 'view',
30
- type: SemanticTokenTypes.variable
40
+ property: 'key',
41
+ type: SemanticTokenTypes.property
31
42
  })
32
- }
33
- if (ast.isDescedantsExpr(node) && node.$cstNode) {
34
43
  acceptor({
35
- cst: node.$cstNode,
44
+ node,
45
+ property: 'value',
36
46
  type: SemanticTokenTypes.variable,
37
47
  modifier: [
38
48
  SemanticTokenModifiers.definition,
@@ -41,7 +51,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
41
51
  })
42
52
  return 'prune'
43
53
  }
44
- if (ast.isWildcardExpr(node) && node.$cstNode) {
54
+ if ((ast.isElementDescedantsExpression(node) || ast.isWildcardExpression(node)) && node.$cstNode) {
45
55
  acceptor({
46
56
  cst: node.$cstNode,
47
57
  type: SemanticTokenTypes.variable,
@@ -52,8 +62,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
52
62
  })
53
63
  return 'prune'
54
64
  }
55
-
56
- if (ast.isElementKindExpr(node)) {
65
+ if (ast.isElementKindExpression(node) && isTruthy(node.kind)) {
57
66
  acceptor({
58
67
  node,
59
68
  property: 'kind',
@@ -61,7 +70,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
61
70
  modifier: [SemanticTokenModifiers.definition]
62
71
  })
63
72
  }
64
- if (ast.isElementTagExpr(node)) {
73
+ if (ast.isElementTagExpression(node) && isTruthy(node.tag)) {
65
74
  acceptor({
66
75
  node,
67
76
  property: 'tag',
@@ -98,6 +107,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
98
107
  property: 'value',
99
108
  type: SemanticTokenTypes.interface
100
109
  })
110
+ return
101
111
  }
102
112
  if (ast.isTag(node)) {
103
113
  return acceptor({
@@ -134,6 +144,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
134
144
  property: 'value',
135
145
  type: SemanticTokenTypes.enum
136
146
  })
147
+ return 'prune'
137
148
  }
138
149
  if (ast.isOpacityProperty(node)) {
139
150
  acceptor({
@@ -146,26 +157,34 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
146
157
  property: 'value',
147
158
  type: SemanticTokenTypes.number
148
159
  })
149
- return
160
+ return 'prune'
150
161
  }
151
162
  if (
152
163
  ast.isLinkProperty(node)
153
164
  || ast.isIconProperty(node)
154
- || ast.isElementStringProperty(node)
155
- || ast.isRelationStringProperty(node)
156
- || ast.isViewStringProperty(node)
165
+ || ast.isStringProperty(node)
157
166
  ) {
158
167
  acceptor({
159
168
  node,
160
169
  property: 'key',
161
170
  type: SemanticTokenTypes.property
162
171
  })
163
- acceptor({
164
- node,
165
- property: 'value',
166
- type: SemanticTokenTypes.string
167
- })
168
- return
172
+ if (ast.isIconProperty(node) && node.libicon) {
173
+ acceptor({
174
+ node,
175
+ property: 'libicon',
176
+ type: SemanticTokenTypes.enum
177
+ })
178
+ }
179
+ if ('value' in node) {
180
+ acceptor({
181
+ node,
182
+ property: 'value',
183
+ type: SemanticTokenTypes.string
184
+ })
185
+ }
186
+
187
+ return 'prune'
169
188
  }
170
189
  if (ast.isElement(node)) {
171
190
  return this.highlightAstElement(node, acceptor)
@@ -1,14 +1,38 @@
1
1
  import { AsFqn, type c4, nonexhaustive } from '@likec4/core'
2
- import { MultiMap } from 'langium'
2
+ import { type AstNodeDescription, type AstNodeLocator, AstUtils, CstUtils, GrammarUtils, MultiMap } from 'langium'
3
3
  import { isEmpty, isNullish as isNil } from 'remeda'
4
4
  import { ast, ElementOps, type LikeC4LangiumDocument } from '../ast'
5
5
  import { getFqnElementRef } from '../elementRef'
6
6
  import type { LikeC4Services } from '../module'
7
7
 
8
+ const { findNodeForProperty } = GrammarUtils
9
+ const { toDocumentSegment } = CstUtils
10
+ const { getDocument } = AstUtils
11
+
8
12
  type TraversePair = [el: ast.Element | ast.ExtendElement | ast.Relation, parent: c4.Fqn | null]
9
13
 
14
+ function toAstNodeDescription(
15
+ locator: AstNodeLocator,
16
+ entry: ast.Element,
17
+ doc: LikeC4LangiumDocument
18
+ ): AstNodeDescription {
19
+ const $cstNode = findNodeForProperty(entry.$cstNode, 'name')
20
+ return {
21
+ documentUri: doc.uri,
22
+ name: entry.name,
23
+ ...(entry.$cstNode && {
24
+ selectionSegment: toDocumentSegment(entry.$cstNode)
25
+ }),
26
+ ...($cstNode && {
27
+ nameSegment: toDocumentSegment($cstNode)
28
+ }),
29
+ path: locator.getAstNodePath(entry),
30
+ type: ast.Element
31
+ }
32
+ }
33
+
10
34
  export function computeDocumentFqn(document: LikeC4LangiumDocument, services: LikeC4Services) {
11
- const c4fqns = (document.c4fqns = new MultiMap())
35
+ const c4fqnIndex = (document.c4fqnIndex = new MultiMap())
12
36
  const elements = document.parseResult.value.models.flatMap(m => m.elements)
13
37
  if (elements.length === 0) {
14
38
  return
@@ -30,11 +54,9 @@ export function computeDocumentFqn(document: LikeC4LangiumDocument, services: Li
30
54
  }
31
55
  if (ast.isElement(el)) {
32
56
  const fqn = AsFqn(el.name, parent)
33
- const path = locator.getAstNodePath(el)
34
- c4fqns.add(fqn, {
35
- el: new WeakRef(el),
36
- path,
37
- name: el.name
57
+ c4fqnIndex.add(fqn, {
58
+ ...toAstNodeDescription(locator, el, document),
59
+ fqn
38
60
  })
39
61
  ElementOps.writeId(el, fqn)
40
62
  if (!isNil(el.body) && !isEmpty(el.body.elements)) {
@@ -1,8 +1,8 @@
1
1
  import type { Fqn } from '@likec4/core'
2
2
  import { nameFromFqn, parentFqn } from '@likec4/core'
3
- import type { LangiumDocuments, Stream } from 'langium'
3
+ import type { AstNodeDescription, LangiumDocuments, Stream } from 'langium'
4
4
  import { DocumentState, DONE_RESULT, MultiMap, stream, StreamImpl } from 'langium'
5
- import type { ast, FqnIndexedDocument } from '../ast'
5
+ import type { ast, DocFqnIndexAstNodeDescription, FqnIndexedDocument } from '../ast'
6
6
  import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from '../ast'
7
7
  import { logError, logger } from '../logger'
8
8
  import type { LikeC4Services } from '../module'
@@ -17,8 +17,6 @@ export interface FqnIndexEntry {
17
17
  path: string
18
18
  }
19
19
 
20
- const True = () => true
21
-
22
20
  export class FqnIndex {
23
21
  protected langiumDocuments: LangiumDocuments
24
22
 
@@ -31,7 +29,7 @@ export class FqnIndex {
31
29
  logger.debug(`[FqnIndex] onIndexedContent ${docs.length}:\n` + printDocs(docs))
32
30
  for (const doc of docs) {
33
31
  if (isLikeC4LangiumDocument(doc)) {
34
- delete doc.c4fqns
32
+ delete doc.c4fqnIndex
35
33
  delete doc.c4Elements
36
34
  delete doc.c4Specification
37
35
  delete doc.c4Relations
@@ -53,18 +51,13 @@ export class FqnIndex {
53
51
  return this.langiumDocuments.all.filter(isFqnIndexedDocument)
54
52
  }
55
53
 
56
- private entries(filterByFqn: (fqn: Fqn) => boolean = True): Stream<FqnIndexEntry> {
57
- return this.documents.flatMap(doc =>
58
- doc.c4fqns.entries().flatMap(([fqn, entry]): FqnIndexEntry | FqnIndexEntry[] => {
59
- if (filterByFqn(fqn)) {
60
- const el = entry.el.deref()
61
- if (el) {
62
- return { ...entry, fqn, el, doc }
63
- }
64
- }
65
- return []
66
- })
67
- )
54
+ private entries(filterByFqn?: (fqn: Fqn) => boolean): Stream<DocFqnIndexAstNodeDescription> {
55
+ return this.documents.flatMap(doc => {
56
+ if (filterByFqn) {
57
+ return doc.c4fqnIndex.keys().filter(filterByFqn).flatMap(fqn => doc.c4fqnIndex.get(fqn))
58
+ }
59
+ return doc.c4fqnIndex.values()
60
+ })
68
61
  }
69
62
 
70
63
  public getFqn(el: ast.Element): Fqn | null {
@@ -83,22 +76,16 @@ export class FqnIndex {
83
76
  // return fqn
84
77
  }
85
78
 
86
- public byFqn(fqn: Fqn): Stream<FqnIndexEntry> {
79
+ public byFqn(fqn: Fqn): Stream<AstNodeDescription> {
87
80
  return this.documents.flatMap(doc => {
88
- return doc.c4fqns.get(fqn).flatMap(entry => {
89
- const el = entry.el.deref()
90
- if (el) {
91
- return { fqn, el, doc, path: entry.path, name: entry.name }
92
- }
93
- return []
94
- })
81
+ return doc.c4fqnIndex.get(fqn)
95
82
  })
96
83
  }
97
84
 
98
- public directChildrenOf(parent: Fqn): Stream<FqnIndexEntry> {
85
+ public directChildrenOf(parent: Fqn): Stream<AstNodeDescription> {
99
86
  return stream([parent]).flatMap(_parent => {
100
87
  const children = this.entries(fqn => parentFqn(fqn) === _parent)
101
- .map((entry): [string, FqnIndexEntry] => [entry.name, entry])
88
+ .map((entry) => [entry.name, entry] as [string, AstNodeDescription])
102
89
  .toArray()
103
90
  if (children.length === 0) {
104
91
  return []
@@ -113,15 +100,15 @@ export class FqnIndex {
113
100
  /**
114
101
  * Returns descedant elements with unique names in the scope
115
102
  */
116
- public uniqueDescedants(parent: Fqn): Stream<FqnIndexEntry> {
103
+ public uniqueDescedants(parent: Fqn): Stream<AstNodeDescription> {
117
104
  return new StreamImpl(
118
105
  () => {
119
106
  const prefix = `${parent}.`
120
107
 
121
108
  const childrenNames = new Set<string>()
122
- const descedants = [] as FqnIndexEntry[]
109
+ const descedants = [] as AstNodeDescription[]
123
110
 
124
- const nested = new MultiMap<string, FqnIndexEntry>()
111
+ const nested = new MultiMap<string, AstNodeDescription>()
125
112
 
126
113
  this.entries(f => f.startsWith(prefix)).forEach(e => {
127
114
  const name = nameFromFqn(e.fqn)
@@ -154,7 +141,7 @@ export class FqnIndex {
154
141
  if (iterator) {
155
142
  return iterator.next()
156
143
  }
157
- return DONE_RESULT as IteratorResult<FqnIndexEntry>
144
+ return DONE_RESULT as IteratorResult<AstNodeDescription>
158
145
  }
159
146
  )
160
147
  }