@likec4/language-server 1.9.0 → 1.10.1

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 (52) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +3 -4
  4. package/dist/browser.d.mts +3 -4
  5. package/dist/browser.d.ts +3 -4
  6. package/dist/browser.mjs +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.mjs +1 -1
  14. package/dist/node.cjs +1 -1
  15. package/dist/node.d.cts +3 -4
  16. package/dist/node.d.mts +3 -4
  17. package/dist/node.d.ts +3 -4
  18. package/dist/node.mjs +1 -1
  19. package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.BFBeyvV8.mjs} +486 -108
  20. package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.BGy3FJPJ.d.cts} +43 -14
  21. package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.Bfc-5M8A.cjs} +482 -104
  22. package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.CbqwHp7Q.mjs} +184 -120
  23. package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CnVuAxDh.d.ts} +43 -14
  24. package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.DEK39RmI.d.mts} +43 -14
  25. package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DJhoJBWh.cjs} +180 -116
  26. package/package.json +13 -11
  27. package/src/ast.ts +8 -6
  28. package/src/formatting/LikeC4Formatter.ts +390 -0
  29. package/src/formatting/utils.ts +26 -0
  30. package/src/generated/ast.ts +203 -11
  31. package/src/generated/grammar.ts +2 -2
  32. package/src/generated/module.ts +1 -1
  33. package/src/like-c4.langium +34 -7
  34. package/src/lsp/CompletionProvider.ts +1 -1
  35. package/src/lsp/DocumentLinkProvider.ts +27 -15
  36. package/src/lsp/SemanticTokenProvider.ts +1 -1
  37. package/src/lsp/index.ts +1 -1
  38. package/src/model/fqn-index.ts +0 -1
  39. package/src/model/model-builder.ts +43 -32
  40. package/src/model/model-parser.ts +43 -21
  41. package/src/model-graph/compute-view/compute.ts +111 -80
  42. package/src/model-graph/compute-view/predicates.ts +3 -5
  43. package/src/model-graph/dynamic-view/compute.ts +96 -60
  44. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  45. package/src/model-graph/utils/uniqueTags.test.ts +42 -0
  46. package/src/model-graph/utils/uniqueTags.ts +19 -0
  47. package/src/module.ts +6 -9
  48. package/src/test/testServices.ts +27 -7
  49. package/src/validation/index.ts +2 -1
  50. package/src/validation/property-checks.ts +13 -1
  51. package/src/validation/specification.ts +3 -3
  52. package/src/view-utils/resolve-relative-paths.ts +14 -17
@@ -3,13 +3,13 @@ import { Fqn, ViewID } from '@likec4/core';
3
3
  import { AstNode, Reference, LangiumDocument, MultiMap, AstNodeDescription, DiagnosticInfo, ReferenceDescription, MaybePromise, DefaultScopeComputation, PrecomputedScopes, DefaultScopeProvider, ReferenceInfo, Scope, Disposable, DefaultWorkspaceManager, WorkspaceCache, Module, LangiumDocuments, Stream, Cancellation, URI as URI$1 } from 'langium';
4
4
  import { SetRequired, ValueOf, ConditionalPick } from 'type-fest';
5
5
  import { Diagnostic, DocumentSymbol, SymbolKind, Hover, Location, Range, TextEdit, CompletionItemKind } from 'vscode-languageserver-types';
6
- import { CodeLensProvider, DefaultCompletionProvider, DefaultDocumentHighlightProvider, DocumentLinkProvider, DocumentSymbolProvider, NodeKindProvider as NodeKindProvider$1, AstNodeHoverProvider, DefaultRenameProvider, AbstractSemanticTokenProvider, SemanticTokenAcceptor, LangiumSharedServices, DefaultWorkspaceSymbolProvider, LangiumServices, PartialLangiumServices, DefaultSharedModuleContext } from 'langium/lsp';
6
+ import { CodeLensProvider, DefaultCompletionProvider, DefaultDocumentHighlightProvider, DocumentLinkProvider, DocumentSymbolProvider, NodeKindProvider as NodeKindProvider$1, AstNodeHoverProvider, AbstractSemanticTokenProvider, SemanticTokenAcceptor, LangiumSharedServices, DefaultWorkspaceSymbolProvider, LangiumServices, PartialLangiumServices, DefaultSharedModuleContext } from 'langium/lsp';
7
7
  import { CodeLensParams, CancellationToken, CodeLens, DocumentHighlight, DocumentLinkParams, DocumentLink, WorkspaceFolder } from 'vscode-languageserver';
8
8
  import { ChangeViewRequestParams } from '../protocol.mjs';
9
9
  import { URI } from 'vscode-uri';
10
10
 
