@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.
- package/dist/browser/index.mjs +4 -2
- package/dist/chunks/LikeC4FileSystem.mjs +1 -1
- package/dist/chunks/module.d.mts +37 -5
- package/dist/chunks/module.mjs +293 -104
- package/dist/chunks/utils.mjs +28 -4
- package/dist/filesystem/index.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/module.mjs +1 -1
- package/dist/protocol.mjs +2 -1
- package/package.json +15 -15
package/dist/chunks/module.mjs
CHANGED
|
@@ -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
|
|
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))
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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 (
|
|
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
|
-
|
|
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)
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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)
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2011
|
-
label: keyword.value,
|
|
2040
|
+
acceptReplacing({
|
|
2012
2041
|
detail: `Insert ${keyword.value}`,
|
|
2013
2042
|
kind: CompletionItemKind.Snippet,
|
|
2014
|
-
|
|
2015
|
-
|
|
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
|
-
|
|
2153
|
-
label: keyword.value,
|
|
2179
|
+
acceptReplacing({
|
|
2154
2180
|
kind: CompletionItemKind.Operator,
|
|
2155
2181
|
detail: `Insert ${keyword.value} predicate`,
|
|
2156
|
-
insertTextFormat: InsertTextFormat.PlainText
|
|
2157
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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))
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
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
|
|
5391
|
+
summary,
|
|
5249
5392
|
description,
|
|
5250
|
-
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
|
|
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
|
|
5351
|
-
astPath
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
6245
|
-
|
|
6246
|
-
const _viewOf =
|
|
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 =
|
|
6250
|
-
this.logError(`viewOf ${viewId} not resolved ${msg}`,
|
|
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
|
|
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
|
|
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))
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
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
|
-
|
|
6685
|
+
parseModelElementRef(node, errorMessage) {
|
|
6512
6686
|
const ref = this.parseFqnRef(node.modelElement);
|
|
6513
|
-
if (!c4.FqnRef.isModelRef(ref)) throw new Error(
|
|
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)
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
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
|
|
7010
|
-
|
|
7011
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
7839
|
-
|
|
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
|
|
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({});
|