@likec4/language-server 1.59.2 → 1.59.3

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.
@@ -1,7 +1,7 @@
1
1
  import { t as __name } from "./rolldown-runtime.mjs";
2
2
  import { t as LibIcons } from "./icons.mjs";
3
3
  import { Uri, isLikeC4Builtin, isNotLikeC4Builtin } from "../likec4lib.mjs";
4
- import { $ as isDynamicView, $n as isTextSizeProperty, $t as isLinkProperty, A as isAltSteps, An as isShapeSizeProperty, Ar as isWhereRelationParticipantTag, At as isFqnExprWith, B as isDeployedInstanceBody, Bn as isStep, Bt as isIconColorProperty, C as ModelViews, Cn as isRelationKindDotRef, Cr as isWhereRelation, Ct as isExtendRelation, D as SpecificationRelationshipKind, Dn as isRelationshipKind, Dr as isWhereRelationNegation, Dt as isFqnExprOrWhere, E as SpecificationElementKind, En as isRelationStyleProperty, Er as isWhereRelationMetadata, Et as isFqnExpr, F as isColorLiteral, Fn as isSpecificationElementStringProperty, Fr as compareByUri, Ft as isGlobalPredicateGroup, G as isDeploymentRelation, Gn as isStrictFqnElementRef, Gt as isImportsFromPoject, H as isDeploymentNode$1, Hn as isStepSeries, Hr as logger$10, Ht as isIconProperty, I as isColorProperty, In as isSpecificationRelationshipKind, It as isGlobalStyle, J as isDeploymentViewBody, Jn as isStyleProperty, Jt as isLibIcon, K as isDeploymentRelationBody, Kn as isStrictFqnRef, Kt as isInOutRelationExpr, L as isCustomElementProperties, Ln as isSpecificationRelationshipStringProperty, Lr as NoFileSystem, Lt as isGlobalStyleGroup, M as isArrowProperty, Mn as isSpecificationColor, Mr as isWhereTagEqual, Mt as isFqnRef, N as isBorderProperty, Nn as isSpecificationDeploymentNodeKind, Nr as isWildcardExpression, Nt as isFqnRefExpr, O as SpecificationRule, On as isRelationshipStyleProperty, Or as isWhereRelationParticipantKind, Ot as isFqnExprOrWith, P as isCatchBlock, Pn as isSpecificationElementKind, Pr as ADisposable, Pt as isGlobalDynamicPredicateGroup, Q as isDirectedRelationExpr, Qn as isTags, Qt as isLineProperty, R as isCustomRelationProperties, Rn as isSpecificationRule, Rt as isGlobals, S as ModelDeployments, Sn as isRelationExprWith, Sr as isWhereMetadataEqual, St as isExtendElementOrRelation, T as SpecificationDeploymentNodeKind, Tn as isRelationStringProperty, Tr as isWhereRelationKind, Tt as isFinallyBlock, U as isDeploymentNodeBody, Un as isStepStatement, Ut as isIconSizeProperty, V as isDeploymentElement, Vn as isStepOrSeries, Vr as logWarnError, Vt as isIconPositionProperty, W as isDeploymentNodeKind, Wn as isStepsBlock, Wt as isImported, X as isDeploymentViewRulePredicate, Xn as isTag, Xt as isLikeC4Lib, Y as isDeploymentViewRule, Yn as isSubflowStep, Yt as isLikeC4Grammar, Z as isDeploymentViewRuleStyle, Zn as isTagRef, Zt as isLikeC4View, _ as Globals, _n as isRelationBody, _r as isWhereElementKind, _t as isExpressions, a as LikeC4LanguageMetaData, an as isModel, ar as isViewRuleAutoLayout, at as isElement, b as LikeC4View, bn as isRelationExprOrWith, br as isWhereElementTag, bt as isExtendElement, c as DeploymentElement, cn as isMultipleProperty, cr as isViewRuleGroup, ct as isElementKindExpression, d as Element, dn as isNotesProperty, dr as isViewRuleStyle, dt as isElementStringProperty, en as isMarkdownOrString, er as isTryBlock, et as isDynamicViewBody, f as ElementKind, fn as isOpacityProperty, fr as isViewRuleStyleOrGlobalRef, ft as isElementStyleProperty, g as GlobalStyleId, gn as isRelation, gr as isWhereElementExpression, gt as isExpressionV2, h as GlobalPredicateGroup, hn as isRGBAColor, hr as isWhereElement, ht as isElementViewBody, i as LikeC4GeneratedSharedModule, in as isMetadataProperty, ir as isViewRuleAncestors, it as isDynamicViewRule, j as isAnyProperty, jn as isSizeProperty, jr as isWhereRelationTag, jt as isFqnExpressions, k as SpecificationTag, kn as isShapeProperty, kr as isWhereRelationParticipantMetadata, kt as isFqnExprWhere, l as DeploymentNode, ln as isNavigateToProperty, lr as isViewRulePredicate, lt as isElementProperty, m as GlobalDynamicPredicateGroup, mn as isPaddingSizeProperty, mr as isWhereBinaryExpression, mt as isElementView$1, n as safeCall, nn as isMetadataAttribute, nr as isViewProperty, nt as isDynamicViewGlobalPredicateRef, o as CustomColor, on as isModelDeployments, or as isViewRuleGlobalPredicateRef, ot as isElementBody, p as ExtendElement, pn as isOutgoingRelationExpr, pr as isViewStringProperty, pt as isElementTagExpression, q as isDeploymentView, qn as isStringProperty, qt as isIncomingRelationExpr, r as LikeC4GeneratedModule, rn as isMetadataBody, rr as isViewRule, rt as isDynamicViewIncludePredicate, s as DeployedInstance, sn as isModelViews, sr as isViewRuleGlobalStyle, st as isElementKind, t as performanceMark, tn as isMetadataArray, tr as isTryStep, tt as isDynamicViewDisplayVariantProperty, u as DeploymentNodeKind, un as isNotationProperty, ur as isViewRuleRank, ut as isElementRef, v as Imported, vn as isRelationExpr, vr as isWhereElementMetadata, vt as isExtendDeployment, w as RelationshipKind, wn as isRelationNavigateToProperty, wr as isWhereRelationExpression, wt as isExtendRelationBody, x as Model, xn as isRelationExprWhere, xr as isWhereKindEqual, xt as isExtendElementBody, y as LibIcon, yn as isRelationExprOrWhere, yr as isWhereElementNegation, yt as isExtendDeploymentBody, z as isDeployedInstance, zn as isSpecificationTag, zr as NoLikeC4ManualLayouts, zt as isHexColor } from "./utils.mjs";
4
+ import { $ as isDynamicView, $n as isTextSizeProperty, $t as isLinkProperty, A as isAltSteps, An as isShapeSizeProperty, Ar as isWhereRelationParticipantMetadata, At as isFqnExprWith, B as isDeployedInstanceBody, Bn as isStep, Br as NoLikeC4ManualLayouts, Bt as isIconColorProperty, C as ModelViews, Cn as isRelationKindDotRef, Cr as isWhereMetadataEqual, Ct as isExtendRelation, D as SpecificationRelationshipKind, Dn as isRelationshipKind, Dr as isWhereRelationMetadata, Dt as isFqnExprOrWhere, E as SpecificationElementKind, En as isRelationStyleProperty, Er as isWhereRelationKind, Et as isFqnExpr, F as isColorLiteral, Fn as isSpecificationElementStringProperty, Fr as ADisposable, Ft as isGlobalPredicateGroup, G as isDeploymentRelation, Gn as isStrictFqnElementRef, Gt as isImportsFromPoject, H as isDeploymentNode$1, Hn as isStepSeries, Hr as logWarnError, Ht as isIconProperty, I as isColorProperty, In as isSpecificationRelationshipKind, Ir as compareByUri, It as isGlobalStyle, J as isDeploymentViewBody, Jn as isStyleProperty, Jt as isLibIcon, K as isDeploymentRelationBody, Kn as isStrictFqnRef, Kt as isInOutRelationExpr, L as isCustomElementProperties, Ln as isSpecificationRelationshipStringProperty, Lt as isGlobalStyleGroup, M as isArrowProperty, Mn as isSpecificationColor, Mr as isWhereRelationTag, Mt as isFqnRef, N as isBorderProperty, Nn as isSpecificationDeploymentNodeKind, Nr as isWhereTagEqual, Nt as isFqnRefExpr, O as SpecificationRule, On as isRelationshipStyleProperty, Or as isWhereRelationNegation, Ot as isFqnExprOrWith, P as isCatchBlock, Pn as isSpecificationElementKind, Pr as isWildcardExpression, Pt as isGlobalDynamicPredicateGroup, Q as isDirectedRelationExpr, Qn as isTags, Qt as isLineProperty, R as isCustomRelationProperties, Rn as isSpecificationRule, Rr as NoFileSystem, Rt as isGlobals, S as ModelDeployments, Sn as isRelationExprWith, Sr as isWhereKindEqual, St as isExtendElementOrRelation, T as SpecificationDeploymentNodeKind, Tn as isRelationStringProperty, Tr as isWhereRelationExpression, Tt as isFinallyBlock, U as isDeploymentNodeBody, Un as isStepStatement, Ur as logger$10, Ut as isIconSizeProperty, V as isDeploymentElement, Vn as isStepOrSeries, Vt as isIconPositionProperty, W as isDeploymentNodeKind, Wn as isStepsBlock, Wt as isImported, X as isDeploymentViewRulePredicate, Xn as isTag, Xt as isLikeC4Lib, Y as isDeploymentViewRule, Yn as isSubflowStep, Yt as isLikeC4Grammar, Z as isDeploymentViewRuleStyle, Zn as isTagRef, Zt as isLikeC4View, _ as Globals, _n as isRelationBody, _r as isWhereElementExpression, _t as isExpressions, a as LikeC4LanguageMetaData, an as isModel, ar as isViewRuleAncestors, at as isElement, b as LikeC4View, bn as isRelationExprOrWith, br as isWhereElementNegation, bt as isExtendElement, c as DeploymentElement, cn as isMultipleProperty, cr as isViewRuleGlobalStyle, ct as isElementKindExpression, d as Element, dn as isNotesProperty, dr as isViewRuleRank, dt as isElementStringProperty, en as isMarkdownOrString, er as isTryBlock, et as isDynamicViewBody, f as ElementKind, fn as isOpacityProperty, fr as isViewRuleStyle, ft as isElementStyleProperty, g as GlobalStyleId, gn as isRelation, gr as isWhereElement, gt as isExpressionV2, h as GlobalPredicateGroup, hn as isRGBAColor, hr as isWhereBinaryExpression, ht as isElementViewBody, i as LikeC4GeneratedSharedModule, in as isMetadataProperty, ir as isViewRule, it as isDynamicViewRule, j as isAnyProperty, jn as isSizeProperty, jr as isWhereRelationParticipantTag, jt as isFqnExpressions, k as SpecificationTag, kn as isShapeProperty, kr as isWhereRelationParticipantKind, kt as isFqnExprWhere, l as DeploymentNode, ln as isNavigateToProperty, lr as isViewRuleGroup, lt as isElementProperty, m as GlobalDynamicPredicateGroup, mn as isPaddingSizeProperty, mr as isViewStringProperty, mt as isElementView$1, n as safeCall, nn as isMetadataAttribute, nr as isViewOrderProperty, nt as isDynamicViewGlobalPredicateRef, o as CustomColor, on as isModelDeployments, or as isViewRuleAutoLayout, ot as isElementBody, p as ExtendElement, pn as isOutgoingRelationExpr, pr as isViewRuleStyleOrGlobalRef, pt as isElementTagExpression, q as isDeploymentView, qn as isStringProperty, qt as isIncomingRelationExpr, r as LikeC4GeneratedModule, rn as isMetadataBody, rr as isViewProperty, rt as isDynamicViewIncludePredicate, s as DeployedInstance, sn as isModelViews, sr as isViewRuleGlobalPredicateRef, st as isElementKind, t as performanceMark, tn as isMetadataArray, tr as isTryStep, tt as isDynamicViewDisplayVariantProperty, u as DeploymentNodeKind, un as isNotationProperty, ur as isViewRulePredicate, ut as isElementRef, v as Imported, vn as isRelationExpr, vr as isWhereElementKind, vt as isExtendDeployment, w as RelationshipKind, wn as isRelationNavigateToProperty, wr as isWhereRelation, wt as isExtendRelationBody, x as Model, xn as isRelationExprWhere, xr as isWhereElementTag, xt as isExtendElementBody, y as LibIcon, yn as isRelationExprOrWhere, yr as isWhereElementMetadata, yt as isExtendDeploymentBody, z as isDeployedInstance, zn as isSpecificationTag, zt as isHexColor } from "./utils.mjs";
5
5
  import { BuildDocuments, ChangeView, DidChangeModelNotification, DidChangeProjectsNotification, DidChangeSnapshotNotification, DidRequestOpenViewNotification, FetchComputedModel, FetchLayoutedModel, FetchProjects, FetchProjectsOverview, FetchTelemetryMetrics, FetchViewsFromAllProjects, GetDocumentTags, LayoutView, Locate, RegisterProject, ReloadProjects, ValidateLayout } from "../protocol.mjs";