11
11
  /******************************************************************************
12
- * This file was generated by langium-cli 3.1.1.
12
+ * This file was generated by langium-cli 3.2.0.
13
13
  * DO NOT EDIT MANUALLY!
14
14
  ******************************************************************************/
15
15
 
@@ -48,12 +48,12 @@ type RelationPredicate = RelationPredicateOrWhere | RelationPredicateWith;
48
48
  declare const RelationPredicate = "RelationPredicate";
49
49
  type RelationPredicateOrWhere = RelationExpression | RelationPredicateWhere;
50
50
  declare const RelationPredicateOrWhere = "RelationPredicateOrWhere";
51
- type RelationProperty = LinkProperty | MetadataProperty | RelationStringProperty | RelationStyleProperty;
51
+ type RelationProperty = LinkProperty | MetadataProperty | RelationNavigateToProperty | RelationStringProperty | RelationStyleProperty;
52
52
  declare const RelationProperty = "RelationProperty";
53
53
  declare function isRelationProperty(item: unknown): item is RelationProperty;
54
54
  type RelationshipStyleProperty = ArrowProperty | ColorProperty | LineProperty;
55
55
  declare const RelationshipStyleProperty = "RelationshipStyleProperty";
56
- type StringProperty = ElementStringProperty | MetadataAttribute | NotationProperty | RelationStringProperty | SpecificationElementStringProperty | SpecificationRelationshipStringProperty | ViewStringProperty;
56
+ type StringProperty = ElementStringProperty | MetadataAttribute | NotationProperty | NotesProperty | RelationStringProperty | SpecificationElementStringProperty | SpecificationRelationshipStringProperty | ViewStringProperty;
57
57
  declare const StringProperty = "StringProperty";
58
58
  type StyleProperty = BorderProperty | ColorProperty | IconProperty | OpacityProperty | ShapeProperty;
59
59
  declare const StyleProperty = "StyleProperty";
@@ -120,7 +120,7 @@ declare const CustomElementProperties = "CustomElementProperties";
120
120
  interface CustomRelationProperties extends AstNode {
121
121
  readonly $container: DynamicViewStep | RelationPredicateWith;
122
122
  readonly $type: 'CustomRelationProperties';
123
- props: Array<NotationProperty | RelationStringProperty | RelationshipStyleProperty>;
123
+ props: Array<NotationProperty | NotesProperty | RelationNavigateToProperty | RelationStringProperty | RelationshipStyleProperty>;
124
124
  }
125
125
  declare const CustomRelationProperties = "CustomRelationProperties";
