@likec4/language-server 1.19.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Rpc.js +6 -0
- package/dist/browser.d.ts +1 -0
- package/dist/documentation/documentation-provider.d.ts +8 -0
- package/dist/documentation/documentation-provider.js +46 -0
- package/dist/documentation/index.d.ts +1 -0
- package/dist/documentation/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/dist/lsp/DocumentSymbolProvider.d.ts +11 -2
- package/dist/lsp/DocumentSymbolProvider.js +78 -6
- package/dist/model/fqn-computation.js +2 -2
- package/dist/model/model-builder.js +3 -3
- package/dist/module.d.ts +9 -1
- package/dist/module.js +47 -8
- package/dist/protocol.d.ts +19 -1
- package/dist/protocol.js +1 -0
- package/dist/references/scope-computation.d.ts +2 -2
- package/dist/references/scope-computation.js +9 -9
- package/dist/validation/dynamic-view-rule.js +3 -2
- package/dist/validation/dynamic-view-step.js +23 -27
- package/dist/validation/property-checks.js +3 -2
- package/dist/validation/specification.js +14 -14
- package/dist/validation/view-predicates/element-with.js +3 -2
- package/dist/validation/view-predicates/expanded-element.js +3 -2
- package/dist/validation/view-predicates/incoming.js +3 -2
- package/dist/validation/view-predicates/outgoing.js +3 -2
- package/dist/validation/view-predicates/relation-with.js +3 -2
- package/dist/validation/view.js +3 -3
- package/dist/views/configurable-layouter.d.ts +7 -0
- package/dist/views/configurable-layouter.js +55 -0
- package/dist/views/index.d.ts +1 -0
- package/dist/views/index.js +1 -0
- package/dist/views/likec4-views.d.ts +26 -0
- package/dist/views/likec4-views.js +113 -0
- package/package.json +13 -10
- package/src/Rpc.ts +13 -7
- package/src/browser.ts +1 -0
- package/src/documentation/documentation-provider.ts +52 -0
- package/src/documentation/index.ts +1 -0
- package/src/index.ts +4 -2
- package/src/lsp/DocumentSymbolProvider.ts +110 -28
- package/src/model/fqn-computation.ts +8 -8
- package/src/model/model-builder.ts +52 -52
- package/src/module.ts +56 -9
- package/src/protocol.ts +29 -4
- package/src/references/scope-computation.ts +35 -35
- package/src/validation/dynamic-view-rule.ts +5 -4
- package/src/validation/dynamic-view-step.ts +23 -26
- package/src/validation/property-checks.ts +11 -10
- package/src/validation/specification.ts +38 -38
- package/src/validation/view-predicates/element-with.ts +6 -5
- package/src/validation/view-predicates/expanded-element.ts +6 -5
- package/src/validation/view-predicates/incoming.ts +6 -5
- package/src/validation/view-predicates/outgoing.ts +6 -5
- package/src/validation/view-predicates/relation-with.ts +6 -5
- package/src/validation/view.ts +5 -5
- package/src/views/configurable-layouter.ts +65 -0
- package/src/views/index.ts +1 -0
- package/src/views/likec4-views.ts +139 -0
|
@@ -1,33 +1,29 @@
|
|
|
1
1
|
import { isAncestor } from "@likec4/core";
|
|
2
|
-
import { logError } from "../logger.js";
|
|
3
2
|
import { elementRef } from "../utils/elementRef.js";
|
|
3
|
+
import { tryOrLog } from "./_shared.js";
|
|
4
4
|
export const dynamicViewStep = (services) => {
|
|
5
5
|
const fqnIndex = services.likec4.FqnIndex;
|
|
6
|
-
return (el, accept) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
const targetEl = elementRef(el.target);
|
|
17
|
-
const target = targetEl && fqnIndex.getFqn(targetEl);
|
|
18
|
-
if (!target) {
|
|
19
|
-
accept("error", "Target not found (not parsed/indexed yet)", {
|
|
20
|
-
node: el,
|
|
21
|
-
property: "target"
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
if (source && target && (isAncestor(source, target) || isAncestor(target, source))) {
|
|
25
|
-
accept("error", "Invalid parent-child relationship", {
|
|
26
|
-
node: el
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
} catch (e) {
|
|
30
|
-
logError(e);
|
|
6
|
+
return tryOrLog((el, accept) => {
|
|
7
|
+
const sourceEl = elementRef(el.source);
|
|
8
|
+
const source = sourceEl && fqnIndex.getFqn(sourceEl);
|
|
9
|
+
if (!source) {
|
|
10
|
+
accept("error", "Source not found (not parsed/indexed yet)", {
|
|
11
|
+
node: el,
|
|
12
|
+
property: "source"
|
|
13
|
+
});
|
|
31
14
|
}
|
|
32
|
-
|
|
15
|
+
const targetEl = elementRef(el.target);
|
|
16
|
+
const target = targetEl && fqnIndex.getFqn(targetEl);
|
|
17
|
+
if (!target) {
|
|
18
|
+
accept("error", "Target not found (not parsed/indexed yet)", {
|
|
19
|
+
node: el,
|
|
20
|
+
property: "target"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (source && target && (isAncestor(source, target) || isAncestor(target, source))) {
|
|
24
|
+
accept("error", "Invalid parent-child relationship", {
|
|
25
|
+
node: el
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
33
29
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AstUtils } from "langium";
|
|
2
2
|
import { ast } from "../ast.js";
|
|
3
|
+
import { tryOrLog } from "./_shared.js";
|
|
3
4
|
export const opacityPropertyRuleChecks = (_) => {
|
|
4
|
-
return (node, accept) => {
|
|
5
|
+
return tryOrLog((node, accept) => {
|
|
5
6
|
const opacity = parseFloat(node.value);
|
|
6
7
|
if (isNaN(opacity) || opacity < 0 || opacity > 100) {
|
|
7
8
|
accept("warning", `Value ignored, must be between 0% and 100%`, {
|
|
@@ -9,7 +10,7 @@ export const opacityPropertyRuleChecks = (_) => {
|
|
|
9
10
|
property: "value"
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
|
-
};
|
|
13
|
+
});
|
|
13
14
|
};
|
|
14
15
|
export const iconPropertyRuleChecks = (_) => {
|
|
15
16
|
return (node, accept) => {
|
|
@@ -2,34 +2,34 @@ import { AstUtils } from "langium";
|
|
|
2
2
|
import { ast } from "../ast.js";
|
|
3
3
|
import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
|
|
4
4
|
export const specificationRuleChecks = (_) => {
|
|
5
|
-
return (node, accept) => {
|
|
5
|
+
return tryOrLog((node, accept) => {
|
|
6
6
|
if (node.$containerIndex && node.$containerIndex > 0) {
|
|
7
7
|
accept("warning", `Prefer one specification per document`, {
|
|
8
8
|
node,
|
|
9
9
|
property: "name"
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
-
};
|
|
12
|
+
});
|
|
13
13
|
};
|
|
14
14
|
export const modelRuleChecks = (_) => {
|
|
15
|
-
return (node, accept) => {
|
|
15
|
+
return tryOrLog((node, accept) => {
|
|
16
16
|
if (node.$containerIndex && node.$containerIndex > 0) {
|
|
17
17
|
accept("warning", `Prefer one model per document`, {
|
|
18
18
|
node,
|
|
19
19
|
property: "name"
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
};
|
|
22
|
+
});
|
|
23
23
|
};
|
|
24
24
|
export const globalsChecks = (_) => {
|
|
25
|
-
return (node, accept) => {
|
|
25
|
+
return tryOrLog((node, accept) => {
|
|
26
26
|
if (node.$containerIndex && node.$containerIndex > 0) {
|
|
27
27
|
accept("warning", `Prefer one global block per document`, {
|
|
28
28
|
node,
|
|
29
29
|
property: "name"
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
};
|
|
32
|
+
});
|
|
33
33
|
};
|
|
34
34
|
export const elementKindChecks = (services) => {
|
|
35
35
|
const index = services.shared.workspace.IndexManager;
|
|
@@ -63,7 +63,7 @@ export const elementKindChecks = (services) => {
|
|
|
63
63
|
};
|
|
64
64
|
export const tagChecks = (services) => {
|
|
65
65
|
const index = services.shared.workspace.IndexManager;
|
|
66
|
-
return (node, accept) => {
|
|
66
|
+
return tryOrLog((node, accept) => {
|
|
67
67
|
const tagname = "#" + node.name;
|
|
68
68
|
const sameTag = index.allElements(ast.Tag).filter((n) => n.name === tagname && n.node !== node).head();
|
|
69
69
|
if (sameTag) {
|
|
@@ -88,11 +88,11 @@ export const tagChecks = (services) => {
|
|
|
88
88
|
}
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
|
-
};
|
|
91
|
+
});
|
|
92
92
|
};
|
|
93
93
|
export const relationshipChecks = (services) => {
|
|
94
94
|
const index = services.shared.workspace.IndexManager;
|
|
95
|
-
return (node, accept) => {
|
|
95
|
+
return tryOrLog((node, accept) => {
|
|
96
96
|
if (RESERVED_WORDS.includes(node.name)) {
|
|
97
97
|
accept("error", `Reserved word: ${node.name}`, {
|
|
98
98
|
node,
|
|
@@ -106,11 +106,11 @@ export const relationshipChecks = (services) => {
|
|
|
106
106
|
property: "name"
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
-
};
|
|
109
|
+
});
|
|
110
110
|
};
|
|
111
111
|
export const globalPredicateChecks = (services) => {
|
|
112
112
|
const index = services.shared.workspace.IndexManager;
|
|
113
|
-
return (node, accept) => {
|
|
113
|
+
return tryOrLog((node, accept) => {
|
|
114
114
|
const predicateGroups = index.allElements(ast.GlobalPredicateGroup);
|
|
115
115
|
const dynamicPredicateGroups = index.allElements(ast.GlobalDynamicPredicateGroup);
|
|
116
116
|
const sameName = predicateGroups.concat(dynamicPredicateGroups).filter((s) => s.name === node.name).limit(2).count();
|
|
@@ -120,11 +120,11 @@ export const globalPredicateChecks = (services) => {
|
|
|
120
120
|
property: "name"
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
};
|
|
123
|
+
});
|
|
124
124
|
};
|
|
125
125
|
export const globalStyleIdChecks = (services) => {
|
|
126
126
|
const index = services.shared.workspace.IndexManager;
|
|
127
|
-
return (node, accept) => {
|
|
127
|
+
return tryOrLog((node, accept) => {
|
|
128
128
|
const sameName = index.allElements(ast.GlobalStyleId).filter((s) => s.name === node.name).limit(2).count();
|
|
129
129
|
if (sameName > 1) {
|
|
130
130
|
accept("error", `Duplicate GlobalStyleId name '${node.name}'`, {
|
|
@@ -132,5 +132,5 @@ export const globalStyleIdChecks = (services) => {
|
|
|
132
132
|
property: "name"
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
-
};
|
|
135
|
+
});
|
|
136
136
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { nonexhaustive } from "@likec4/core";
|
|
2
2
|
import { AstUtils } from "langium";
|
|
3
3
|
import { ast } from "../../ast.js";
|
|
4
|
+
import { tryOrLog } from "../_shared.js";
|
|
4
5
|
export const elementPredicateWithChecks = (_services) => {
|
|
5
|
-
return (el, accept) => {
|
|
6
|
+
return tryOrLog((el, accept) => {
|
|
6
7
|
const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate);
|
|
7
8
|
if (ast.isExcludePredicate(container)) {
|
|
8
9
|
accept("error", 'Invalid usage inside "exclude"', {
|
|
@@ -26,5 +27,5 @@ export const elementPredicateWithChecks = (_services) => {
|
|
|
26
27
|
default:
|
|
27
28
|
nonexhaustive(subject);
|
|
28
29
|
}
|
|
29
|
-
};
|
|
30
|
+
});
|
|
30
31
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AstUtils } from "langium";
|
|
2
2
|
import { ast } from "../../ast.js";
|
|
3
|
+
import { tryOrLog } from "../_shared.js";
|
|
3
4
|
export const expandElementExprChecks = (_services) => {
|
|
4
|
-
return (el, accept) => {
|
|
5
|
+
return tryOrLog((el, accept) => {
|
|
5
6
|
if (AstUtils.hasContainerOfType(el, ast.isRelationExpression)) {
|
|
6
7
|
accept("warning", `Redundant usage, expand predicate resolves parent element only when used in relations`, {
|
|
7
8
|
node: el
|
|
8
9
|
});
|
|
9
10
|
}
|
|
10
|
-
};
|
|
11
|
+
});
|
|
11
12
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AstUtils } from "langium";
|
|
2
2
|
import { isNullish } from "remeda";
|
|
3
3
|
import { ast } from "../../ast.js";
|
|
4
|
+
import { tryOrLog } from "../_shared.js";
|
|
4
5
|
export const incomingExpressionChecks = (_services) => {
|
|
5
|
-
return (el, accept) => {
|
|
6
|
+
return tryOrLog((el, accept) => {
|
|
6
7
|
if (ast.isWildcardExpression(el.to) && !ast.isInOutRelationExpression(el.$container)) {
|
|
7
8
|
const view = AstUtils.getContainerOfType(el, ast.isElementView);
|
|
8
9
|
if (isNullish(view?.viewOf)) {
|
|
@@ -11,5 +12,5 @@ export const incomingExpressionChecks = (_services) => {
|
|
|
11
12
|
});
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
};
|
|
15
|
+
});
|
|
15
16
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AstUtils } from "langium";
|
|
2
2
|
import { isNullish } from "remeda";
|
|
3
3
|
import { ast } from "../../ast.js";
|
|
4
|
+
import { tryOrLog } from "../_shared.js";
|
|
4
5
|
export const outgoingExpressionChecks = (_services) => {
|
|
5
|
-
return (el, accept) => {
|
|
6
|
+
return tryOrLog((el, accept) => {
|
|
6
7
|
if (ast.isWildcardExpression(el.from) && !ast.isDirectedRelationExpression(el.$container)) {
|
|
7
8
|
const view = AstUtils.getContainerOfType(el, ast.isElementView);
|
|
8
9
|
if (isNullish(view?.viewOf)) {
|
|
@@ -11,5 +12,5 @@ export const outgoingExpressionChecks = (_services) => {
|
|
|
11
12
|
});
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
};
|
|
15
|
+
});
|
|
15
16
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { AstUtils } from "langium";
|
|
2
2
|
import { ast } from "../../ast.js";
|
|
3
|
+
import { tryOrLog } from "../_shared.js";
|
|
3
4
|
export const relationPredicateWithChecks = (_services) => {
|
|
4
|
-
return (el, accept) => {
|
|
5
|
+
return tryOrLog((el, accept) => {
|
|
5
6
|
const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate);
|
|
6
7
|
if (ast.isExcludePredicate(container)) {
|
|
7
8
|
accept("error", 'Invalid usage inside "exclude"', {
|
|
8
9
|
node: el
|
|
9
10
|
});
|
|
10
11
|
}
|
|
11
|
-
};
|
|
12
|
+
});
|
|
12
13
|
};
|
package/dist/validation/view.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ast } from "../ast.js";
|
|
2
|
-
import { RESERVED_WORDS } from "./_shared.js";
|
|
2
|
+
import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
|
|
3
3
|
export const viewChecks = (services) => {
|
|
4
4
|
const index = services.shared.workspace.IndexManager;
|
|
5
|
-
return (el, accept) => {
|
|
5
|
+
return tryOrLog((el, accept) => {
|
|
6
6
|
if (!el.name) {
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
@@ -19,5 +19,5 @@ export const viewChecks = (services) => {
|
|
|
19
19
|
property: "name"
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
};
|
|
22
|
+
});
|
|
23
23
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { GraphvizLayouter, GraphvizWasmAdapter } from "@likec4/layouts";
|
|
2
|
+
import { GraphvizBinaryAdapter } from "@likec4/layouts/graphviz/binary";
|
|
3
|
+
import { isEmpty } from "remeda";
|
|
4
|
+
import which from "which";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
function graphvizBinPath() {
|
|
7
|
+
try {
|
|
8
|
+
return which.sync("dot");
|
|
9
|
+
} catch (error) {
|
|
10
|
+
logger.error("Error checking for native Graphviz:", error);
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const ConfigurableLayouter = {
|
|
15
|
+
likec4: {
|
|
16
|
+
Layouter(services) {
|
|
17
|
+
logger.debug("Creating ConfigurableLayouter");
|
|
18
|
+
const wasmAdapter = new GraphvizWasmAdapter();
|
|
19
|
+
const layouter = new GraphvizLayouter(wasmAdapter);
|
|
20
|
+
const langId = services.LanguageMetaData.languageId;
|
|
21
|
+
services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
|
|
22
|
+
logger.debug("Configuration update", update);
|
|
23
|
+
if (update.section === langId) {
|
|
24
|
+
try {
|
|
25
|
+
const { mode, path } = update.configuration.graphviz ?? {
|
|
26
|
+
mode: "wasm",
|
|
27
|
+
path: ""
|
|
28
|
+
};
|
|
29
|
+
if (mode === "wasm") {
|
|
30
|
+
layouter.changePort(wasmAdapter);
|
|
31
|
+
logger.info("use graphviz wasm");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let binaryPath = isEmpty(path) ? graphvizBinPath() : path;
|
|
35
|
+
if (binaryPath === null) {
|
|
36
|
+
layouter.changePort(wasmAdapter);
|
|
37
|
+
logger.warn(`No Graphviz binaries found on PATH, use graphviz wasm`);
|
|
38
|
+
services.shared.lsp.Connection?.window.showWarningMessage(
|
|
39
|
+
"No Graphviz binaries found on PATH, set path to binaries in settings."
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
layouter.changePort(new GraphvizBinaryAdapter(binaryPath));
|
|
44
|
+
logger.info(`use graphviz binary: ${binaryPath}`);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.error("Failed to update configuration", error);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
logger.warn("Unexpected configuration update", update);
|
|
51
|
+
});
|
|
52
|
+
return layouter;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LikeC4Views } from './likec4-views';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LikeC4Views } from "./likec4-views.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComputedView, DiagramView, OverviewGraph, ViewId } from '@likec4/core';
|
|
2
|
+
import { type Cancellation } from 'langium';
|
|
3
|
+
import type { LikeC4Services } from '../module';
|
|
4
|
+
export type GraphvizOut = {
|
|
5
|
+
dot: string;
|
|
6
|
+
diagram: DiagramView;
|
|
7
|
+
};
|
|
8
|
+
type GraphvizSvgOut = {
|
|
9
|
+
id: ViewId;
|
|
10
|
+
dot: string;
|
|
11
|
+
svg: string;
|
|
12
|
+
};
|
|
13
|
+
export declare class LikeC4Views {
|
|
14
|
+
private services;
|
|
15
|
+
private cache;
|
|
16
|
+
private viewsWithReportedErrors;
|
|
17
|
+
constructor(services: LikeC4Services);
|
|
18
|
+
private get layouter();
|
|
19
|
+
computedViews(cancelToken?: Cancellation.CancellationToken): Promise<ComputedView[]>;
|
|
20
|
+
layoutAllViews(cancelToken?: Cancellation.CancellationToken): Promise<Array<Readonly<GraphvizOut>>>;
|
|
21
|
+
layoutView(viewId: ViewId, cancelToken?: Cancellation.CancellationToken): Promise<GraphvizOut | null>;
|
|
22
|
+
diagrams(): Promise<Array<DiagramView>>;
|
|
23
|
+
viewsAsGraphvizOut(): Promise<Array<GraphvizSvgOut>>;
|
|
24
|
+
overviewGraph(): Promise<OverviewGraph>;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { values } from "remeda";
|
|
2
|
+
import { logger, logWarnError } from "../logger.js";
|
|
3
|
+
export class LikeC4Views {
|
|
4
|
+
constructor(services) {
|
|
5
|
+
this.services = services;
|
|
6
|
+
}
|
|
7
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
viewsWithReportedErrors = /* @__PURE__ */ new Set();
|
|
9
|
+
get layouter() {
|
|
10
|
+
return this.services.likec4.Layouter;
|
|
11
|
+
}
|
|
12
|
+
async computedViews(cancelToken) {
|
|
13
|
+
const model = await this.services.likec4.ModelBuilder.buildComputedModel(cancelToken);
|
|
14
|
+
return model ? values(model.views) : [];
|
|
15
|
+
}
|
|
16
|
+
async layoutAllViews(cancelToken) {
|
|
17
|
+
const views = await this.computedViews(cancelToken);
|
|
18
|
+
if (views.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const results = [];
|
|
22
|
+
const tasks = [];
|
|
23
|
+
for (const view of views) {
|
|
24
|
+
this.viewsWithReportedErrors.delete(view.id);
|
|
25
|
+
tasks.push(
|
|
26
|
+
this.layouter.layout(view).then((result) => {
|
|
27
|
+
this.cache.set(view, result);
|
|
28
|
+
return result;
|
|
29
|
+
}).catch((e) => {
|
|
30
|
+
this.cache.delete(view);
|
|
31
|
+
logWarnError(e);
|
|
32
|
+
return Promise.reject(e);
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
for (const task of await Promise.allSettled(tasks)) {
|
|
37
|
+
if (task.status === "fulfilled") {
|
|
38
|
+
results.push(task.value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
async layoutView(viewId, cancelToken) {
|
|
44
|
+
const model = await this.services.likec4.ModelBuilder.buildComputedModel(cancelToken);
|
|
45
|
+
if (!model) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const view = model.views[viewId];
|
|
49
|
+
if (!view) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
let cached = this.cache.get(view);
|
|
53
|
+
if (cached) {
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const result = await this.layouter.layout(view);
|
|
58
|
+
this.viewsWithReportedErrors.delete(viewId);
|
|
59
|
+
this.cache.set(view, result);
|
|
60
|
+
return result;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (!this.viewsWithReportedErrors.has(viewId)) {
|
|
63
|
+
const errMessage = e instanceof Error ? e.message : "" + e;
|
|
64
|
+
this.services.shared.lsp.Connection?.window.showErrorMessage(`LikeC4: ${errMessage}`);
|
|
65
|
+
this.viewsWithReportedErrors.add(viewId);
|
|
66
|
+
}
|
|
67
|
+
logger.error(e);
|
|
68
|
+
return Promise.reject(e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async diagrams() {
|
|
72
|
+
const layouted = await this.layoutAllViews();
|
|
73
|
+
return layouted.map((l) => l.diagram);
|
|
74
|
+
}
|
|
75
|
+
async viewsAsGraphvizOut() {
|
|
76
|
+
const KEY = "All-LayoutedViews-DotWithSvg";
|
|
77
|
+
const cache = this.services.WorkspaceCache;
|
|
78
|
+
if (cache.has(KEY)) {
|
|
79
|
+
return await Promise.resolve(cache.get(KEY));
|
|
80
|
+
}
|
|
81
|
+
const views = await this.computedViews();
|
|
82
|
+
const tasks = views.map(async (view) => {
|
|
83
|
+
const { dot, svg } = await this.layouter.svg(view);
|
|
84
|
+
return {
|
|
85
|
+
id: view.id,
|
|
86
|
+
dot,
|
|
87
|
+
svg
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const succeed = [];
|
|
91
|
+
const settledResult = await Promise.allSettled(tasks);
|
|
92
|
+
for (const result of settledResult) {
|
|
93
|
+
if (result.status === "fulfilled") {
|
|
94
|
+
succeed.push(result.value);
|
|
95
|
+
} else {
|
|
96
|
+
logWarnError(result.reason);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
cache.set(KEY, succeed);
|
|
100
|
+
return succeed;
|
|
101
|
+
}
|
|
102
|
+
async overviewGraph() {
|
|
103
|
+
const KEY = "OverviewGraph";
|
|
104
|
+
const cache = this.services.WorkspaceCache;
|
|
105
|
+
if (cache.has(KEY)) {
|
|
106
|
+
return await Promise.resolve(cache.get(KEY));
|
|
107
|
+
}
|
|
108
|
+
const views = await this.computedViews();
|
|
109
|
+
const overviewGraph = await this.layouter.layoutOverviewGraph(views);
|
|
110
|
+
cache.set(KEY, overviewGraph);
|
|
111
|
+
return overviewGraph;
|
|
112
|
+
}
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/language-server",
|
|
3
3
|
"description": "LikeC4 Language Server",
|
|
4
|
-
"version": "1.19.
|
|
4
|
+
"version": "1.19.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
27
|
-
"
|
|
27
|
+
"sources": "./src/index.ts",
|
|
28
28
|
"node": {
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"import": "./dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"./likec4lib": {
|
|
40
|
-
"
|
|
40
|
+
"sources": "./src/likec4lib.ts",
|
|
41
41
|
"default": {
|
|
42
42
|
"types": "./dist/likec4lib.d.ts",
|
|
43
43
|
"import": "./dist/likec4lib.js",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"./browser": {
|
|
48
|
-
"
|
|
48
|
+
"sources": "./src/browser.ts",
|
|
49
49
|
"default": {
|
|
50
50
|
"types": "./dist/browser.d.ts",
|
|
51
51
|
"import": "./dist/browser.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"./protocol": {
|
|
56
|
-
"
|
|
56
|
+
"sources": "./src/protocol.ts",
|
|
57
57
|
"default": {
|
|
58
58
|
"types": "./dist/protocol.d.ts",
|
|
59
59
|
"import": "./dist/protocol.js",
|
|
@@ -82,8 +82,9 @@
|
|
|
82
82
|
"test:watch": "vitest"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@likec4/core": "1.19.
|
|
86
|
-
"@likec4/
|
|
85
|
+
"@likec4/core": "1.19.1",
|
|
86
|
+
"@likec4/layouts": "1.19.1",
|
|
87
|
+
"@likec4/log": "1.19.1",
|
|
87
88
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
88
89
|
"@smithy/util-base64": "^3.0.0",
|
|
89
90
|
"esm-env": "^1.2.1",
|
|
@@ -100,12 +101,14 @@
|
|
|
100
101
|
"vscode-jsonrpc": "8.2.0",
|
|
101
102
|
"vscode-languageserver": "9.0.1",
|
|
102
103
|
"vscode-languageserver-types": "3.17.5",
|
|
103
|
-
"vscode-uri": "3.0.8"
|
|
104
|
+
"vscode-uri": "3.0.8",
|
|
105
|
+
"which": "^4.0.0"
|
|
104
106
|
},
|
|
105
107
|
"devDependencies": {
|
|
106
|
-
"@likec4/icons": "1.19.
|
|
107
|
-
"@likec4/tsconfig": "1.19.
|
|
108
|
+
"@likec4/icons": "1.19.1",
|
|
109
|
+
"@likec4/tsconfig": "1.19.1",
|
|
108
110
|
"@types/node": "^20.17.7",
|
|
111
|
+
"@types/which": "^3.0.4",
|
|
109
112
|
"@vitest/coverage-v8": "^2.1.8",
|
|
110
113
|
"execa": "^9.3.1",
|
|
111
114
|
"langium-cli": "3.3.0",
|
package/src/Rpc.ts
CHANGED
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
computeView,
|
|
13
13
|
fetchComputedModel,
|
|
14
14
|
fetchModel,
|
|
15
|
+
layoutView,
|
|
15
16
|
locate,
|
|
16
|
-
onDidChangeModel
|
|
17
|
+
onDidChangeModel,
|
|
17
18
|
} from './protocol'
|
|
18
19
|
|
|
19
20
|
export class Rpc implements Disposable {
|
|
@@ -25,6 +26,7 @@ export class Rpc implements Disposable {
|
|
|
25
26
|
const modelBuilder = this.services.likec4.ModelBuilder
|
|
26
27
|
const modelLocator = this.services.likec4.ModelLocator
|
|
27
28
|
const modelEditor = this.services.likec4.ModelChanges
|
|
29
|
+
const views = this.services.likec4.Views
|
|
28
30
|
const connection = this.services.shared.lsp.Connection
|
|
29
31
|
if (!connection) {
|
|
30
32
|
logger.info(`[ServerRpc] no connection, not initializing`)
|
|
@@ -44,8 +46,8 @@ export class Rpc implements Disposable {
|
|
|
44
46
|
{
|
|
45
47
|
timing: 'trailing',
|
|
46
48
|
waitMs: 300,
|
|
47
|
-
maxWaitMs: 1000
|
|
48
|
-
}
|
|
49
|
+
maxWaitMs: 1000,
|
|
50
|
+
},
|
|
49
51
|
)
|
|
50
52
|
|
|
51
53
|
let isFirstBuild = true
|
|
@@ -70,6 +72,10 @@ export class Rpc implements Disposable {
|
|
|
70
72
|
const view = await modelBuilder.computeView(viewId, cancelToken)
|
|
71
73
|
return { view }
|
|
72
74
|
}),
|
|
75
|
+
connection.onRequest(layoutView, async ({ viewId }, cancelToken) => {
|
|
76
|
+
const result = await views.layoutView(viewId, cancelToken)
|
|
77
|
+
return { result }
|
|
78
|
+
}),
|
|
73
79
|
connection.onRequest(buildDocuments, async ({ docs }, cancelToken) => {
|
|
74
80
|
const changed = docs.map(d => URI.parse(d))
|
|
75
81
|
const notChanged = (uri: URI) => changed.every(c => !UriUtils.equals(c, uri))
|
|
@@ -80,7 +86,7 @@ export class Rpc implements Disposable {
|
|
|
80
86
|
logger.debug(
|
|
81
87
|
`[ServerRpc] received request to build:
|
|
82
88
|
changed (total ${changed.length}):${docs.map(d => '\n - ' + d).join('')}
|
|
83
|
-
deleted (total ${deleted.length}):${deleted.map(d => '\n - ' + d.toString()).join('\n')}
|
|
89
|
+
deleted (total ${deleted.length}):${deleted.map(d => '\n - ' + d.toString()).join('\n')}`,
|
|
84
90
|
)
|
|
85
91
|
|
|
86
92
|
if (!isFirstBuild && (changed.length + deleted.length) > 0) {
|
|
@@ -91,13 +97,13 @@ export class Rpc implements Disposable {
|
|
|
91
97
|
try {
|
|
92
98
|
await connection.sendDiagnostics({
|
|
93
99
|
uri,
|
|
94
|
-
diagnostics: []
|
|
100
|
+
diagnostics: [],
|
|
95
101
|
})
|
|
96
102
|
} catch (e) {
|
|
97
103
|
// Ignore
|
|
98
104
|
logger.warn(`error clearing diagnostics for ${uri}: ${e}`)
|
|
99
105
|
}
|
|
100
|
-
})
|
|
106
|
+
}),
|
|
101
107
|
)
|
|
102
108
|
await interruptAndCheck(cancelToken)
|
|
103
109
|
}
|
|
@@ -121,7 +127,7 @@ export class Rpc implements Disposable {
|
|
|
121
127
|
}),
|
|
122
128
|
connection.onRequest(changeView, async (request, _cancelToken) => {
|
|
123
129
|
return await modelEditor.applyChange(request)
|
|
124
|
-
})
|
|
130
|
+
}),
|
|
125
131
|
)
|
|
126
132
|
}
|
|
127
133
|
|
package/src/browser.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type { DocumentParser, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4Mode
|
|
|
7
7
|
|
|
8
8
|
export { createCustomLanguageServices, createLanguageServices, LikeC4Module } from './module'
|
|
9
9
|
export type { LikeC4Services, LikeC4SharedServices } from './module'
|
|
10
|
+
export type { LikeC4Views } from './views'
|
|
10
11
|
|
|
11
12
|
// This is an example copied as is from here:
|
|
12
13
|
// https://github.com/microsoft/vscode-extension-samples/blob/main/lsp-web-extension-sample/server/src/browserServerMain.ts
|