6
6
  import { DefaultMap, DefaultWeakMap, MultiMap, ancestorsFqn, compareNatural, compareNaturalHierarchically, ifilter, invariant, isNonEmptyArray, isString, nameFromFqn, nonNullable, nonexhaustive, onNextTick, parentFqn, sortNaturalByFqn, sortParentsFirst, stringHash, toArray } from "@likec4/core/utils";
7
7
  import { loggable, wrapError } from "@likec4/log";
@@ -399,6 +399,12 @@ function _overlaps(folderA, folderB) {
399
399
  const b = isString(folderB) ? folderB : folderB.folder;
400
400
  return a === b || a.startsWith(b) || b.startsWith(a);
401
401
  }
402
+ function sameFolder(folderA, folderB) {
403
+ return (isString(folderA) ? folderA : folderA.folder) === (isString(folderB) ? folderB : folderB.folder);
404
+ }
405
+ function hasAmbiguousIncludePathOverlap(includePath, otherIncludePath) {
406
+ return _overlaps(includePath, otherIncludePath) && !sameFolder(includePath, otherIncludePath);
407
+ }
402
408
  function overlaps(...args) {
403
409
  return purry(_overlaps, args);
404
410
  }
@@ -932,13 +938,16 @@ var ProjectsManager = class ProjectsManager extends ADisposable {
932
938
  }
933
939
  const exclude = config.exclude;
934
940
  if (isNullish(exclude)) project.exclude = DefaultProject.exclude;
935
- else if (hasAtLeast(exclude, 1)) project.exclude = picomatch([project.folderUri, ...map(project.includePaths ?? [], prop("uri"))].flatMap((root) => map(exclude, (p) => {
936
- if (!isRelative(p) && !p.startsWith("**")) p = joinURL("**", p);
937
- return cleanDoubleSlashes(joinRelativeURL(uriPathForMatcher(root), p));
938
- })), {
939
- contains: true,
940
- dot: true
941
- });
941
+ else if (hasAtLeast(exclude, 1)) {
942
+ const patterns = [project.folderUri, ...map(project.includePaths ?? [], prop("uri"))].flatMap((root) => map(exclude, (p) => {
943
+ if (!isRelative(p) && !p.startsWith("**")) p = joinURL("**", p);
944
+ return cleanDoubleSlashes(joinRelativeURL(uriPathForMatcher(root), p));
945
+ }));
946
+ project.exclude = picomatch(patterns, {
947
+ contains: true,
948
+ dot: true
949
+ });
950
+ }
942
951
  if (!project.includePaths) return project;
943
952
  for (const includePath of project.includePaths) for (const otherProject of this.#projects) {
944
953
  if (otherProject.id === project.id) continue;
@@ -948,7 +957,7 @@ var ProjectsManager = class ProjectsManager extends ADisposable {
948
957
  otherProjectId: otherProject.id
949
958
  });