126
126
  interface DirectedRelationExpression extends AstNode {
@@ -142,7 +142,7 @@ interface DynamicViewBody extends AstNode {
142
142
  readonly $type: 'DynamicViewBody';
143
143
  props: Array<ViewProperty>;
144
144
  rules: Array<DynamicViewRule>;
145
- steps: Array<DynamicViewStep>;
145
+ steps: Array<DynamicViewParallelSteps | DynamicViewStep>;
146
146
  tags?: Tags;
147
147
  }
148
148
  declare const DynamicViewBody = "DynamicViewBody";
@@ -153,6 +153,13 @@ interface DynamicViewIncludePredicate extends AstNode {
153
153
  predicates: DynamicViewPredicateIterator;
154
154
  }
155
155
  declare const DynamicViewIncludePredicate = "DynamicViewIncludePredicate";
156
+ interface DynamicViewParallelSteps extends AstNode {
157
+ readonly $container: DynamicViewBody;
158
+ readonly $type: 'DynamicViewParallelSteps';
159
+ steps: Array<DynamicViewStep>;
160
+ }
161
+ declare const DynamicViewParallelSteps = "DynamicViewParallelSteps";
162
+ declare function isDynamicViewParallelSteps(item: unknown): item is DynamicViewParallelSteps;
156
163
  interface DynamicViewPredicateIterator extends AstNode {
157
164
  readonly $container: DynamicViewIncludePredicate | DynamicViewPredicateIterator;
158
165
  readonly $type: 'DynamicViewPredicateIterator';
@@ -161,8 +168,14 @@ interface DynamicViewPredicateIterator extends AstNode {
161
168
  }
162
169
  declare const DynamicViewPredicateIterator = "DynamicViewPredicateIterator";
163
170
  declare function isDynamicViewPredicateIterator(item: unknown): item is DynamicViewPredicateIterator;
171
+ interface DynamicViewRef extends AstNode {
172
+ readonly $container: RelationNavigateToProperty;
173
+ readonly $type: 'DynamicViewRef';
174
+ view: Reference<DynamicView>;
175
+ }
176
+ declare const DynamicViewRef = "DynamicViewRef";
164
177
  interface DynamicViewStep extends AstNode {
165
- readonly $container: DynamicViewBody;
178
+ readonly $container: DynamicViewBody | DynamicViewParallelSteps;
166
179
  readonly $type: 'DynamicViewStep';
167
180
  custom?: CustomRelationProperties;
168
181
  isBackward: boolean;
@@ -423,6 +436,13 @@ interface NotationProperty extends AstNode {
423
436
  value: string;
424
437
  }
425
438
  declare const NotationProperty = "NotationProperty";
439
+ interface NotesProperty extends AstNode {
440
+ readonly $container: CustomRelationProperties;
441
+ readonly $type: 'NotesProperty';
442
+ key: 'notes';
443
+ value: string;
444
+ }
445
+ declare const NotesProperty = "NotesProperty";
426
446
  interface OpacityProperty extends AstNode {
427
447
  readonly $container: CustomElementProperties | ElementStyleProperty | ViewRuleStyle;
428
448
  readonly $type: 'OpacityProperty';
@@ -466,6 +486,13 @@ interface RelationBody extends AstNode {
466
486
  }
467
487
  declare const RelationBody = "RelationBody";
468
488
  declare function isRelationBody(item: unknown): item is RelationBody;
489
+ interface RelationNavigateToProperty extends AstNode {
490
+ readonly $container: CustomRelationProperties | RelationBody;
491
+ readonly $type: 'RelationNavigateToProperty';
492
+ key: 'navigateTo';
493
+ value: DynamicViewRef;
494
+ }
495
+ declare const RelationNavigateToProperty = "RelationNavigateToProperty";
469
496
  interface RelationPredicateWhere extends AstNode {
470
497
  readonly $container: Predicates | RelationPredicateWith;
471
498
  readonly $type: 'RelationPredicateWhere';
@@ -673,7 +700,9 @@ type LikeC4AstType = {
673
700
  DynamicView: DynamicView;
674
701
  DynamicViewBody: DynamicViewBody;
675
702
  DynamicViewIncludePredicate: DynamicViewIncludePredicate;
703
+ DynamicViewParallelSteps: DynamicViewParallelSteps;
676
704
  DynamicViewPredicateIterator: DynamicViewPredicateIterator;
705
+ DynamicViewRef: DynamicViewRef;
677
706
  DynamicViewRule: DynamicViewRule;
678
707
  DynamicViewStep: DynamicViewStep;
679
708
  Element: Element;
@@ -717,6 +746,7 @@ type LikeC4AstType = {
717
746
  ModelViews: ModelViews;
718
747
  NavigateToProperty: NavigateToProperty;
719
748
  NotationProperty: NotationProperty;
749
+ NotesProperty: NotesProperty;
720
750
  OpacityProperty: OpacityProperty;
721
751
  OutgoingRelationExpression: OutgoingRelationExpression;
722
752
  Predicate: Predicate;
@@ -724,6 +754,7 @@ type LikeC4AstType = {
724
754
  Relation: Relation;
725
755
  RelationBody: RelationBody;
726
756
  RelationExpression: RelationExpression;
757
+ RelationNavigateToProperty: RelationNavigateToProperty;
727
758
  RelationPredicate: RelationPredicate;
728
759
  RelationPredicateOrWhere: RelationPredicateOrWhere;
729
760
  RelationPredicateWhere: RelationPredicateWhere;
@@ -836,6 +867,7 @@ interface ParsedAstRelation {
836
867
  head?: c4.RelationshipArrowType;
837
868
  tail?: c4.RelationshipArrowType;
838
869
  links?: c4.NonEmptyArray<ParsedLink>;
870
+ navigateTo?: c4.ViewID;
839
871
  metadata?: {
840
872
  [key: string]: string;
841
873
  };
@@ -861,7 +893,7 @@ interface ParsedAstDynamicView {
861
893
  description: string | null;
862
894
  tags: c4.NonEmptyArray<c4.Tag> | null;
863
895
  links: c4.NonEmptyArray<ParsedLink> | null;
864
- steps: c4.DynamicViewStep[];
896
+ steps: c4.DynamicViewStepOrParallel[];
865
897
  rules: Array<c4.DynamicViewRule>;
866
898
  manualLayout?: c4.ViewManualLayout;
867
899
  }
@@ -891,7 +923,7 @@ interface ParsedLikeC4LangiumDocument extends Omit<LangiumDocument<LikeC4Grammar
891
923
  }
892
924
  type Guard<N extends AstNode> = (n: AstNode) => n is N;
893
925
  type Guarded<G> = G extends Guard<infer N> ? N : never;
894
- declare const isValidatableAstNode: (n: AstNode) => n is Guarded<typeof isRelation | typeof isExtendElement | typeof isElement | typeof isLikeC4View | typeof isDynamicViewPredicateIterator | typeof isElementPredicateWith | typeof isRelationPredicateWith | typeof isElementExpression | typeof isRelationExpression | typeof isDynamicViewStep | typeof isViewProperty | typeof isStyleProperty | typeof isTags | typeof isViewRule | typeof isDynamicViewRule | typeof isElementViewBody | typeof isDynamicViewBody | typeof isRelationProperty | typeof isRelationBody | typeof isElementProperty | typeof isElementBody | typeof isExtendElementBody | typeof isSpecificationElementKind | typeof isSpecificationRelationshipKind | typeof isSpecificationTag | typeof isSpecificationColor | typeof isSpecificationRule | typeof isModelViews | typeof isModel>;
926
+ declare const isValidatableAstNode: (n: AstNode) => n is Guarded<typeof isRelation | typeof isExtendElement | typeof isElement | typeof isLikeC4View | typeof isDynamicViewPredicateIterator | typeof isElementPredicateWith | typeof isRelationPredicateWith | typeof isElementExpression | typeof isRelationExpression | typeof isDynamicViewParallelSteps | typeof isDynamicViewStep | typeof isViewProperty | typeof isStyleProperty | typeof isTags | typeof isViewRule | typeof isDynamicViewRule | typeof isElementViewBody | typeof isDynamicViewBody | typeof isRelationProperty | typeof isRelationBody | typeof isElementProperty | typeof isElementBody | typeof isExtendElementBody | typeof isSpecificationElementKind | typeof isSpecificationRelationshipKind | typeof isSpecificationTag | typeof isSpecificationColor | typeof isSpecificationRule | typeof isModelViews | typeof isModel>;
895
927
  type ValidatableAstNode = Guarded<typeof isValidatableAstNode>;
896
928
  declare function checksFromDiagnostics(doc: LikeC4LangiumDocument): {
897
929
  isValid: (n: ValidatableAstNode) => boolean;
@@ -923,6 +955,7 @@ declare class LikeC4DocumentLinkProvider implements DocumentLinkProvider {
923
955
  constructor(services: LikeC4Services);
924
956
  getDocumentLinks(doc: LangiumDocument, _params: DocumentLinkParams): MaybePromise<DocumentLink[]>;
925
957
  resolveLink(doc: LangiumDocument, link: string): string;
958
+ relativeLink(doc: LangiumDocument, link: string): string | null;
926
959
  }
927
960
 
928
961
  declare class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
@@ -950,10 +983,6 @@ declare class LikeC4HoverProvider extends AstNodeHoverProvider {
950
983
  protected getAstNodeHoverContent(node: AstNode): MaybePromise<Hover | undefined>;
951
984
  }
952
985
 
953
- declare class LikeC4RenameProvider extends DefaultRenameProvider {
954
- constructor(services: LikeC4Services);
955
- }
956
-
957
986
  declare class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
958
987
  protected highlightElement(node: AstNode, acceptor: SemanticTokenAcceptor): void | undefined | 'prune';
959
988
  private highlightAstElement;
@@ -1061,7 +1090,6 @@ interface LikeC4AddedServices {
1061
1090
  ModelChanges: LikeC4ModelChanges;
1062
1091
  };
1063
1092
  lsp: {
1064
- RenameProvider: LikeC4RenameProvider;
1065
1093
  CompletionProvider: LikeC4CompletionProvider;
1066
1094
  DocumentHighlightProvider: LikeC4DocumentHighlightProvider;
1067
1095
  DocumentSymbolProvider: LikeC4DocumentSymbolProvider;
@@ -1181,6 +1209,7 @@ declare class LikeC4ModelParser {
1181
1209
  private parseRelationExpr;
1182
1210
  private parseViewRule;
1183
1211
  private parseViewManualLaout;
1212
+ private parseDynamicParallelSteps;
1184
1213
  private parseDynamicStep;
1185
1214
  private parseElementView;
1186
1215
  private parseDynamicElementView;
@@ -295,8 +295,8 @@ function buildElementNotations(nodes) {
295
295
  }))
296
296
  ),
297
297
  remeda.sortBy(
298
- remeda.prop("title"),
299
298
  remeda.prop("shape"),
299
+ remeda.prop("title"),
300
300
  [
301
301
  (n) => n.kinds.length,
302
302
  "desc"
@@ -373,6 +373,16 @@ function sortNodes({
373
373
  return sorted;
374
374
  }
375
375
 
376
+ function uniqueTags(elements) {
377
+ const tags = remeda.pipe(
378
+ elements,
379
+ remeda.flatMap((e) => e.tags ?? []),
380
+ remeda.unique(),
381
+ remeda.sort(core.compareNatural)
382
+ );
383
+ return core.hasAtLeast(tags, 1) ? tags : null;
384
+ }
385
+
376
386
  const NoFilter = () => true;
377
387
  const Identity = (x) => x;
378
388
  const filterBy = (pred) => pred === NoFilter ? Identity : remeda.filter(pred);
@@ -621,13 +631,11 @@ const filterEdges = (edges, where) => {
621
631
  );
622
632
  };
623
633
  const filterRelations = (edges, where) => {
624
- if (!where) {
625
- return edges.flatMap((e) => e.relations);
626
- }
627
634
  return remeda.pipe(
628
635
  edges,
629
636
  remeda.flatMap((e) => e.relations),
630
- remeda.filter(where)
637
+ where ? remeda.filter(where) : Identity,
638
+ remeda.unique()
631
639
  );
632
640
  };
633
641
  function includeIncomingExpr(expr, where) {
@@ -875,7 +883,7 @@ class ComputeCtx {
875
883
  computeEdges() {
876
884
  return this.ctxEdges.map((e) => {
877
885
  core.invariant(remeda.hasAtLeast(e.relations, 1), "Edge must have at least one relation");
878
- const relations = e.relations.toSorted(core.compareRelations);
886
+ const relations = remeda.sort(e.relations, core.compareRelations);
879
887
  const source = e.source.id;
880
888
  const target = e.target.id;
881
889
  const edge = {
@@ -887,50 +895,68 @@ class ComputeCtx {
887
895
  relations: relations.map((r) => r.id)
888
896
  };
889
897
  let relation;
890
- if (relations.length === 1) {
891
- relation = relations[0];
892
- } else {
893
- relation = relations.find((r) => r.source === source && r.target === target);
894
- }
898
+ relation = remeda.only(relations) ?? remeda.pipe(
899
+ relations,
900
+ remeda.filter((r) => r.source === source && r.target === target),
901
+ remeda.only()
902
+ );
895
903
  if (!relation) {
896
- relation = relations.reduce((acc, r) => {
897
- if (r.color && acc.color !== r.color) {
898
- acc.color = void 0;
899
- }
900
- if (r.kind && acc.kind !== r.kind) {
901
- acc.kind = void 0;
902
- }
903
- if (r.head && acc.head !== r.head) {
904
- acc.head = void 0;
905
- }
906
- if (r.tail && acc.tail !== r.tail) {
907
- acc.tail = void 0;
908
- }
909
- if (r.line && acc.line !== r.line) {
910
- acc.line = void 0;
911
- }
912
- if (r.description && acc.description !== r.description) {
913
- acc.description = void 0;
914
- }
915
- if (r.technology && acc.technology !== r.technology) {
916
- acc.technology = void 0;
917
- }
918
- if (remeda.isTruthy(r.title) && acc.title !== r.title) {
919
- acc.title = "[...]";
920
- }
921
- return acc;
922
- }, {
923
- title: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.title) ? r.title : [])) ?? "[...]",
924
- description: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.description) ? r.description : [])),
925
- technology: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.technology) ? r.technology : [])),
926
- kind: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.kind) ? r.kind : [])),
927
- head: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.head) ? r.head : [])),
928
- tail: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.tail) ? r.tail : [])),
929
- color: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.color) ? r.color : [])),
930
- line: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.line) ? r.line : []))
931
- });
904
+ const allprops = remeda.pipe(
905
+ relations,
906
+ remeda.reduce((acc, r) => {
907
+ if (remeda.isTruthy(r.title) && !acc.title.includes(r.title)) {
908
+ acc.title.push(r.title);
909
+ }
910
+ if (remeda.isTruthy(r.description) && !acc.description.includes(r.description)) {
911
+ acc.description.push(r.description);
912
+ }
913
+ if (remeda.isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
914
+ acc.technology.push(r.technology);
915
+ }
916
+ if (remeda.isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
917
+ acc.kind.push(r.kind);
918
+ }
919
+ if (remeda.isTruthy(r.color) && !acc.color.includes(r.color)) {
920
+ acc.color.push(r.color);
921
+ }
922
+ if (remeda.isTruthy(r.line) && !acc.line.includes(r.line)) {
923
+ acc.line.push(r.line);
924
+ }
925
+ if (remeda.isTruthy(r.head) && !acc.head.includes(r.head)) {
926
+ acc.head.push(r.head);
927
+ }
928
+ if (remeda.isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
929
+ acc.tail.push(r.tail);
930
+ }
931
+ if (remeda.isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
932
+ acc.navigateTo.push(r.navigateTo);
933
+ }
934
+ return acc;
935
+ }, {
936
+ title: [],
937
+ description: [],
938
+ technology: [],
939
+ kind: [],
940
+ head: [],
941
+ tail: [],
942
+ color: [],
943
+ line: [],
944
+ navigateTo: []
945
+ })
946
+ );
947
+ relation = {
948
+ title: remeda.only(allprops.title) ?? "[...]",
949
+ description: remeda.only(allprops.description),
950
+ technology: remeda.only(allprops.technology),
951
+ kind: remeda.only(allprops.kind),
952
+ head: remeda.only(allprops.head),
953
+ tail: remeda.only(allprops.tail),
954
+ color: remeda.only(allprops.color),
955
+ line: remeda.only(allprops.line),
956
+ navigateTo: remeda.only(allprops.navigateTo)
957
+ };
932
958
  }
