@likec4/language-server 1.32.1 → 1.32.2
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/bundled.mjs +2282 -2286
- package/dist/config/schema.d.ts +2 -2
- package/dist/config/schema.js +8 -11
- package/dist/formatting/LikeC4Formatter.js +2 -2
- package/dist/generated/ast.d.ts +2 -0
- package/dist/generated/ast.js +2 -0
- package/dist/generated/grammar.js +1 -1
- package/dist/mcp/LikeC4MCPServerFactory.d.ts +4 -0
- package/dist/mcp/LikeC4MCPServerFactory.js +6 -0
- package/dist/mcp/sseserver/MCPServer.d.ts +4 -2
- package/dist/mcp/sseserver/MCPServer.js +12 -6
- package/dist/mcp/sseserver/with-mcp-server.js +22 -6
- package/dist/model/model-builder.js +20 -19
- package/dist/model/parser/DeploymentModelParser.js +7 -5
- package/dist/model/parser/ModelParser.js +7 -5
- package/dist/module.d.ts +2 -2
- package/dist/module.js +4 -4
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +19 -0
- package/dist/views/configurable-layouter.d.ts +2 -2
- package/dist/views/configurable-layouter.js +4 -2
- package/dist/views/likec4-views.d.ts +2 -3
- package/dist/views/likec4-views.js +29 -50
- package/dist/workspace/ProjectsManager.d.ts +0 -1
- package/dist/workspace/ProjectsManager.js +15 -4
- package/package.json +12 -12
|
@@ -2,6 +2,8 @@ import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import type { LikeC4Services } from '../module';
|
|
4
4
|
export interface LikeC4MCPServer {
|
|
5
|
+
readonly isStarted: boolean;
|
|
6
|
+
readonly port: number;
|
|
5
7
|
start(port: number): Promise<void>;
|
|
6
8
|
stop(): Promise<void>;
|
|
7
9
|
}
|
|
@@ -9,6 +11,8 @@ export interface LikeC4MCPServerFactory {
|
|
|
9
11
|
create(options?: ServerOptions): McpServer;
|
|
10
12
|
}
|
|
11
13
|
export declare class NoopLikeC4MCPServer implements LikeC4MCPServer {
|
|
14
|
+
get isStarted(): boolean;
|
|
15
|
+
get port(): number;
|
|
12
16
|
start(port: number): Promise<never>;
|
|
13
17
|
stop(): Promise<never>;
|
|
14
18
|
}
|
|
@@ -3,10 +3,12 @@ import type { LikeC4Services } from '../../module';
|
|
|
3
3
|
import type { LikeC4MCPServer } from '../LikeC4MCPServerFactory';
|
|
4
4
|
export declare class SSELikeC4MCPServer implements LikeC4MCPServer, AsyncDisposable {
|
|
5
5
|
private services;
|
|
6
|
-
private
|
|
6
|
+
private transports;
|
|
7
7
|
private server;
|
|
8
|
-
private
|
|
8
|
+
private _port;
|
|
9
9
|
constructor(services: LikeC4Services);
|
|
10
|
+
get isStarted(): boolean;
|
|
11
|
+
get port(): number;
|
|
10
12
|
dispose(): Promise<void>;
|
|
11
13
|
start(port?: number): Promise<void>;
|
|
12
14
|
stop(): Promise<void>;
|
|
@@ -9,7 +9,13 @@ export class SSELikeC4MCPServer {
|
|
|
9
9
|
// Store transports by session ID to send notifications
|
|
10
10
|
transports = {};
|
|
11
11
|
server = void 0;
|
|
12
|
-
|
|
12
|
+
_port = 33335;
|
|
13
|
+
get isStarted() {
|
|
14
|
+
return this.server?.listening === true;
|
|
15
|
+
}
|
|
16
|
+
get port() {
|
|
17
|
+
return this._port;
|
|
18
|
+
}
|
|
13
19
|
async dispose() {
|
|
14
20
|
await this.stop();
|
|
15
21
|
}
|
|
@@ -20,8 +26,8 @@ export class SSELikeC4MCPServer {
|
|
|
20
26
|
}
|
|
21
27
|
await this.stop();
|
|
22
28
|
}
|
|
23
|
-
logger.info("Starting server on port {port}", { port });
|
|
24
|
-
this.
|
|
29
|
+
logger.info("Starting MCP server on port {port}", { port });
|
|
30
|
+
this._port = port;
|
|
25
31
|
const mcp = this.services.mcp.ServerFactory.create();
|
|
26
32
|
const app = express();
|
|
27
33
|
app.get("/sse", async (_, res) => {
|
|
@@ -44,13 +50,12 @@ export class SSELikeC4MCPServer {
|
|
|
44
50
|
}
|
|
45
51
|
});
|
|
46
52
|
return new Promise((resolve, reject) => {
|
|
47
|
-
this.server = app.listen(
|
|
53
|
+
this.server = app.listen(this._port, (err) => {
|
|
48
54
|
if (err) {
|
|
49
|
-
logger.error("Failed to start server", { err });
|
|
50
55
|
reject(err);
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
53
|
-
logger.info("server listening on port {port}", { port });
|
|
58
|
+
logger.info("MCP server listening on port {port}", { port: this._port });
|
|
54
59
|
resolve();
|
|
55
60
|
});
|
|
56
61
|
});
|
|
@@ -62,6 +67,7 @@ export class SSELikeC4MCPServer {
|
|
|
62
67
|
}
|
|
63
68
|
logger.info("Stopping server");
|
|
64
69
|
this.server = void 0;
|
|
70
|
+
this.transports = {};
|
|
65
71
|
return new Promise((resolve) => {
|
|
66
72
|
server.close((err) => {
|
|
67
73
|
if (err) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { loggable } from "@likec4/log";
|
|
2
|
+
import { isError } from "remeda";
|
|
1
3
|
import { logger } from "../../logger.js";
|
|
2
4
|
import { SSELikeC4MCPServer } from "./MCPServer.js";
|
|
3
5
|
import { LikeC4MCPServerFactory } from "./MCPServerFactory.js";
|
|
@@ -14,19 +16,33 @@ export const WithMCPServer = {
|
|
|
14
16
|
return;
|
|
15
17
|
}
|
|
16
18
|
logger.debug("Configuration update: {update}", { update });
|
|
17
|
-
const {
|
|
18
|
-
enabled
|
|
19
|
-
port
|
|
20
|
-
};
|
|
19
|
+
const {
|
|
20
|
+
enabled = false,
|
|
21
|
+
port = 33335
|
|
22
|
+
} = update.configuration.mcp;
|
|
21
23
|
if (!enabled) {
|
|
22
24
|
server.stop();
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
25
27
|
Promise.resolve().then(() => server.start(port)).then(() => {
|
|
26
28
|
connection?.telemetry?.logEvent({
|
|
27
|
-
eventName: "mcp-server-started"
|
|
29
|
+
eventName: "mcp-server-started",
|
|
30
|
+
mcpPort: port
|
|
28
31
|
});
|
|
29
|
-
}).catch((err) =>
|
|
32
|
+
}).catch((err) => {
|
|
33
|
+
const message = isError(err) ? err.message : void 0;
|
|
34
|
+
connection?.telemetry?.logEvent({
|
|
35
|
+
eventName: "mcp-server-start-failed",
|
|
36
|
+
mcpPort: port,
|
|
37
|
+
...message && { message }
|
|
38
|
+
});
|
|
39
|
+
logger.error("Failed to start LikeC4 MCP Server", { err });
|
|
40
|
+
if (connection) {
|
|
41
|
+
connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server
|
|
42
|
+
|
|
43
|
+
${loggable(err)}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
30
46
|
});
|
|
31
47
|
return server;
|
|
32
48
|
},
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
DocumentState,
|
|
12
12
|
interruptAndCheck
|
|
13
13
|
} from "langium";
|
|
14
|
-
import prettyMs from "pretty-ms";
|
|
15
14
|
import {
|
|
16
15
|
filter,
|
|
17
16
|
flatMap,
|
|
@@ -26,13 +25,13 @@ import {
|
|
|
26
25
|
import { CancellationToken } from "vscode-jsonrpc";
|
|
27
26
|
import { isLikeC4Builtin } from "../likec4lib.js";
|
|
28
27
|
import { logger as mainLogger, logWarnError } from "../logger.js";
|
|
29
|
-
import { ADisposable } from "../utils/index.js";
|
|
28
|
+
import { ADisposable, performanceMark } from "../utils/index.js";
|
|
30
29
|
import { assignNavigateTo } from "../view-utils/index.js";
|
|
31
30
|
import { buildModelData } from "./builder/buildModel.js";
|
|
32
31
|
const parsedWithoutImportsCacheKey = (projectId) => `parsed-without-imports-${projectId}`;
|
|
33
32
|
const parsedModelCacheKey = (projectId) => `parsed-model-${projectId}`;
|
|
34
33
|
const computedModelCacheKey = (projectId) => `computed-model-${projectId}`;
|
|
35
|
-
const
|
|
34
|
+
const builderLogger = mainLogger.getChild("model-builder");
|
|
36
35
|
export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
37
36
|
projects;
|
|
38
37
|
parser;
|
|
@@ -65,7 +64,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
65
64
|
}
|
|
66
65
|
)
|
|
67
66
|
);
|
|
68
|
-
|
|
67
|
+
builderLogger.debug`created`;
|
|
69
68
|
}
|
|
70
69
|
/**
|
|
71
70
|
* WARNING:
|
|
@@ -76,19 +75,19 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
76
75
|
*/
|
|
77
76
|
unsafeSyncParseModelData(projectId) {
|
|
78
77
|
const cache = this.cache;
|
|
79
|
-
const
|
|
78
|
+
const logger = builderLogger.getChild(projectId);
|
|
80
79
|
const key = parsedWithoutImportsCacheKey(projectId);
|
|
81
80
|
if (cache.has(key)) {
|
|
82
|
-
|
|
81
|
+
logger.debug`unsafeSyncBuildModelData from cache`;
|
|
83
82
|
}
|
|
84
83
|
return cache.get(key, () => {
|
|
85
84
|
try {
|
|
86
85
|
const docs = this.documents(projectId);
|
|
87
86
|
if (docs.length === 0) {
|
|
88
|
-
logger.debug`no documents to build model
|
|
87
|
+
logger.debug`no documents to build model`;
|
|
89
88
|
return null;
|
|
90
89
|
}
|
|
91
|
-
|
|
90
|
+
logger.debug`unsafeSyncBuildModelData`;
|
|
92
91
|
return buildModelData(projectId, docs);
|
|
93
92
|
} catch (e) {
|
|
94
93
|
logWarnError(e);
|
|
@@ -100,6 +99,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
100
99
|
* To avoid circular dependencies, first we parse all documents and then we join them.
|
|
101
100
|
*/
|
|
102
101
|
unsafeSyncJoinedModelData(projectId) {
|
|
102
|
+
const logger = builderLogger.getChild(projectId);
|
|
103
103
|
const cache = this.cache;
|
|
104
104
|
const key = parsedModelCacheKey(projectId);
|
|
105
105
|
return cache.get(key, () => {
|
|
@@ -133,18 +133,18 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
133
133
|
}
|
|
134
134
|
async parseModel(projectId, cancelToken = CancellationToken.None) {
|
|
135
135
|
const project = this.projects.ensureProjectId(projectId);
|
|
136
|
-
const
|
|
136
|
+
const logger = builderLogger.getChild(project);
|
|
137
137
|
const cache = this.cache;
|
|
138
138
|
const cached = cache.get(parsedModelCacheKey(project));
|
|
139
139
|
if (cached) {
|
|
140
|
-
|
|
140
|
+
logger.debug`parseModel from cache`;
|
|
141
141
|
return cached;
|
|
142
142
|
}
|
|
143
|
-
const t0 =
|
|
143
|
+
const t0 = performanceMark();
|
|
144
144
|
return await this.mutex.read(async () => {
|
|
145
145
|
await interruptAndCheck(cancelToken);
|
|
146
146
|
const result = this.unsafeSyncJoinedModelData(project);
|
|
147
|
-
|
|
147
|
+
logger.debug`parseModel in ${t0.pretty}`;
|
|
148
148
|
return result;
|
|
149
149
|
});
|
|
150
150
|
}
|
|
@@ -155,6 +155,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
155
155
|
* Otherwise, the model may be incomplete.
|
|
156
156
|
*/
|
|
157
157
|
unsafeSyncBuildModel(projectId) {
|
|
158
|
+
const logger = builderLogger.getChild(projectId);
|
|
158
159
|
const cache = this.cache;
|
|
159
160
|
const viewsCache = this.cache;
|
|
160
161
|
return cache.get(computedModelCacheKey(projectId), () => {
|
|
@@ -189,24 +190,24 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
189
190
|
}
|
|
190
191
|
async buildLikeC4Model(projectId, cancelToken = CancellationToken.None) {
|
|
191
192
|
const project = this.projects.ensureProjectId(projectId);
|
|
192
|
-
const
|
|
193
|
+
const logger = builderLogger.getChild(project);
|
|
193
194
|
const cache = this.cache;
|
|
194
195
|
const cached = cache.get(computedModelCacheKey(project));
|
|
195
196
|
if (cached) {
|
|
196
|
-
|
|
197
|
+
logger.debug("buildLikeC4Model from cache");
|
|
197
198
|
return cached;
|
|
198
199
|
}
|
|
199
|
-
const t0 =
|
|
200
|
+
const t0 = performanceMark();
|
|
200
201
|
return await this.mutex.read(async () => {
|
|
201
202
|
await interruptAndCheck(cancelToken);
|
|
202
203
|
const result = this.unsafeSyncBuildModel(project);
|
|
203
|
-
|
|
204
|
+
logger.debug(`buildLikeC4Model in ${t0.pretty}`);
|
|
204
205
|
return result;
|
|
205
206
|
});
|
|
206
207
|
}
|
|
207
208
|
async computeView(viewId, projectId, cancelToken = CancellationToken.None) {
|
|
208
209
|
const project = this.projects.ensureProjectId(projectId);
|
|
209
|
-
const
|
|
210
|
+
const logger = builderLogger.getChild(project);
|
|
210
211
|
const cache = this.cache;
|
|
211
212
|
const cacheKey = computedViewKey(project, viewId);
|
|
212
213
|
if (cache.has(cacheKey)) {
|
|
@@ -219,10 +220,10 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
219
220
|
return cache.get(cacheKey, () => {
|
|
220
221
|
const view = parsed.$data.views[viewId];
|
|
221
222
|
if (!view) {
|
|
222
|
-
|
|
223
|
+
logger.warn`computeView: cant find view ${viewId}`;
|
|
223
224
|
return null;
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
+
logger.debug`computeView: ${viewId}`;
|
|
226
227
|
const result = computeView(view, parsed);
|
|
227
228
|
if (!result.isSuccess) {
|
|
228
229
|
logWarnError(result.error);
|
|
@@ -164,9 +164,11 @@ export function DeploymentModelParser(B) {
|
|
|
164
164
|
const links = this.convertLinks(astNode.body);
|
|
165
165
|
const kind = (astNode.kind ?? astNode.dotKind?.kind)?.ref?.name;
|
|
166
166
|
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
|
|
167
|
-
const bodyProps =
|
|
168
|
-
astNode.body?.props
|
|
169
|
-
(
|
|
167
|
+
const bodyProps = pipe(
|
|
168
|
+
astNode.body?.props ?? [],
|
|
169
|
+
filter(ast.isRelationStringProperty),
|
|
170
|
+
filter((p) => isTruthy(p.value)),
|
|
171
|
+
mapToObj((p) => [p.key, p.value || void 0])
|
|
170
172
|
);
|
|
171
173
|
const navigateTo = pipe(
|
|
172
174
|
astNode.body?.props ?? [],
|
|
@@ -175,8 +177,8 @@ export function DeploymentModelParser(B) {
|
|
|
175
177
|
filter(isTruthy),
|
|
176
178
|
first()
|
|
177
179
|
);
|
|
178
|
-
const title = removeIndent(astNode.title ?? bodyProps.title);
|
|
179
|
-
const description = removeIndent(bodyProps.description);
|
|
180
|
+
const title = removeIndent(astNode.title ?? bodyProps.title) ?? "";
|
|
181
|
+
const description = removeIndent(astNode.description ?? bodyProps.description);
|
|
180
182
|
const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology);
|
|
181
183
|
const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty);
|
|
182
184
|
const id = stringHash(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { invariant, isNonEmptyArray, LinkedList, nonexhaustive, nonNullable } from "@likec4/core";
|
|
2
2
|
import { FqnRef } from "@likec4/core/types";
|
|
3
3
|
import { loggable } from "@likec4/log";
|
|
4
|
-
import { filter, first, isDefined, isEmpty,
|
|
4
|
+
import { filter, first, isDefined, isEmpty, isTruthy, map, mapToObj, pipe } from "remeda";
|
|
5
5
|
import {
|
|
6
6
|
ast,
|
|
7
7
|
toRelationshipStyleExcludeDefaults
|
|
@@ -140,9 +140,11 @@ ${error}`, {
|
|
|
140
140
|
const kind = (astNode.kind ?? astNode.dotKind?.kind)?.ref?.name;
|
|
141
141
|
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
|
|
142
142
|
const astPath = this.getAstNodePath(astNode);
|
|
143
|
-
const bodyProps =
|
|
144
|
-
astNode.body?.props
|
|
145
|
-
(
|
|
143
|
+
const bodyProps = pipe(
|
|
144
|
+
astNode.body?.props ?? [],
|
|
145
|
+
filter(ast.isRelationStringProperty),
|
|
146
|
+
filter((p) => isTruthy(p.value)),
|
|
147
|
+
mapToObj((p) => [p.key, p.value || void 0])
|
|
146
148
|
);
|
|
147
149
|
const navigateTo = pipe(
|
|
148
150
|
astNode.body?.props ?? [],
|
|
@@ -152,7 +154,7 @@ ${error}`, {
|
|
|
152
154
|
first()
|
|
153
155
|
);
|
|
154
156
|
const title = removeIndent(astNode.title ?? bodyProps.title) ?? "";
|
|
155
|
-
const description = removeIndent(bodyProps.description);
|
|
157
|
+
const description = removeIndent(astNode.description ?? bodyProps.description);
|
|
156
158
|
const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology);
|
|
157
159
|
const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty);
|
|
158
160
|
const id = stringHash(
|
package/dist/module.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueueGraphvizLayoter } from '@likec4/layouts';
|
|
2
2
|
import { type Module, WorkspaceCache } from 'langium';
|
|
3
3
|
import { type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices } from 'langium/lsp';
|
|
4
4
|
import { LikeC4DocumentationProvider } from './documentation';
|
|
@@ -43,7 +43,7 @@ export interface LikeC4AddedServices {
|
|
|
43
43
|
likec4: {
|
|
44
44
|
LanguageServices: LikeC4LanguageServices;
|
|
45
45
|
Views: LikeC4Views;
|
|
46
|
-
Layouter:
|
|
46
|
+
Layouter: QueueGraphvizLayoter;
|
|
47
47
|
DeploymentsIndex: DeploymentsIndex;
|
|
48
48
|
FqnIndex: FqnIndex;
|
|
49
49
|
ModelParser: LikeC4ModelParser;
|
package/dist/module.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
|
|
2
2
|
import {
|
|
3
3
|
DocumentState,
|
|
4
4
|
EmptyFileSystem,
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
LikeC4GeneratedSharedModule
|
|
17
17
|
} from "./generated/module.js";
|
|
18
18
|
import { DefaultLikeC4LanguageServices } from "./LikeC4LanguageServices.js";
|
|
19
|
-
import { logger } from "./logger.js";
|
|
20
19
|
import {
|
|
21
20
|
LikeC4CodeLensProvider,
|
|
22
21
|
LikeC4CompletionProvider,
|
|
@@ -87,8 +86,9 @@ export const LikeC4Module = {
|
|
|
87
86
|
likec4: {
|
|
88
87
|
LanguageServices: bind(DefaultLikeC4LanguageServices),
|
|
89
88
|
Layouter: (_services) => {
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
return new QueueGraphvizLayoter({
|
|
90
|
+
graphviz: new GraphvizWasmAdapter()
|
|
91
|
+
});
|
|
92
92
|
},
|
|
93
93
|
Views: bind(DefaultLikeC4Views),
|
|
94
94
|
DeploymentsIndex: bind(DeploymentsIndex),
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -4,3 +4,8 @@ export * from './fqnRef';
|
|
|
4
4
|
export * from './projectId';
|
|
5
5
|
export * from './stringHash';
|
|
6
6
|
export declare function safeCall<T>(fn: () => T): T | undefined;
|
|
7
|
+
export declare function performanceNow(): number;
|
|
8
|
+
export declare function performanceMark(): {
|
|
9
|
+
readonly ms: number;
|
|
10
|
+
readonly pretty: string;
|
|
11
|
+
};
|
package/dist/utils/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import prettyMs from "pretty-ms";
|
|
1
2
|
import { logger } from "../logger.js";
|
|
2
3
|
export * from "./disposable.js";
|
|
3
4
|
export * from "./elementRef.js";
|
|
@@ -12,3 +13,21 @@ export function safeCall(fn) {
|
|
|
12
13
|
return void 0;
|
|
13
14
|
}
|
|
14
15
|
}
|
|
16
|
+
export function performanceNow() {
|
|
17
|
+
try {
|
|
18
|
+
return performance.now();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return Date.now();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function performanceMark() {
|
|
24
|
+
const t0 = performanceNow();
|
|
25
|
+
return {
|
|
26
|
+
get ms() {
|
|
27
|
+
return performanceNow() - t0;
|
|
28
|
+
},
|
|
29
|
+
get pretty() {
|
|
30
|
+
return prettyMs(performanceNow() - t0);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueueGraphvizLayoter } from '@likec4/layouts';
|
|
2
2
|
import type { LikeC4Services } from '../module';
|
|
3
3
|
export declare const ConfigurableLayouter: {
|
|
4
4
|
likec4: {
|
|
5
|
-
Layouter(services: LikeC4Services):
|
|
5
|
+
Layouter(services: LikeC4Services): QueueGraphvizLayoter;
|
|
6
6
|
};
|
|
7
7
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
|
|
2
2
|
import { GraphvizBinaryAdapter } from "@likec4/layouts/graphviz/binary";
|
|
3
3
|
import { isEmpty } from "remeda";
|
|
4
4
|
import which from "which";
|
|
@@ -16,7 +16,9 @@ export const ConfigurableLayouter = {
|
|
|
16
16
|
Layouter(services) {
|
|
17
17
|
logger.debug("Creating ConfigurableLayouter");
|
|
18
18
|
const wasmAdapter = new GraphvizWasmAdapter();
|
|
19
|
-
const layouter = new
|
|
19
|
+
const layouter = new QueueGraphvizLayoter({
|
|
20
|
+
graphviz: wasmAdapter
|
|
21
|
+
});
|
|
20
22
|
const langId = services.LanguageMetaData.languageId;
|
|
21
23
|
services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
|
|
22
24
|
logger.debug("Configuration update: {update}", { update });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComputedView, DiagramView, ProjectId, ViewId } from '@likec4/core';
|
|
2
|
-
import { GraphvizLayouter } from '@likec4/layouts';
|
|
2
|
+
import { type QueueGraphvizLayoter, GraphvizLayouter } from '@likec4/layouts';
|
|
3
3
|
import { CancellationToken } from 'vscode-jsonrpc';
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
5
|
export type GraphvizOut = {
|
|
@@ -24,9 +24,8 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
|
|
|
24
24
|
private cache;
|
|
25
25
|
private viewsWithReportedErrors;
|
|
26
26
|
private ModelBuilder;
|
|
27
|
-
private queue;
|
|
28
27
|
constructor(services: LikeC4Services);
|
|
29
|
-
get layouter():
|
|
28
|
+
get layouter(): QueueGraphvizLayoter;
|
|
30
29
|
computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
|
|
31
30
|
layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<Readonly<GraphvizOut>>>;
|
|
32
31
|
layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { loggable } from "@likec4/log";
|
|
2
|
-
import PQueue from "p-queue";
|
|
3
|
-
import prettyMs from "pretty-ms";
|
|
4
2
|
import { values } from "remeda";
|
|
5
3
|
import { CancellationToken } from "vscode-jsonrpc";
|
|
6
4
|
import { logError, logger as rootLogger, logWarnError } from "../logger.js";
|
|
5
|
+
import { performanceMark } from "../utils/index.js";
|
|
6
|
+
const viewsLogger = rootLogger.getChild("views");
|
|
7
7
|
export class DefaultLikeC4Views {
|
|
8
8
|
constructor(services) {
|
|
9
9
|
this.services = services;
|
|
@@ -12,7 +12,6 @@ export class DefaultLikeC4Views {
|
|
|
12
12
|
cache = /* @__PURE__ */ new WeakMap();
|
|
13
13
|
viewsWithReportedErrors = /* @__PURE__ */ new Set();
|
|
14
14
|
ModelBuilder;
|
|
15
|
-
queue = new PQueue({ concurrency: 2, timeout: 2e4, throwOnTimeout: true });
|
|
16
15
|
get layouter() {
|
|
17
16
|
return this.services.likec4.Layouter;
|
|
18
17
|
}
|
|
@@ -26,16 +25,10 @@ export class DefaultLikeC4Views {
|
|
|
26
25
|
if (views.length === 0) {
|
|
27
26
|
return [];
|
|
28
27
|
}
|
|
29
|
-
const
|
|
28
|
+
const m0 = performanceMark();
|
|
29
|
+
const logger = projectId ? viewsLogger.getChild(projectId) : viewsLogger;
|
|
30
30
|
logger.debug`layoutAll: ${views.length} views`;
|
|
31
|
-
|
|
32
|
-
logger.debug`wait for previous layouts to finish`;
|
|
33
|
-
await this.queue.onIdle();
|
|
34
|
-
}
|
|
35
|
-
if (this.layouter.port.concurrency !== this.queue.concurrency) {
|
|
36
|
-
this.queue.concurrency = this.layouter.port.concurrency;
|
|
37
|
-
logger.debug`set queue concurrency to ${this.layouter.port.concurrency}`;
|
|
38
|
-
}
|
|
31
|
+
const tasks = [];
|
|
39
32
|
const specification = likeC4Model.$data.specification;
|
|
40
33
|
const results = [];
|
|
41
34
|
for (const view of views) {
|
|
@@ -45,40 +38,36 @@ export class DefaultLikeC4Views {
|
|
|
45
38
|
results.push(cached);
|
|
46
39
|
continue;
|
|
47
40
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
41
|
+
tasks.push({
|
|
42
|
+
view,
|
|
43
|
+
specification
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (tasks.length > 0) {
|
|
47
|
+
await this.layouter.batchLayout({
|
|
48
|
+
batch: tasks,
|
|
49
|
+
onSuccess: (task, result) => {
|
|
50
|
+
this.viewsWithReportedErrors.delete(task.view.id);
|
|
51
|
+
this.cache.set(task.view, result);
|
|
52
|
+
results.push(result);
|
|
53
|
+
},
|
|
54
|
+
onError: (task, error) => {
|
|
55
|
+
logger.warn(`Fail layout view ${task.view.id}`, { error });
|
|
56
|
+
this.cache.delete(task.view);
|
|
60
57
|
}
|
|
61
|
-
this.viewsWithReportedErrors.delete(view.id);
|
|
62
|
-
logger.debug`done layout view ${view.id}`;
|
|
63
|
-
this.cache.set(view, result);
|
|
64
|
-
results.push(result);
|
|
65
|
-
}).catch((e) => {
|
|
66
|
-
logger.error(`Fail layout view ${view.id}`, { e });
|
|
67
|
-
this.cache.delete(view);
|
|
68
58
|
});
|
|
69
59
|
}
|
|
70
|
-
await this.queue.onIdle();
|
|
71
60
|
if (results.length !== views.length) {
|
|
72
|
-
logger.warn`layouted ${results.length} of ${views.length} views`;
|
|
61
|
+
logger.warn`layouted ${results.length} of ${views.length} views in ${m0.pretty}`;
|
|
73
62
|
} else if (results.length > 0) {
|
|
74
|
-
logger.debug`layouted all ${results.length} views`;
|
|
63
|
+
logger.debug`layouted all ${results.length} views in ${m0.pretty}`;
|
|
75
64
|
}
|
|
76
65
|
return results;
|
|
77
66
|
}
|
|
78
67
|
async layoutView(viewId, projectId, cancelToken = CancellationToken.None) {
|
|
79
68
|
const model = await this.ModelBuilder.buildLikeC4Model(projectId, cancelToken);
|
|
80
69
|
const view = model.findView(viewId)?.$view;
|
|
81
|
-
const logger =
|
|
70
|
+
const logger = projectId ? viewsLogger.getChild(projectId) : viewsLogger;
|
|
82
71
|
if (!view) {
|
|
83
72
|
logger.warn`layoutView ${viewId} not found`;
|
|
84
73
|
return null;
|
|
@@ -89,24 +78,14 @@ export class DefaultLikeC4Views {
|
|
|
89
78
|
return await Promise.resolve(cached);
|
|
90
79
|
}
|
|
91
80
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
const result = await this.queue.add(async () => {
|
|
98
|
-
logger.debug`layouting view ${view.id}...`;
|
|
99
|
-
return await this.layouter.layout({
|
|
100
|
-
view,
|
|
101
|
-
specification: model.$data.specification
|
|
102
|
-
});
|
|
81
|
+
const m0 = performanceMark();
|
|
82
|
+
const result = await this.layouter.layout({
|
|
83
|
+
view,
|
|
84
|
+
specification: model.$data.specification
|
|
103
85
|
});
|
|
104
|
-
if (!result) {
|
|
105
|
-
throw new Error(`Failed to layout view ${viewId}`);
|
|
106
|
-
}
|
|
107
86
|
this.viewsWithReportedErrors.delete(viewId);
|
|
108
87
|
this.cache.set(view, result);
|
|
109
|
-
logger.debug(`layout {viewId} ready in ${
|
|
88
|
+
logger.debug(`layout {viewId} ready in ${m0.pretty}`, { viewId });
|
|
110
89
|
return result;
|
|
111
90
|
} catch (e) {
|
|
112
91
|
if (!this.viewsWithReportedErrors.has(viewId)) {
|
|
@@ -50,7 +50,6 @@ export declare class ProjectsManager {
|
|
|
50
50
|
* Checks if the provided file system entry is a valid project config file.
|
|
51
51
|
*
|
|
52
52
|
* @param entry The file system entry to check
|
|
53
|
-
* @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
|
|
54
53
|
*/
|
|
55
54
|
loadConfigFile(entry: FileSystemNode): Promise<Project | undefined>;
|
|
56
55
|
registerProject(configFile: URI): Promise<Project>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BiMap, invariant, nonNullable } from "@likec4/core";
|
|
2
|
+
import { loggable } from "@likec4/log";
|
|
2
3
|
import { URI, WorkspaceCache } from "langium";
|
|
3
4
|
import picomatch from "picomatch/posix";
|
|
4
5
|
import { hasAtLeast, isNullish, map, pipe, prop, sortBy } from "remeda";
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
withoutProtocol,
|
|
10
11
|
withProtocol
|
|
11
12
|
} from "ufo";
|
|
12
|
-
import { parseConfigJson } from "../config/index.js";
|
|
13
|
+
import { parseConfigJson, validateConfig } from "../config/index.js";
|
|
13
14
|
import { logger as mainLogger } from "../logger.js";
|
|
14
15
|
const logger = mainLogger.getChild("ProjectsManager");
|
|
15
16
|
export class ProjectsManager {
|
|
@@ -115,14 +116,23 @@ export class ProjectsManager {
|
|
|
115
116
|
* Checks if the provided file system entry is a valid project config file.
|
|
116
117
|
*
|
|
117
118
|
* @param entry The file system entry to check
|
|
118
|
-
* @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
|
|
119
119
|
*/
|
|
120
120
|
async loadConfigFile(entry) {
|
|
121
121
|
if (entry.isDirectory) {
|
|
122
122
|
return void 0;
|
|
123
123
|
}
|
|
124
124
|
if (this.isConfigFile(entry)) {
|
|
125
|
-
|
|
125
|
+
try {
|
|
126
|
+
return await this.registerProject(entry.uri);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this.services.lsp.Connection?.window.showErrorMessage(
|
|
129
|
+
`LikeC4: Failed to register project at ${entry.uri.toString()}
|
|
130
|
+
|
|
131
|
+
${loggable(error)}`
|
|
132
|
+
);
|
|
133
|
+
logger.error("Failed to register project at {uri}", { uri: entry.uri.toString(), error });
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
126
136
|
}
|
|
127
137
|
return void 0;
|
|
128
138
|
}
|
|
@@ -135,7 +145,8 @@ export class ProjectsManager {
|
|
|
135
145
|
const folderUri2 = configFile.with({ path });
|
|
136
146
|
return this.registerProject({ config: config2, folderUri: folderUri2 });
|
|
137
147
|
}
|
|
138
|
-
const
|
|
148
|
+
const config = validateConfig(opts.config);
|
|
149
|
+
const { folderUri } = opts;
|
|
139
150
|
let id = config.name;
|
|
140
151
|
let i = 1;
|
|
141
152
|
while (this.projectIdToFolder.has(id)) {
|