950
959
  otherProject.includePaths?.forEach((otherIncludePath) => {
951
- if (overlaps(includePath, otherIncludePath)) logger$9.warn("Project \"{projectId}\" include path \"{includePath}\" overlaps with project \"{otherProjectId}\" include path \"{otherIncludePath}\". Files in overlapping areas will only belong to one project.", {
960
+ if (hasAmbiguousIncludePathOverlap(includePath, otherIncludePath)) logger$9.warn("Project \"{projectId}\" include path \"{includePath}\" overlaps with project \"{otherProjectId}\" include path \"{otherIncludePath}\". Files in overlapping areas will only belong to one project.", {
952
961
  projectId: project.id,
953
962
  includePath: includePath.folder,
954
963
  otherProjectId: otherProject.id,
@@ -1184,7 +1193,8 @@ function projectIdFrom(value) {
1184
1193
  //#endregion
1185
1194
  //#region src/utils/stringHash.ts
1186
1195
  function stringHash$1(...str) {
1187
- return stringHash(str.length > 1 ? str.join(":::") : str[0]);
1196
+ var s = str.length > 1 ? str.join(":::") : str[0];
1197
+ return stringHash(s);
1188
1198
  }
1189
1199
  __name(stringHash$1, "stringHash");
1190
1200
 
@@ -1354,7 +1364,7 @@ var LikeC4Formatter = class extends AbstractFormatter {
1354
1364
  this.on(node, (n) => isRelation(n) || isDeploymentRelation(n), (n, f) => {
1355
1365
  const sourceNodes = n?.source?.$cstNode ? [n?.source?.$cstNode] : [];
1356
1366
  f.cst(sourceNodes).append(FormattingOptions$1.oneSpace);
1357
- f.keywords("]->").prepend(FormattingOptions$1.noSpace);
1367
+ f.keywords("]->", "]<->").prepend(FormattingOptions$1.noSpace);
1358
1368
  f.keywords("-[").append(FormattingOptions$1.noSpace);
1359
1369
  f.nodes(...filter([n.target, n.tags], isTruthy)).prepend(FormattingOptions$1.oneSpace);
1360
1370
  f.properties("title", "description", "technology").prepend(FormattingOptions$1.oneSpace);
@@ -1422,10 +1432,10 @@ var LikeC4Formatter = class extends AbstractFormatter {
1422
1432
  this.on(node, isDeploymentView)?.keywords("deployment", "view").append(FormattingOptions$1.oneSpace);
1423
1433
  }
1424
1434
  formatLeafProperty(node) {
1425
- if (isElementStringProperty(node) || isRelationStringProperty(node) || isViewStringProperty(node) || isNotationProperty(node) || isNotesProperty(node) || isSpecificationElementStringProperty(node) || isSpecificationRelationshipStringProperty(node) || isColorProperty(node) || isLineProperty(node) || isArrowProperty(node) || isIconProperty(node) || isShapeProperty(node) || isBorderProperty(node) || isOpacityProperty(node) || isMultipleProperty(node) || isIconColorProperty(node) || isIconSizeProperty(node) || isIconPositionProperty(node) || isShapeSizeProperty(node) || isPaddingSizeProperty(node) || isTextSizeProperty(node)) {
1435
+ if (isElementStringProperty(node) || isRelationStringProperty(node) || isViewStringProperty(node) || isViewOrderProperty(node) || isNotationProperty(node) || isNotesProperty(node) || isSpecificationElementStringProperty(node) || isSpecificationRelationshipStringProperty(node) || isColorProperty(node) || isLineProperty(node) || isArrowProperty(node) || isIconProperty(node) || isShapeProperty(node) || isBorderProperty(node) || isOpacityProperty(node) || isMultipleProperty(node) || isIconColorProperty(node) || isIconSizeProperty(node) || isIconPositionProperty(node) || isShapeSizeProperty(node) || isPaddingSizeProperty(node) || isTextSizeProperty(node)) {
1426
1436
  const formatter = this.getNodeFormatter(node);
1427
1437
  const colon = formatter.keyword(":");
1428
- const propertyName = formatter.keywords("title", "description", "technology", "summary", "notation", "notes", "color", "line", "head", "tail", "icon", "iconColor", "iconSize", "iconPosition", "shape", "border", "opacity", "multiple", "size", "padding", "textSize");
1438
+ const propertyName = formatter.keywords("title", "description", "order", "technology", "summary", "notation", "notes", "color", "line", "head", "tail", "icon", "iconColor", "iconSize", "iconPosition", "shape", "border", "opacity", "multiple", "size", "padding", "textSize");
1429
1439
  if (colon.nodes.length === 0) propertyName.append(FormattingOptions$1.oneSpace);
1430
1440
  else colon.prepend(FormattingOptions$1.noSpace).append(FormattingOptions$1.oneSpace);
1431
1441
  formatter.keyword(";").prepend(FormattingOptions$1.noSpace).append(FormattingOptions$1.newLine);
@@ -1460,10 +1470,12 @@ var LikeC4Formatter = class extends AbstractFormatter {
1460
1470
  this.on(node, isElement, (n, f) => {
1461
1471
  const kind = GrammarUtils.findNodeForProperty(n.$cstNode, "kind");
1462
1472
  const name = GrammarUtils.findNodeForProperty(n.$cstNode, "name");
1463
- if (name && kind) if (compareRanges(name, kind) > 0) f.cst([kind]).append(FormattingOptions$1.oneSpace);
1464
- else {
1465
- f.cst([name]).append(FormattingOptions$1.oneSpace);
1466
- f.cst([kind]).prepend(FormattingOptions$1.oneSpace);
1473
+ if (name && kind) {
1474
+ if (compareRanges(name, kind) > 0) f.cst([kind]).append(FormattingOptions$1.oneSpace);
1475
+ else {
1476
+ f.cst([name]).append(FormattingOptions$1.oneSpace);
1477
+ f.cst([kind]).prepend(FormattingOptions$1.oneSpace);
1478
+ }
1467
1479
  }
1468
1480
  f.properties("props").prepend(FormattingOptions$1.oneSpace);
1469
1481
  });
@@ -1512,10 +1524,12 @@ var LikeC4Formatter = class extends AbstractFormatter {
1512
1524
  this.on(node, isDeploymentNode$1, (n, f) => {
1513
1525
  const kind = GrammarUtils.findNodeForProperty(n.$cstNode, "kind");
1514
1526
  const name = GrammarUtils.findNodeForProperty(n.$cstNode, "name");
1515
- if (name && kind) if (compareRanges(name, kind) > 0) f.cst([kind]).append(FormattingOptions$1.oneSpace);
1516
- else {
1517
- f.cst([name]).append(FormattingOptions$1.oneSpace);
1518
- f.cst([kind]).prepend(FormattingOptions$1.oneSpace);
1527
+ if (name && kind) {
1528
+ if (compareRanges(name, kind) > 0) f.cst([kind]).append(FormattingOptions$1.oneSpace);
1529
+ else {
1530
+ f.cst([name]).append(FormattingOptions$1.oneSpace);
1531
+ f.cst([kind]).prepend(FormattingOptions$1.oneSpace);
1532
+ }
1519
1533
  }
1520
1534
  f.properties("title").prepend(FormattingOptions$1.oneSpace);
1521
1535
  });
@@ -1848,6 +1862,9 @@ var DefaultLikeC4LanguageServices = class {
1848
1862
  default: nonexhaustive$1(params);
1849
1863
  }
1850
1864
  }
1865
+ documentText(uri) {
1866
+ return this.services.shared.workspace.LangiumDocuments.getDocument(URI.parse(uri))?.textDocument.getText();
1867
+ }
1851
1868
  /**
1852
1869
  * Formats documents sequentially and returns a map of document URI → formatted source text.
1853
1870
  *
@@ -1997,24 +2014,34 @@ var LikeC4CompletionProvider = class extends DefaultCompletionProvider {
1997
2014
  }
1998
2015
  completionForKeyword(context, keyword, acceptor) {
1999
2016
  if (!this.filterKeyword(context, keyword)) return;
2000
- const acceptProperty = (insertAfterText) => {
2017
+ /**
2018
+ * The generated text starts with the keyword itself, so it must replace the
2019
+ * already typed prefix. We provide an explicit `textEdit` instead of `insertText`,
2020
+ * because clients that apply `insertText` at the cursor would duplicate the
2021
+ * keyword (see https://github.com/likec4/likec4/issues/3091)
2022
+ */
2023
+ const acceptReplacing = (item, newText) => {
2024
+ const textEdit = this.buildCompletionTextEdit(context, keyword.value, newText);
2025
+ if (!textEdit) return;
2001
2026
  acceptor(context, {
2002
2027
  label: keyword.value,
2003
- detail: `Insert ${keyword.value} property`,
2004
- kind: CompletionItemKind.Property,
2005
2028
  insertTextFormat: InsertTextFormat.Snippet,
2006
- insertText: `${keyword.value} ${insertAfterText}`
2029
+ ...item,
2030
+ textEdit
2007
2031
  });
2008
2032
  };
2033
+ const acceptProperty = (insertAfterText) => {
2034
+ acceptReplacing({
2035
+ detail: `Insert ${keyword.value} property`,
2036
+ kind: CompletionItemKind.Property
2037
+ }, `${keyword.value} ${insertAfterText}`);
2038
+ };
2009
2039
  const acceptSnippet = ({ insertText, ...item }) => {
2010
- acceptor(context, {
2011
- label: keyword.value,
2040
+ acceptReplacing({
2012
2041
  detail: `Insert ${keyword.value}`,
2013
2042
  kind: CompletionItemKind.Snippet,
2014
- insertTextFormat: InsertTextFormat.Snippet,
2015
- ...item,
2016
- insertText: `${keyword.value} ${insertText}`
2017
- });
2043
+ ...item
2044
+ }, `${keyword.value} ${insertText}`);
2018
2045
  };
2019
2046
  const acceptPropertyAndSuggest = (variants) => {
2020
2047
  acceptProperty(`\${1|${variants.join(",")}|}$0`);
@@ -2149,13 +2176,11 @@ var LikeC4CompletionProvider = class extends DefaultCompletionProvider {
2149
2176
  acceptPropertyAndSuggest(["sequence", "diagram"]);
2150
2177
  break;
2151
2178
  case ["include", "exclude"].includes(keyword.value):
2152
- acceptor(context, {
2153
- label: keyword.value,
2179
+ acceptReplacing({
2154
2180
  kind: CompletionItemKind.Operator,
2155
2181
  detail: `Insert ${keyword.value} predicate`,
2156
- insertTextFormat: InsertTextFormat.PlainText,
2157
- insertText: `${keyword.value} `
2158
- });
2182
+ insertTextFormat: InsertTextFormat.PlainText
2183
+ }, `${keyword.value} `);
2159
2184
  break;
2160
2185
  default: acceptor(context, {
2161
2186
  label: keyword.value,
@@ -2234,7 +2259,8 @@ var LikeC4DocumentLinkProvider = class {
2234
2259
  if (isRelative(link)) {
2235
2260
  const base = this.services.shared.workspace.ProjectsManager.getProject(doc).folderUri.toString();
2236
2261
  const docURL = new URL(doc.uri.toString());
2237
- return withoutLeadingSlash(withoutBase(new URL(link, docURL).toString(), base));
2262
+ const linkURL = new URL(link, docURL).toString();
2263
+ return withoutLeadingSlash(withoutBase(linkURL, base));
2238
2264
  }
2239
2265
  return null;
2240
2266
  }
@@ -2376,7 +2402,7 @@ var LikeC4DocumentSymbolProvider = class {
2376
2402
  const nameNode = GrammarUtils.findNodeForProperty(cst, "name");
2377
2403
  if (!nameNode || !cst) return [];
2378
2404
  const name = astElement.name;
2379
- const detail = astElement.kind.$refText;
2405
+ const detail = `(${astElement.kind.$refText})`;
2380
2406
  return [{
2381
2407
  kind: this.symbolKind(astElement),
2382
2408
  name,
@@ -2453,7 +2479,7 @@ var LikeC4DocumentSymbolProvider = class {
2453
2479
  const nameNode = this.nameProvider.getNameNode(astElement);
2454
2480
  if (!nameNode || !cst) return [];
2455
2481
  const name = this.nameProvider.getNameStrict(astElement);
2456
- const detail = astElement.kind.$refText;
2482
+ const detail = `(${astElement.kind.$refText})`;
2457
2483
  return [{
2458
2484
  kind: this.symbolKind(astElement),
2459
2485
  name,
@@ -2746,7 +2772,9 @@ var LikeC4SemanticTokenProvider = class extends AbstractSemanticTokenProvider {
2746
2772
  mark.property("name").readonly.declaration.type();
2747
2773
  });
2748
2774
  when(isSpecificationTag, (mark) => {
2749
- if (isTruthy(mark.node.color)) mark.keyword("color").property();
2775
+ if (isTruthy(mark.node.color) || isTruthy(mark.node.themeColor) || isTruthy(mark.node.customColor)) mark.keyword("color").property();
2776
+ if (isTruthy(mark.node.customColor)) mark.property("customColor").enum();
2777
+ if (isTruthy(mark.node.themeColor)) mark.property("themeColor").enum();
2750
2778
  });
2751
2779
  when(isAnyOf$1(isSpecificationElementKind, isSpecificationRelationshipKind, isSpecificationDeploymentNodeKind), (mark) => {
2752
2780
  mark.property("kind").readonly.declaration.type();
@@ -3178,11 +3206,16 @@ var MergedSpecification = class {
3178
3206
  /**
3179
3207
  * Converts a parsed model into a C4 model relation.
3180
3208
  */
3181
- toModelRelation = ({ astPath: _astPath, source, target, kind, links, id, title, tags, ...model }) => {
3209
+ toModelRelation = ({ astPath: _astPath, source, target, kind, links, id, title, tags, isBidirectional, ...model }) => {
3210
+ const bidirectionalRelationship = (relationship) => isBidirectional ? {
3211
+ ...relationship,
3212
+ isBidirectional,
3213
+ tail: relationship.tail ?? relationship.head ?? "normal"
3214
+ } : relationship;
3182
3215
  if (isNonNullish(kind) && this.specs.relationships[kind]) {
3183
3216
  const { multiple: _multiple, tags: specTags, ...spec } = this.specs.relationships[kind];
3184
3217
  if (specTags && isNonEmptyArray(specTags)) tags = tags ? unique([...specTags, ...tags]) : specTags;
3185
- return {
3218
+ return bidirectionalRelationship({
3186
3219
  ...spec,
3187
3220
  ...model,
3188
3221
  ...links && { links },
@@ -3192,17 +3225,17 @@ var MergedSpecification = class {
3192
3225
  target,
3193
3226
  kind,
3194
3227
  id
3195
- };
3228
+ });
3196
3229
  }
3197
- return {
3230
+ return bidirectionalRelationship({
3198
3231
  ...links && { links },
3199
3232
  ...model,
3200
3233
  ...tags && { tags },
3201
- title,
3202
3234
  source,
3203
3235
  target,
3204
- id
3205
- };
3236
+ id,
3237
+ title
3238
+ });
3206
3239
  };
3207
3240
  /**
3208
3241
  * Converts a parsed deployment model into a C4 deployment model
@@ -3353,6 +3386,19 @@ function assignNavigateTo(views) {
3353
3386
  return views;
3354
3387
  }
3355
3388
 
3389
+ //#endregion
3390
+ //#region src/model/relationFingerprint.ts
3391
+ function relationFingerprint({ source, target, kind, title, isBidirectional }) {
3392
+ let sourceKey = FqnRef$1.flatten(source);
3393
+ let targetKey = FqnRef$1.flatten(target);
3394
+ if (isBidirectional && targetKey < sourceKey) {
3395
+ const normalizedSource = targetKey;
3396
+ targetKey = sourceKey;
3397
+ sourceKey = normalizedSource;
3398
+ }
3399
+ return stringHash$1("extend-relation", sourceKey, targetKey, kind ?? "default", title ?? "", isBidirectional ? "bidirectional" : "directed");
3400
+ }
3401
+
3356
3402
  //#endregion
3357
3403
  //#region src/model/builder/MergedExtends.ts
3358
3404
  var MergedExtends = class {
@@ -3450,7 +3496,7 @@ function buildModelData(services, project, docs) {
3450
3496
  }
3451
3497
  return true;
3452
3498
  }), map((rel) => {
3453
- const matchKey = stringHash$1("extend-relation", FqnRef$1.flatten(rel.source), FqnRef$1.flatten(rel.target), rel.kind ?? "default", rel.title ?? "");
3499
+ const matchKey = relationFingerprint(rel);
3454
3500
  const relExtends = relationExtends.filter((ext) => ext.id === matchKey);
3455
3501
  if (relExtends.length === 0) return rel;
3456
3502
  relExtends.forEach((ext) => matchedExtendIds.add(ext.astPath));
@@ -3562,6 +3608,10 @@ function buildModelData(services, project, docs) {
3562
3608
  let views = pipe(parsedViews, indexBy(prop("id")));
3563
3609
  if (parsedViews.some(isExtendsElementView)) views = resolveRulesExtendedViews(views);
3564
3610
  for (const ext of relationExtends) if (!matchedExtendIds.has(ext.astPath)) logger$10.warn(`Relation extend at ${ext.astPath} does not match any relation in the model`);
3611
+ const elementsForOutput = mapValues(elements, (el) => el.title.includes("\\/") ? {
3612
+ ...el,
3613
+ title: el.title.replaceAll("\\/", "/")
3614
+ } : el);
3565
3615
  return {
3566
3616
  data: {
3567
3617
  [_stage$1]: "parsed",
@@ -3570,6 +3620,8 @@ function buildModelData(services, project, docs) {
3570
3620
  id: project.id,
3571
3621
  title: project.config.title ?? project.config.name,
3572
3622
  styles: project.config.styles,
3623
+ contactPerson: project.config.contactPerson,
3624
+ metadata: project.config.metadata,
3573
3625
  manualLayouts: project.config.manualLayouts,
3574
3626
  inferTechnologyFromIcon: project.config.inferTechnologyFromIcon
3575
3627
  }),
@@ -3585,7 +3637,7 @@ function buildModelData(services, project, docs) {
3585
3637
  ...metadataKeys.size > 0 && { metadataKeys: [...metadataKeys].sort(compareNatural) },
3586
3638
  customColors
3587
3639
  },
3588
- elements,
3640
+ elements: elementsForOutput,
3589
3641
  relations,
3590
3642
  globals: c4Specification.globals,
3591
3643
  views,
@@ -3602,6 +3654,29 @@ function buildModelData(services, project, docs) {
3602
3654
  //#endregion
3603
3655
  //#region src/model/model-builder.ts
3604
3656
  const builderLogger = logger$10.getChild("builder");
3657
+ function isRequestedImportedElement(fqns, element) {
3658
+ for (const fqn of fqns) if (element.id === fqn || element.id.startsWith(`${fqn}.`)) return true;
3659
+ return false;
3660
+ }
3661
+ function importedElementTitles(imports) {
3662
+ const titles = /* @__PURE__ */ new Map();
3663
+ for (const [projectId, elements] of entries(imports)) for (const element of elements) titles.set(GlobalFqn(projectId, element.id), element.title);
3664
+ return titles;
3665
+ }
3666
+ function withImportedScopedViewTitles(views, imports) {
3667
+ const titles = importedElementTitles(imports);
3668
+ if (titles.size === 0) return views;
3669
+ return mapValues(views, (view) => {
3670
+ if (view[_type] === "element" && view.title === null && view.viewOf) {
3671
+ const title = titles.get(view.viewOf);
3672
+ if (title !== void 0) return {
3673
+ ...view,
3674
+ title
3675
+ };
3676
+ }
3677
+ return view;
3678
+ });
3679
+ }
3605
3680
  var DefaultLikeC4ModelBuilder = class extends ADisposable {
3606
3681
  services;
3607
3682
  projects;
@@ -3680,14 +3755,15 @@ var DefaultLikeC4ModelBuilder = class extends ADisposable {
3680
3755
  if (fqns.size === 0) return acc;
3681
3756
  const anotherProject = this.unsafeSyncParseModelData(projectId);
3682
3757
  if (anotherProject) {
3683
- const imported = [...fqns].flatMap((fqn) => anotherProject.data.elements[fqn] ?? []);
3758
+ const imported = values(anotherProject.data.elements).filter((element) => isRequestedImportedElement(fqns, element));
3684
3759
  if (hasAtLeast(imported, 1)) acc[projectId] = structuredClone(imported);
3685
3760
  }
3686
3761
  return acc;
3687
3762
  }, {});
3688
3763
  return {
3689
3764
  ...result.data,
3690
- imports
3765
+ imports,
3766
+ views: withImportedScopedViewTitles(result.data.views, imports)
3691
3767
  };
3692
3768
  });
3693
3769
  }
@@ -4365,7 +4441,8 @@ const checkImportsFromPoject = (services) => {
4365
4441
  });
4366
4442
  return;
4367
4443
  }
4368
- const projectId = projectIdFrom(getDocument$1(el));
4444
+ const doc = getDocument$1(el);
4445
+ const projectId = projectIdFrom(doc);
4369
4446
  if (el.project === projectId) {
4370
4447
  accept("error", "Imported project cannot be the same as the current project", {
4371
4448
  node: el,
@@ -4469,11 +4546,13 @@ const relationChecks = (services) => {
4469
4546
  });
4470
4547
  return;
4471
4548
  }
4472
- if (FqnRef.isImportRef(source)) if (FqnRef.isImportRef(target)) accept("warning", "Relationship between imported elements may not be visible in origin projects", { node: el });
4473
- else accept("warning", "Relationship from imported element to local element may not be visible in origin project", {
4474
- node: el,
4475
- property: "source"
4476
- });
4549
+ if (FqnRef.isImportRef(source)) {
4550
+ if (FqnRef.isImportRef(target)) accept("warning", "Relationship between imported elements may not be visible in origin projects", { node: el });
4551
+ else accept("warning", "Relationship from imported element to local element may not be visible in origin project", {
4552
+ node: el,
4553
+ property: "source"
4554
+ });
4555
+ }
4477
4556
  if (isSameHierarchy(FqnRef.flatten(source), FqnRef.flatten(target))) accept("error", "Invalid parent-child relationship", { node: el });
4478
4557
  });
4479
4558
  };
@@ -4485,9 +4564,8 @@ const checkRelationBody = (_services) => {
4485
4564
  const extendRelationChecks = (services) => {
4486
4565
  const modelParser = services.likec4.ModelParser;
4487
4566
  const cache = new WorkspaceCache(services.shared, DocumentState.Linked);
4488
- const calcFingerprint = ({ source, target, kind, title }) => stringHash$1("extend-relation", FqnRef.flatten(source), FqnRef.flatten(target), kind ?? "default", title ?? "");
4489
4567
  function getProjectFingerprints(projectId) {
4490
- return cache.get(projectId, () => new Set(pipe(services.shared.workspace.LangiumDocuments.projectDocuments(projectId).toArray(), flatMap((doc) => doc.c4Relations ?? []), map((rel) => calcFingerprint(rel)))));
4568
+ return cache.get(projectId, () => new Set(pipe(services.shared.workspace.LangiumDocuments.projectDocuments(projectId).toArray(), flatMap((doc) => doc.c4Relations ?? []), map((rel) => relationFingerprint(rel)))));
4491
4569
  }
4492
4570
  return tryOrLog((el, accept) => {
4493
4571
  const doc = AstUtils.getDocument(el);
@@ -4525,13 +4603,14 @@ const extendRelationChecks = (services) => {
4525
4603
  const projectId = projectIdFrom(doc);
4526
4604
  const kind = (el.kind ?? el.dotKind?.kind)?.ref?.name ?? "default";
4527
4605
  const { title = "" } = parser.parseBaseProps({}, { title: el.title });
4528
- const extendKey = calcFingerprint({
4606
+ const extendKey = relationFingerprint({
4529
4607
  source,
4530
4608
  target,
4531
4609
  kind,
4532
- title
4610
+ title,
4611
+ isBidirectional: el.isBidirectional || void 0
4533
4612
  });
4534
- if (!getProjectFingerprints(projectId).has(extendKey)) accept("warning", "This extend does not match any relation (by source, kind, target, title)", { node: el });
4613
+ if (!getProjectFingerprints(projectId).has(extendKey)) accept("warning", "This extend does not match any relation (by source, kind, target, title, and directionality)", { node: el });
4535
4614
  });
4536
4615
  };
4537
4616
 
@@ -4693,6 +4772,17 @@ const viewChecks = (services) => {
4693
4772
  });
4694
4773
  });
4695
4774
  };
4775
+ /**
4776
+ * Validates per-view navigation order values.
4777
+ */
4778
+ const viewOrderChecks = (_services) => {
4779
+ return tryOrLog((node, accept) => {
4780
+ if (!Number.isSafeInteger(node.value) || node.value < 0) accept("error", "View order must be a non-negative safe integer", {
4781
+ node,
4782
+ property: "value"
4783
+ });
4784
+ });
4785
+ };
4696
4786
 
4697
4787
  //#endregion
4698
4788
  //#region src/validation/view-checks.ts
@@ -4804,7 +4894,8 @@ const checkFqnRefExpr = (services) => {
4804
4894
  const checkIncomingRelationExpr = (_services) => {
4805
4895
  return tryOrLog((el, accept) => {
4806
4896
  if (el.to.$type === "WildcardExpression" && !isInOutRelationExpr(el.$container)) {
4807
- if (isNullish(AstUtils.getContainerOfType(el, isElementView$1)?.viewOf)) accept("warning", "Predicate is ignored as it concerns all relationships", { node: el });
4897
+ const view = AstUtils.getContainerOfType(el, isElementView$1);
4898
+ if (isNullish(view?.viewOf)) accept("warning", "Predicate is ignored as it concerns all relationships", { node: el });
4808
4899
  }
4809
4900
  });
4810
4901
  };
@@ -4941,7 +5032,8 @@ function registerValidationChecks(services) {
4941
5032
  ImportsFromPoject: checkImportsFromPoject(services),
4942
5033
  ColorLiteral: colorLiteralRuleChecks(services),
4943
5034
  DynamicViewDisplayVariantProperty: dynamicViewDisplayVariant(services),
4944
- ViewRuleRank: viewRuleRankChecks(services)
5035
+ ViewRuleRank: viewRuleRankChecks(services),
5036
+ ViewOrderProperty: viewOrderChecks(services)
4945
5037
  });
4946
5038
  const connection = services.shared.lsp.Connection;
4947
5039
  if (connection) onNextTick(() => {
@@ -4960,6 +5052,48 @@ function registerValidationChecks(services) {
4960
5052
  //#endregion
4961
5053
  //#region src/model/parser/Base.ts
4962
5054
  const logger$4 = logger$10.getChild("parser");
5055
+ const BASIC_ESCAPES = {
5056
+ b: "\b",
5057
+ f: "\f",
5058
+ n: "\n",
5059
+ r: "\r",
5060
+ t: " ",
5061
+ v: "\v",
5062
+ "0": "\0"
5063
+ };
5064
+ /**
5065
+ * Mirrors langium's default string conversion, except an escaped `\/` is kept as-is,
5066
+ * so @likec4/core's view-folder splitting can tell it apart from a real separator later on.
5067
+ */
5068
+ function unescapeKeepingSlash(quoted) {
5069
+ let result = "";
5070
+ for (let i = 1; i < quoted.length - 1; i++) {
5071
+ const c = quoted[i];
5072
+ if (c === "\\") {
5073
+ const next = quoted[++i] ?? "";
5074
+ result += next === "/" ? "\\/" : BASIC_ESCAPES[next] ?? next;
5075
+ } else result += c;
5076
+ }
5077
+ return result;
5078
+ }
5079
+ /**
5080
+ * Re-reads a title's raw token text (instead of the already-unescaped AST value) so an
5081
+ * escaped `\/` survives for the view-folder splitting logic. Returns `undefined` for
5082
+ * markdown (triple-quoted) titles, which don't support this escape.
5083
+ */
5084
+ function parseTitleKeepingEscapedSlash(node) {
5085
+ if (!node || !isString$1(node.text)) return;
5086
+ const raw = node.$cstNode?.text;
5087
+ return raw ? unescapeKeepingSlash(raw) : node.text;
5088
+ }
5089
+ /**
5090
+ * Same as {@link parseTitleKeepingEscapedSlash}, for an inline `String` property (not
5091
+ * wrapped in `MarkdownOrString`), such as an element's positional title.
5092
+ */
5093
+ function parseInlineTitleKeepingEscapedSlash(containerNode, property, index = 0) {
5094
+ const raw = GrammarUtils.findNodeForProperty(containerNode?.$cstNode, property, index)?.text;
5095
+ return raw ? unescapeKeepingSlash(raw) : void 0;
5096
+ }
4963
5097
  function toSingleLine(str) {
4964
5098
  if (str === null || str === void 0) return;
4965
5099
  const without = removeIndent(str);
@@ -4977,6 +5111,13 @@ function removeIndent(str) {
4977
5111
  default: return;
4978
5112
  }
4979
5113
  }
5114
+ /**
5115
+ * Returns a non-negative safe view order while discarding recovered negative literals.
5116
+ */
5117
+ function parseViewOrder(prop) {
5118
+ if (!prop || /^\s*order\b\s*-/.test(prop.$cstNode?.text ?? "")) return;
5119
+ return Number.isSafeInteger(prop.value) && prop.value >= 0 ? prop.value : void 0;
5120
+ }
4980
5121
  var BaseParser = class {
4981
5122
  services;
4982
5123
  doc;
@@ -5243,11 +5384,13 @@ var BaseParser = class {
5243
5384
  parseBaseProps(props, override) {
5244
5385
  const title = removeIndent(override?.title ?? parseMarkdownAsString(props.title));
5245
5386
  const description = override?.description ? { txt: removeIndent(override.description) } : this.parseMarkdownOrString(props.description);
5387
+ const summary = override?.summary ? { txt: removeIndent(override.summary) } : this.parseMarkdownOrString(props.summary);
5388
+ const technology = toSingleLine(override?.technology) ?? removeIndent(parseMarkdownAsString(props.technology));
5246
5389
  return exact({
5247
5390
  title,
5248
- summary: override?.summary ? { txt: removeIndent(override.summary) } : this.parseMarkdownOrString(props.summary),
5391
+ summary,
5249
5392
  description,
5250
- technology: toSingleLine(override?.technology) ?? removeIndent(parseMarkdownAsString(props.technology))
5393
+ technology
5251
5394
  });
5252
5395
  }
5253
5396
  };
@@ -5331,11 +5474,12 @@ function DeploymentModelParser(B) {
5331
5474
  title: astNode.title,
5332
5475
  summary: astNode.summary
5333
5476
  });
5477
+ const links = this.parseLinks(astNode.body);
5334
5478
  return exact({
5335
5479
  id,
5336
5480
  element: target,
5337
5481
  tags,
5338
- links: this.parseLinks(astNode.body),
5482
+ links,
5339
5483
  ...baseProps,
5340
5484
  style,
5341
5485
  metadata
@@ -5346,9 +5490,11 @@ function DeploymentModelParser(B) {
5346
5490
  const metadata = this.getMetadata(astNode.body?.props.find(isMetadataProperty));
5347
5491
  const links = this.parseLinks(astNode.body);
5348
5492
  if (!tags && isEmpty(metadata ?? {}) && !links) return null;
5493
+ const id = this.resolveFqn(astNode);
5494
+ const astPath = this.getAstNodePath(astNode);
5349
5495
  return exact({
5350
- id: this.resolveFqn(astNode),
5351
- astPath: this.getAstNodePath(astNode),
5496
+ id,
5497
+ astPath,
5352
5498
  metadata,
5353
5499
  tags,
5354
5500
  links
@@ -5374,8 +5520,9 @@ function DeploymentModelParser(B) {
5374
5520
  const navigateTo = pipe(astNode.body?.props ?? [], filter(isRelationNavigateToProperty), first())?.value.view.ref?.name;
5375
5521
  const titleDescAndTech = this.parseBaseProps(bodyProps, { title: astNode.title });
5376
5522
  const styleProp = astNode.body?.props.find(isRelationStyleProperty);
5523
+ const id = stringHash$1("deployment", this.docUri, astPath, source.deployment, target.deployment);
5377
5524
  return exact({
5378
- id: stringHash$1("deployment", this.docUri, astPath, source.deployment, target.deployment),
5525
+ id,
5379
5526
  source,
5380
5527
  target,
5381
5528
  ...titleDescAndTech,
@@ -5402,9 +5549,11 @@ function DeploymentViewParser(B) {
5402
5549
  const astPath = this.getAstNodePath(astNode);
5403
5550
  let id = astNode.name;
5404
5551
  if (!id) id = "deployment_" + stringHash$1(this.doc.uri.toString(), astPath);
5405
- const { title = null, description = null } = this.parseBaseProps(pipe(props, filter(isViewStringProperty), mapToObj((p) => [p.key, p.value])));
5552
+ const deploymentViewBodyProps = pipe(props, filter(isViewStringProperty), mapToObj((p) => [p.key, p.value]));
5553
+ const { title = null, description = null } = this.parseBaseProps(deploymentViewBodyProps, { title: parseTitleKeepingEscapedSlash(deploymentViewBodyProps.title) });
5406
5554
  const tags = this.convertTags(body);
5407
5555
  const links = this.convertLinks(body);
5556
+ const order = parseViewOrder(props.find(isViewOrderProperty));
5408
5557
  ViewOps.writeId(astNode, id);
5409
5558
  return {
5410
5559
  [c4._type]: "deployment",
@@ -5412,6 +5561,7 @@ function DeploymentViewParser(B) {
5412
5561
  astPath,
5413
5562
  title: toSingleLine(title) ?? null,
5414
5563
  description,
5564
+ ...order !== void 0 && { order },
5415
5565
  tags,
5416
5566
  links: isNonEmptyArray$1(links) ? links : null,
5417
5567
  rules: this.tryMap("deployment", body.rules, (n) => this.parseDeploymentViewRule(n))
@@ -5962,7 +6112,7 @@ function ModelParser(B) {
5962
6112
  let [_title, _summary, _technology] = astNode.props ?? [];
5963
6113
  const bodyProps = pipe(astNode.body?.props ?? [], filter(isValid), filter(isElementStringProperty), mapToObj((p) => [p.key, p.value]));
5964
6114
  const { title, ...descAndTech } = this.parseBaseProps(bodyProps, {
5965
- title: _title,
6115
+ title: isDefined(_title) ? parseInlineTitleKeepingEscapedSlash(astNode, "props", 0) ?? _title : parseTitleKeepingEscapedSlash(bodyProps.title),
5966
6116
  summary: _summary,
5967
6117
  technology: _technology
5968
6118
  });
@@ -5984,8 +6134,9 @@ function ModelParser(B) {
5984
6134
  const links = this.parseLinks(astNode.body);
5985
6135
  if (!tags && isEmptyish(metadata) && !links) return null;
5986
6136
  const astPath = this.getAstNodePath(astNode);
6137
+ const id = this.resolveFqn(astNode);
5987
6138
  return exact$1({
5988
- id: this.resolveFqn(astNode),
6139
+ id,
5989
6140
  astPath,
5990
6141
  metadata,
5991
6142
  tags,
@@ -6004,9 +6155,17 @@ function ModelParser(B) {
6004
6155
  if (!tags && isEmpty(metadata ?? {}) && !links) return null;
6005
6156
  const kind = (astNode.kind ?? astNode.dotKind?.kind)?.ref?.name;
6006
6157
  const { title = "" } = this.parseBaseProps({}, { title: astNode.title });
6158
+ const id = relationFingerprint({
6159
+ source,
6160
+ target,
6161
+ kind,
6162
+ title,
6163
+ isBidirectional: astNode.isBidirectional || void 0
6164
+ });
6007
6165
  return exact$1({
6008
- id: stringHash$1("extend-relation", FqnRef$1.flatten(source), FqnRef$1.flatten(target), kind ?? "default", title),
6166
+ id,
6009
6167
  astPath,
6168
+ isBidirectional: astNode.isBidirectional || void 0,
6010
6169
  metadata,
6011
6170
  tags,
6012
6171
  links
@@ -6040,11 +6199,13 @@ function ModelParser(B) {
6040
6199
  technology: astNode.technology
6041
6200
  });
6042
6201
  const styleProp = astNode.body?.props.find(isRelationStyleProperty);
6202
+ const id = stringHash$1(this.docUri, astPath, FqnRef$1.flatten(source), FqnRef$1.flatten(target));
6043
6203
  return exact$1({
6044
- id: stringHash$1(this.docUri, astPath, FqnRef$1.flatten(source), FqnRef$1.flatten(target)),
6204
+ id,
6045
6205
  astPath,
6046
6206
  source,
6047
6207
  target,
6208
+ isBidirectional: astNode.isBidirectional || void 0,
6048
6209
  title,
6049
6210
  metadata,
6050
6211
  kind,
@@ -6152,7 +6313,7 @@ function SpecificationParser(B) {
6152
6313
  for (const tagSpec of tags_specs) try {
6153
6314
  const tag = tagSpec.tag.name;
6154
6315
  const astPath = this.getAstNodePath(tagSpec.tag);
6155
- const color = tagSpec.color && this.parseColorLiteral(tagSpec.color);
6316
+ const color = toColor(tagSpec) ?? (tagSpec.color && this.parseColorLiteral(tagSpec.color));
6156
6317
  if (tag in c4Specification.tags) {
6157
6318
  this.logError(`Tag ${tag} is already defined, skipping duplicate`, tagSpec, "specification");
6158
6319
  continue;
@@ -6239,28 +6400,32 @@ function ViewsParser(B) {
6239
6400
  parseElementView(astNode, additionalStyles) {
6240
6401
  const body = astNode.body;
6241
6402
  invariant$1(body, "ElementView body is not defined");
6403
+ const props = body.props.filter(this.isValid);
6242
6404
  const astPath = this.getAstNodePath(astNode);
6243
6405
  let viewOf = null;
6244
- if ("viewOf" in astNode) {
6245
- const viewOfEl = elementRef(astNode.viewOf);
6246
- const _viewOf = viewOfEl && safeCall(() => this.resolveFqn(viewOfEl));
6406
+ const viewOfNode = astNode.viewOf;
6407
+ if (viewOfNode) {
6408
+ const _viewOf = safeCall(() => this.parseModelElementRef(viewOfNode, "Invalid reference to viewOf"));
6247
6409
  if (!_viewOf) {
6248
6410
  const viewId = astNode.name ?? "unnamed";
6249
- const msg = astNode.viewOf.$cstNode?.text ?? "<unknown>";
6250
- this.logError(`viewOf ${viewId} not resolved ${msg}`, astNode.viewOf);
6411
+ const msg = viewOfNode.$cstNode?.text ?? "<unknown>";
6412
+ this.logError(`viewOf ${viewId} not resolved ${msg}`, viewOfNode);
6251
6413
  } else viewOf = _viewOf;
6252
6414
  }
6253
6415
  let id = astNode.name;
6254
6416
  if (!id) id = "view_" + stringHash$1(this.doc.uri.toString(), astPath, viewOf ?? "");
6255
- const { title = null, description = null } = this.parseBaseProps(pipe(body.props, filter((p) => this.isValid(p)), filter(isViewStringProperty), mapToObj((p) => [p.key, p.value])));
6417
+ const viewBodyProps = pipe(props, filter(isViewStringProperty), mapToObj((p) => [p.key, p.value]));
6418
+ const { title = null, description = null } = this.parseBaseProps(viewBodyProps, { title: parseTitleKeepingEscapedSlash(viewBodyProps.title) });
6256
6419
  const tags = this.convertTags(body);
6257
6420
  const links = this.convertLinks(body);
6421
+ const order = parseViewOrder(props.find(isViewOrderProperty));
6258
6422
  const view = {
6259
6423
  [c4._type]: "element",
6260
6424
  id,
6261
6425
  astPath,
6262
6426
  title: toSingleLine(title) ?? null,
6263
6427
  description,
6428
+ ...order !== void 0 && { order },
6264
6429
  tags,
6265
6430
  links: isNonEmptyArray$1(links) ? links : null,
6266
6431
  rules: [...additionalStyles, ...this.tryMap("views", body.rules, (r) => this.parseElementViewRule(r))],
@@ -6313,6 +6478,10 @@ function ViewsParser(B) {
6313
6478
  groupRules.push(this.parseViewRulePredicate(rule));
6314
6479
  continue;
6315
6480
  }
6481
+ if (isViewRuleGlobalPredicateRef(rule)) {
6482
+ groupRules.push(this.parseViewRuleGlobalPredicateRef(rule));
6483
+ continue;
6484
+ }
6316
6485
  if (isViewRuleGroup(rule)) {
6317
6486
  groupRules.push(this.parseViewRuleGroup(rule));
6318
6487
  continue;
@@ -6355,9 +6524,11 @@ function ViewsParser(B) {
6355
6524
  const astPath = this.getAstNodePath(astNode);
6356
6525
  let id = astNode.name;
6357
6526
  if (!id) id = "dynamic_" + stringHash$1(this.doc.uri.toString(), astPath);
6358
- const { title = null, description = null } = this.parseBaseProps(pipe(props, filter(isViewStringProperty), mapToObj((p) => [p.key, p.value])));
6527
+ const dynamicViewBodyProps = pipe(props, filter(isViewStringProperty), mapToObj((p) => [p.key, p.value]));
6528
+ const { title = null, description = null } = this.parseBaseProps(dynamicViewBodyProps, { title: parseTitleKeepingEscapedSlash(dynamicViewBodyProps.title) });
6359
6529
  const tags = this.convertTags(body);
6360
6530
  const links = this.convertLinks(body);
6531
+ const order = parseViewOrder(props.find(isViewOrderProperty));
6361
6532
  ViewOps.writeId(astNode, id);
6362
6533
  const variant = find(props, isDynamicViewDisplayVariantProperty)?.value;
6363
6534
  return {
@@ -6366,6 +6537,7 @@ function ViewsParser(B) {
6366
6537
  astPath,
6367
6538
  title: toSingleLine(title) ?? null,
6368
6539
  description,
6540
+ ...order !== void 0 && { order },
6369
6541
  tags,
6370
6542
  links: isNonEmptyArray$1(links) ? links : null,
6371
6543
  variant,
@@ -6481,10 +6653,12 @@ function ViewsParser(B) {
6481
6653
  }
6482
6654
  case isRelationStringProperty(prop):
6483
6655
  case isNotationProperty(prop):
6484
- if (isDefined(prop.value)) if (prop.key === "description") {
6485
- const value = removeIndent(prop.value);
6486
- if (value) step.description = value;
6487
- } else step[prop.key] = removeIndent(parseMarkdownAsString(prop.value)) ?? "";
6656
+ if (isDefined(prop.value)) {
6657
+ if (prop.key === "description") {
6658
+ const value = removeIndent(prop.value);
6659
+ if (value) step.description = value;
6660
+ } else step[prop.key] = removeIndent(parseMarkdownAsString(prop.value)) ?? "";
6661
+ }
6488
6662
  break;
6489
6663
  case isNotesProperty(prop):
6490
6664
  if (isDefined(prop.value)) step[prop.key] = removeIndent(prop.value);
@@ -6508,11 +6682,14 @@ function ViewsParser(B) {
6508
6682
  }
6509
6683
  return step;
6510
6684
  }
6511
- parseDynamicStepEndpoint(node, endpoint) {
6685
+ parseModelElementRef(node, errorMessage) {
6512
6686
  const ref = this.parseFqnRef(node.modelElement);
6513
- if (!c4.FqnRef.isModelRef(ref)) throw new Error(`Invalid reference to ${endpoint}`);
6687
+ if (!c4.FqnRef.isModelRef(ref)) throw new Error(errorMessage);
6514
6688
  return c4.FqnRef.flatten(ref);
6515
6689
  }
6690
+ parseDynamicStepEndpoint(node, endpoint) {
6691
+ return this.parseModelElementRef(node, `Invalid reference to ${endpoint}`);
6692
+ }
6516
6693
  parseSubflowStep(node) {
6517
6694
  const kind = node.kind === "parallel" ? "par" : node.kind;
6518
6695
  switch (kind) {
@@ -6764,10 +6941,14 @@ function changeElementStyle(services, { view, viewAst, targets, style }) {
6764
6941
  end: insertPos
6765
6942
  };
6766
6943
  const includeRange = (range) => {
6767
- if (range.start.line <= modifiedRange.start.line) if (range.start.line == modifiedRange.start.line) modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character);
6768
- else modifiedRange.start = range.start;
6769
- if (range.end.line >= modifiedRange.end.line) if (range.end.line == modifiedRange.end.line) modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character);
6770
- else modifiedRange.end = range.end;
6944
+ if (range.start.line <= modifiedRange.start.line) {
6945
+ if (range.start.line == modifiedRange.start.line) modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character);
6946
+ else modifiedRange.start = range.start;
6947
+ }
6948
+ if (range.end.line >= modifiedRange.end.line) {
6949
+ if (range.end.line == modifiedRange.end.line) modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character);
6950
+ else modifiedRange.end = range.end;
6951
+ }
6771
6952
  };
6772
6953
  const edits = [];
6773
6954
  if (insert.length > 0) {
@@ -7006,11 +7187,9 @@ var LikeC4ModelChanges = class {
7006
7187
  const project = workspace.ProjectsManager.ensureProject(_projectId);
7007
7188
  logger$2.debug`Applying model change ${change.op} to view ${viewId} in project ${project.id}`;
7008
7189
  const lookup = this.locator.locateViewAst(viewId, project.id);
7009
- if (!lookup) throw new Error(`View ${viewId} not found in project ${project.id}`);
7010
- const textDocument = {
7011
- uri: lookup.doc.textDocument.uri,
7012
- version: lookup.doc.textDocument.version
7013
- };
7190
+ if (!lookup && (change.op === "save-view-snapshot" || change.op === "reset-manual-layout")) {
7191
+ if (!(await this.services.likec4.ModelBuilder.computeModel(project.id)).findView(viewId)) throw new Error(`View ${viewId} not found in project ${project.id}`);
7192
+ }
7014
7193
  if (change.op === "save-view-snapshot") {
7015
7194
  invariant$1(viewId === change.layout.id, "View ID does not match, expected " + viewId + ", got " + change.layout.id);
7016
7195
  return {
@@ -7022,6 +7201,11 @@ var LikeC4ModelChanges = class {
7022
7201
  success: true,
7023
7202
  location: await workspace.ManualLayouts.remove(project, viewId)
7024
7203
  };
7204
+ if (!lookup) throw new Error(`View ${viewId} not found in project ${project.id}`);
7205
+ const textDocument = {
7206
+ uri: lookup.doc.textDocument.uri,
7207
+ version: lookup.doc.textDocument.version
7208
+ };
7025
7209
  const { edits, modifiedRange } = this.convertToTextEdit({
7026
7210
  lookup,
7027
7211
  change
@@ -7582,7 +7766,8 @@ var Rpc = class extends ADisposable {
7582
7766
  return { result: reportLayoutDrift((await likec4Services.Views.layoutAllViews(projectId, cancelToken)).map((l) => l.diagram)) };
7583
7767
  }), connection.onRequest(FetchProjects.req, async () => {
7584
7768
  logger$1.debug`received request ${"FetchProjects"}`;
7585
- return { projects: mapValues(LangiumDocuments.groupedByProject(), (docs, projectId) => {
7769
+ const docsByProject = LangiumDocuments.groupedByProject();
7770
+ return { projects: mapValues(docsByProject, (docs, projectId) => {
7586
7771
  const { folderUri, config: { name, title } } = workspace.ProjectsManager.getProject(projectId);
7587
7772
  return {
7588
7773
  folder: folderUri.toString(),
@@ -7812,7 +7997,8 @@ var DefaultLikeC4Views = class extends ADisposable {
7812
7997
  return this.services.likec4.Layouter;
7813
7998
  }
7814
7999
  async computedViews(projectId, cancelToken) {
7815
- return values((await this.ModelBuilder.computeModel(projectId, cancelToken)).$data.views);
8000
+ const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
8001
+ return values(likeC4Model.$data.views);
7816
8002
  }
7817
8003
  async _layoutAllViews(likeC4Model, cancelToken) {
7818
8004
  const views = values(likeC4Model.$data.views);
@@ -7835,8 +8021,10 @@ var DefaultLikeC4Views = class extends ADisposable {
7835
8021
  }
7836
8022
  tasks.push(task);
7837
8023
  }
7838
- if (results.length > 0) if (tasks.length > 0) logger.trace`cached ${results.length} views, need to layout ${tasks.length} views`;
7839
- else logger.trace`cached all ${results.length} views`;
8024
+ if (results.length > 0) {
8025
+ if (tasks.length > 0) logger.trace`cached ${results.length} views, need to layout ${tasks.length} views`;
8026
+ else logger.trace`cached all ${results.length} views`;
8027
+ }
7840
8028
  if (tasks.length > 0) {
7841
8029
  const m0 = performanceMark();
7842
8030
  await this.layouter.batchLayout({
@@ -8155,7 +8343,7 @@ function createLikeC4Module(context) {
8155
8343
  }
8156
8344
  function createLanguageServices(context = {}, module, module2, module3) {
8157
8345
  const shared = createSharedServices(context);
8158
- const likec4 = inject([
8346
+ const modules = [
8159
8347
  createDefaultModule({ shared }),
8160
8348
  LikeC4GeneratedModule,
8161
8349
  createLikeC4Module({
@@ -8167,7 +8355,8 @@ function createLanguageServices(context = {}, module, module2, module3) {
8167
8355
  module,
8168
8356
  module2,
8169
8357
  module3
8170
- ].reduce(_merge, {}));
8358
+ ].reduce(_merge, {});
8359
+ const likec4 = inject(modules);
8171
8360
  shared.ServiceRegistry.register(likec4);
8172
8361
  registerValidationChecks(likec4);
8173
8362
  if (!context.connection) shared.workspace.ConfigurationProvider.initialized({});