933
- const tags = remeda.unique(remeda.flatMap(relations, (r) => r.tags ?? []));
959
+ const tags = uniqueTags(relations);
934
960
  return Object.assign(
935
961
  edge,
936
962
  this.getEdgeLabel(relation),
@@ -941,7 +967,8 @@ class ComputeCtx {
941
967
  relation.line && { line: relation.line },
942
968
  relation.head && { head: relation.head },
943
969
  relation.tail && { tail: relation.tail },
944
- remeda.hasAtLeast(tags, 1) && { tags }
970
+ relation.navigateTo && { navigateTo: relation.navigateTo },
971
+ tags && { tags }
945
972
  );
946
973
  });
947
974
  }
@@ -1002,28 +1029,39 @@ class ComputeCtx {
1002
1029
  this.implicits.delete(el);
1003
1030
  }
1004
1031
  }
1005
- excludeImplicit(...excludes) {
1006
- for (const el of excludes) {
1007
- this.implicits.delete(el);
1008
- }
1009
- }
1032
+ // protected excludeImplicit(...excludes: Element[]) {
1033
+ // for (const el of excludes) {
1034
+ // this.implicits.delete(el)
1035
+ // }
1036
+ // }
1010
1037
  excludeRelation(...relations) {
1038
+ if (relations.length === 0) {
1039
+ return;
1040
+ }
1011
1041
  const excludedImplicits = /* @__PURE__ */ new Set();
1012
- for (const relation of relations) {
1013
- let edge;
1014
- while (edge = this.ctxEdges.find((e) => e.relations.includes(relation))) {
1015
- if (edge.relations.length === 1) {
1042
+ const ctxEdges = remeda.pipe(
1043
+ this.ctxEdges,
1044
+ remeda.map((edge) => {
1045
+ const edgerelations = edge.relations.filter((r) => !relations.includes(r));
1046
+ if (edgerelations.length === 0) {
1016
1047
  excludedImplicits.add(edge.source);
1017
1048
  excludedImplicits.add(edge.target);
1018
- this.ctxEdges.splice(this.ctxEdges.indexOf(edge), 1);
1019
- continue;
1049
+ return null;
1020
1050
  }
1021
- edge.relations = edge.relations.filter((r) => r !== relation);
1022
- }
1023
- }
1051
+ if (edgerelations.length !== edge.relations.length) {
1052
+ return {
1053
+ ...edge,
1054
+ relations: edgerelations
1055
+ };
1056
+ }
1057
+ return edge;
1058
+ }),
1059
+ remeda.filter(remeda.isNonNull)
1060
+ );
1024
1061
  if (excludedImplicits.size === 0) {
1025
1062
  return;
1026
1063
  }
1064
+ this.ctxEdges = ctxEdges;
1027
1065
  const remaining = this.includedElements;
1028
1066
  if (remaining.size === 0) {
1029
1067
  this.implicits.clear();
@@ -1167,7 +1205,7 @@ class ComputeCtx {
1167
1205
  if (remeda.isTruthy(relation.technology)) {
1168
1206
  labelParts.push(`[${relation.technology}]`);
1169
1207
  }
1170
- return labelParts.length > 0 && { label: labelParts.join("\n") };
1208
+ return labelParts.length > 0 ? { label: labelParts.join("\n") } : {};
1171
1209
  }
1172
1210
  }
1173
1211
 
@@ -1200,6 +1238,38 @@ class DynamicViewComputeCtx {
1200
1238
  static compute(view, graph) {
1201
1239
  return new DynamicViewComputeCtx(view, graph).compute();
1202
1240
  }
1241
+ addStep({
1242
+ source: stepSource,
1243
+ target: stepTarget,
1244
+ title: stepTitle,
1245
+ isBackward,
1246
+ navigateTo: stepNavigateTo,
1247
+ ...step
1248
+ }, index, parent) {
1249
+ const id = parent ? core.StepEdgeId(parent, index) : core.StepEdgeId(index);
1250
+ const source = this.graph.element(stepSource);
1251
+ const target = this.graph.element(stepTarget);
1252
+ this.explicits.add(source);
1253
+ this.explicits.add(target);
1254
+ const {
1255
+ title,
1256
+ relations,
1257
+ tags,
1258
+ navigateTo: derivedNavigateTo
1259
+ } = this.findRelations(source, target);
1260
+ const navigateTo = remeda.isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo;
1261
+ this.steps.push({
1262
+ id,
1263
+ ...step,
1264
+ source,
1265
+ target,
1266
+ title: stepTitle ?? title,
1267
+ relations: relations ?? [],
1268
+ isBackward: isBackward ?? false,
1269
+ ...navigateTo ? { navigateTo } : {},
1270
+ ...tags ? { tags } : {}
1271
+ });
1272
+ }
1203
1273
  compute() {
1204
1274
  const {
1205
1275
  docUri: _docUri,
@@ -1208,27 +1278,21 @@ class DynamicViewComputeCtx {
1208
1278
  steps: viewSteps,
1209
1279
  ...view
1210
1280
  } = this.view;
1211
- for (let {
1212
- source: stepSource,
1213
- target: stepTarget,
1214
- title: stepTitle,
1215
- isBackward,
1216
- ...step
1217
- } of viewSteps) {
1218
- const source = this.graph.element(stepSource);
1219
- const target = this.graph.element(stepTarget);
1220
- this.explicits.add(source);
1221
- this.explicits.add(target);
1222
- const { title, relations, tags } = this.findRelations(source, target);
1223
- this.steps.push({
1224
- ...step,
1225
- source,
1226
- target,
1227
- title: remeda.isTruthy(stepTitle) ? stepTitle : title,
1228
- relations: relations ?? [],
1229
- isBackward: isBackward ?? false,
1230
- ...tags ? { tags } : {}
1231
- });
1281
+ let stepNum = 1;
1282
+ for (const step of viewSteps) {
1283
+ if (core.isDynamicViewParallelSteps(step)) {
1284
+ if (step.__parallel.length === 0) {
1285
+ continue;
1286
+ }
1287
+ if (step.__parallel.length === 1) {
1288
+ this.addStep(step.__parallel[0], stepNum);
1289
+ } else {
1290
+ step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum));
1291
+ }
1292
+ } else {
1293
+ this.addStep(step, stepNum);
1294
+ }
1295
+ stepNum++;
1232
1296
  }
1233
1297
  for (const rule of rules) {
1234
1298
  if (core.isDynamicViewIncludeRule(rule)) {
@@ -1240,12 +1304,10 @@ class DynamicViewComputeCtx {
1240
1304
  }
1241
1305
  const elements = [...this.explicits];
1242
1306
  const nodesMap = buildComputeNodes(elements);
1243
- const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }, index) => {
1307
+ const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
1244
1308
  const sourceNode = core.nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`);
1245
1309
  const targetNode = core.nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`);
1246
- const stepNum = index + 1;
1247
1310
  const edge = {
1248
- id: core.StepEdgeId(stepNum),
1249
1311
  parent: core.commonAncestor(source.id, target.id),
1250
1312
  source: source.id,
1251
1313
  target: target.id,
@@ -1302,41 +1364,43 @@ class DynamicViewComputeCtx {
1302
1364
  }
1303
1365
  findRelations(source, target) {
1304
1366
  const relationships = remeda.unique(this.graph.edgesBetween(source, target).flatMap((e) => e.relations));
1305
- const alltags = remeda.unique(relationships.flatMap((r) => r.tags ?? []));
1306
- const tags = remeda.hasAtLeast(alltags, 1) ? alltags : null;
1307
- const relations = remeda.hasAtLeast(relationships, 1) ? remeda.map(relationships, (r) => r.id) : null;
1308
1367
  if (relationships.length === 0) {
1309
1368
  return {
1310
1369
  title: null,
1311
- tags,
1312
- relations
1313
- };
1314
- }
1315
- let relation;
1316
- if (relationships.length === 1) {
1317
- relation = relationships[0];
1318
- } else {
1319
- relation = relationships.find((r) => r.source === source.id && r.target === target.id);
1320
- }
1321
- if (relation && remeda.isTruthy(relation.title)) {
1322
- return {
1323
- title: relation.title,
1324
- tags,
1325
- relations
1326
- };
1327
- }
1328
- const labels = remeda.unique(relationships.flatMap((r) => remeda.isTruthy(r.title) ? r.title : []));
1329
- if (labels.length === 1) {
1330
- return {
1331
- title: labels[0],
1332
- tags,
1333
- relations
1370
+ tags: null,
1371
+ relations: null,
1372
+ navigateTo: null
1334
1373
  };
1335
1374
  }
1375
+ const alltags = remeda.pipe(
1376
+ relationships,
1377
+ remeda.flatMap((r) => r.tags),
1378
+ remeda.filter(remeda.isTruthy),
1379
+ remeda.unique()
1380
+ );
1381
+ const tags = remeda.hasAtLeast(alltags, 1) ? alltags : null;
1382
+ const relations = remeda.hasAtLeast(relationships, 1) ? remeda.map(relationships, (r) => r.id) : null;
1383
+ const relation = remeda.only(relationships) || relationships.find((r) => r.source === source.id && r.target === target.id);
1384
+ const title = remeda.isTruthy(relation?.title) ? relation.title : remeda.pipe(
1385
+ relationships,
1386
+ remeda.map((r) => r.title),
1387
+ remeda.filter(remeda.isTruthy),
1388
+ remeda.unique(),
1389
+ remeda.only()
1390
+ );
1391
+ const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : remeda.pipe(
1392
+ relationships,
1393
+ remeda.map((r) => r.navigateTo),
1394
+ remeda.filter(remeda.isTruthy),
1395
+ remeda.filter((v) => v !== this.view.id),
1396
+ remeda.unique(),
1397
+ remeda.only()
1398
+ );
1336
1399
  return {
1337
- title: null,
1400
+ title: title ?? null,
1338
1401
  tags,
1339
- relations
1402
+ relations,
1403
+ navigateTo: navigateTo ?? null
1340
1404
  };
1341
1405
  }
1342
1406
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "1.9.0",
4
+ "version": "1.10.1",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -102,25 +102,26 @@
102
102
  "lint": "run -T eslint src/ --fix",
103
103
  "clean": "run -T rimraf dist contrib",
104
104
  "test": "vitest run --no-isolate",
105
+ "test-dbg": "vitest run --no-isolate -t formating",
105
106
  "vitest:ui": "vitest --no-isolate --ui",
106
107
  "test:watch": "vitest"
107
108
  },
108
109
  "dependencies": {
109
110
  "@dagrejs/graphlib": "^2.2.4",
110
- "@likec4/core": "1.9.0",
111
- "@likec4/log": "1.9.0",
111
+ "@likec4/core": "1.10.1",
112
+ "@likec4/log": "1.10.1",
112
113
  "@msgpack/msgpack": "^3.0.0-beta2",
113
114
  "@smithy/util-base64": "^3.0.0",
114
115
  "fast-equals": "^5.0.1",
115
116
  "indent-string": "^5.0.0",
116
117
  "json5": "^2.2.3",
117
- "langium": "3.1.3",
118
+ "langium": "3.2.0",
118
119
  "object-hash": "^3.0.0",
119
120
  "p-debounce": "^4.0.0",
120
121
  "remeda": "^2.11.0",
121
122
  "string-hash": "^1.1.3",
122
123
  "strip-indent": "^4.0.0",
123
- "type-fest": "^4.26.0",
124
+ "type-fest": "^4.26.1",
124
125
  "ufo": "^1.5.4",
125
126
  "vscode-jsonrpc": "8.2.0",
126
127
  "vscode-languageserver": "9.0.1",
@@ -128,19 +129,20 @@
128
129
  "vscode-uri": "3.0.8"
129
130
  },
130
131
  "devDependencies": {
131
- "@likec4/icons": "1.9.0",
132
- "@likec4/tsconfig": "1.9.0",
132
+ "@likec4/icons": "1.10.1",
133
+ "@likec4/tsconfig": "1.10.1",
133
134
  "@types/node": "^20.16.1",
134
135
  "@types/object-hash": "^3.0.6",
135
136
  "@types/string-hash": "^1.1.3",
137
+ "@vitest/coverage-v8": "^2.1.0",
136
138
  "execa": "^9.3.1",
137
- "langium-cli": "3.1.1",
139
+ "langium-cli": "3.2.0",
138
140
  "npm-run-all2": "^6.2.2",
139
141
  "tsx": "~4.9.3",
140
142
  "turbo": "^2.1.1",
141
- "typescript": "^5.5.4",
143
+ "typescript": "^5.6.2",
142
144
  "unbuild": "^3.0.0-rc.7",
143
- "vitest": "~2.0.5"
145
+ "vitest": "^2.1.0"
144
146
  },
145
- "packageManager": "yarn@4.3.1"
147
+ "packageManager": "yarn@4.4.1"
146
148
  }