@likec4/language-server 1.43.0 → 1.45.0
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/browser/package.json +1 -1
- package/browser-worker/package.json +1 -1
- package/dist/LikeC4LanguageServices.d.ts +13 -34
- package/dist/LikeC4LanguageServices.js +152 -0
- package/dist/Rpc.js +257 -0
- package/dist/ast.d.ts +5 -1
- package/dist/ast.js +257 -0
- package/dist/browser-worker.js +4 -0
- package/dist/browser.js +35 -0
- package/dist/bundled.js +44 -0
- package/dist/bundled.mjs +3568 -3900
- package/dist/documentation/documentation-provider.js +51 -0
- package/dist/documentation/index.js +1 -0
- package/dist/empty.js +2 -0
- package/dist/filesystem/ChokidarWatcher.d.ts +2 -0
- package/dist/filesystem/ChokidarWatcher.js +108 -0
- package/dist/filesystem/FileSystemWatcher.js +14 -0
- package/dist/filesystem/LikeC4FileSystem.d.ts +1 -2
- package/dist/filesystem/LikeC4FileSystem.js +126 -0
- package/dist/filesystem/index.d.ts +26 -0
- package/dist/filesystem/index.js +29 -0
- package/dist/formatting/LikeC4Formatter.js +637 -0
- package/dist/formatting/utils.js +18 -0
- package/dist/generated/ast.js +2155 -0
- package/dist/generated/{grammar.mjs → grammar.js} +6 -2
- package/dist/generated/module.d.ts +6 -1
- package/dist/generated/module.js +27 -0
- package/dist/generated-lib/{icons.mjs → icons.js} +11 -7
- package/dist/index.d.ts +10 -1
- package/dist/index.js +55 -0
- package/dist/{likec4lib.mjs → likec4lib.js} +3 -3
- package/dist/logger.js +81 -0
- package/dist/lsp/CodeActionProvider.d.ts +14 -0
- package/dist/lsp/CodeActionProvider.js +33 -0
- package/dist/lsp/CodeLensProvider.js +44 -0
- package/dist/lsp/CompletionProvider.d.ts +3 -1
- package/dist/lsp/CompletionProvider.js +200 -0
- package/dist/lsp/DocumentHighlightProvider.js +10 -0
- package/dist/lsp/DocumentLinkProvider.js +58 -0
- package/dist/lsp/DocumentSymbolProvider.js +306 -0
- package/dist/lsp/HoverProvider.js +106 -0
- package/dist/lsp/RenameProvider.js +6 -0
- package/dist/lsp/SemanticTokenProvider.js +257 -0
- package/dist/lsp/index.d.ts +1 -0
- package/dist/lsp/index.js +9 -0
- package/dist/mcp/MCPServerFactory.js +73 -0
- package/dist/mcp/NoopLikeC4MCPServer.js +17 -0
- package/dist/mcp/interfaces.js +5 -0
- package/dist/mcp/server/StdioLikeC4MCPServer.js +51 -0
- package/dist/mcp/server/StreamableLikeC4MCPServer.js +145 -0
- package/dist/mcp/server/WithMCPServer.js +56 -0
- package/dist/mcp/tools/_common.d.ts +8 -7
- package/dist/mcp/tools/_common.js +49 -0
- package/dist/mcp/tools/find-relationships.d.ts +7 -8
- package/dist/mcp/tools/find-relationships.js +150 -0
- package/dist/mcp/tools/list-projects.d.ts +3 -3
- package/dist/mcp/tools/list-projects.js +62 -0
- package/dist/mcp/tools/open-view.d.ts +6 -7
- package/dist/mcp/tools/open-view.js +52 -0
- package/dist/mcp/tools/read-deployment.d.ts +6 -7
- package/dist/mcp/tools/read-deployment.js +132 -0
- package/dist/mcp/tools/read-element.d.ts +6 -7
- package/dist/mcp/tools/read-element.js +194 -0
- package/dist/mcp/tools/read-project-summary.d.ts +5 -6
- package/dist/mcp/tools/read-project-summary.js +176 -0
- package/dist/mcp/tools/read-view.d.ts +6 -7
- package/dist/mcp/tools/read-view.js +203 -0
- package/dist/mcp/tools/search-element.d.ts +3 -3
- package/dist/mcp/tools/search-element.js +177 -0
- package/dist/mcp/utils.d.ts +2 -2
- package/dist/mcp/utils.js +48 -0
- package/dist/model/builder/MergedExtends.js +74 -0
- package/dist/model/builder/MergedSpecification.js +175 -0
- package/dist/model/builder/buildModel.js +176 -0
- package/dist/model/deployments-index.js +102 -0
- package/dist/model/fqn-index.d.ts +1 -2
- package/dist/model/fqn-index.js +247 -0
- package/dist/model/index.js +6 -0
- package/dist/model/model-builder.d.ts +13 -11
- package/dist/model/model-builder.js +232 -0
- package/dist/model/model-locator.d.ts +6 -5
- package/dist/model/model-locator.js +240 -0
- package/dist/model/model-parser-where.js +81 -0
- package/dist/model/model-parser.d.ts +309 -304
- package/dist/model/model-parser.js +126 -0
- package/dist/model/parser/Base.d.ts +2 -2
- package/dist/model/parser/Base.js +367 -0
- package/dist/model/parser/DeploymentModelParser.d.ts +2 -2
- package/dist/model/parser/DeploymentModelParser.js +176 -0
- package/dist/model/parser/DeploymentViewParser.d.ts +3 -3
- package/dist/model/parser/DeploymentViewParser.js +86 -0
- package/dist/model/parser/FqnRefParser.d.ts +2 -2
- package/dist/model/parser/FqnRefParser.js +382 -0
- package/dist/model/parser/GlobalsParser.d.ts +6 -6
- package/dist/model/parser/GlobalsParser.js +84 -0
- package/dist/model/parser/ImportsParser.d.ts +11 -12
- package/dist/model/parser/ImportsParser.js +24 -0
- package/dist/model/parser/ModelParser.d.ts +2 -2
- package/dist/model/parser/ModelParser.js +165 -0
- package/dist/model/parser/PredicatesParser.d.ts +2 -2
- package/dist/model/parser/PredicatesParser.js +45 -0
- package/dist/model/parser/SpecificationParser.d.ts +2 -2
- package/dist/model/parser/SpecificationParser.js +113 -0
- package/dist/model/parser/ValueConverter.js +12 -0
- package/dist/model/parser/ViewsParser.d.ts +3 -3
- package/dist/model/parser/ViewsParser.js +479 -0
- package/dist/model-change/ModelChanges.d.ts +8 -5
- package/dist/model-change/ModelChanges.js +129 -0
- package/dist/model-change/changeElementStyle.js +134 -0
- package/dist/model-change/changeViewLayout.d.ts +2 -2
- package/dist/model-change/changeViewLayout.js +28 -0
- package/dist/model-change/removeManualLayoutV1.d.ts +7 -0
- package/dist/model-change/removeManualLayoutV1.js +27 -0
- package/dist/module.d.ts +10 -5
- package/dist/module.js +143 -0
- package/dist/protocol.d.ts +34 -27
- package/dist/protocol.js +123 -0
- package/dist/references/index.js +3 -0
- package/dist/references/name-provider.js +37 -0
- package/dist/references/scope-computation.js +288 -0
- package/dist/references/scope-provider.d.ts +3 -3
- package/dist/references/scope-provider.js +242 -0
- package/dist/shared/NodeKindProvider.js +57 -0
- package/dist/shared/{WorkspaceSymbolProvider.mjs → WorkspaceSymbolProvider.js} +1 -1
- package/dist/shared/index.js +2 -0
- package/dist/test/index.js +1 -0
- package/dist/test/testServices.d.ts +16 -16
- package/dist/test/testServices.js +210 -0
- package/dist/utils/disposable.js +26 -0
- package/dist/utils/elementRef.d.ts +1 -1
- package/dist/utils/elementRef.js +27 -0
- package/dist/utils/fqnRef.js +63 -0
- package/dist/utils/index.js +35 -0
- package/dist/utils/printDocs.js +1 -0
- package/dist/utils/projectId.js +16 -0
- package/dist/utils/stringHash.js +5 -0
- package/dist/validation/DocumentValidator.js +17 -0
- package/dist/validation/_shared.js +26 -0
- package/dist/validation/deployment-checks.js +140 -0
- package/dist/validation/dynamic-view.js +67 -0
- package/dist/validation/element-ref.js +12 -0
- package/dist/validation/element.js +49 -0
- package/dist/validation/imports.js +46 -0
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +157 -0
- package/dist/validation/property-checks.js +108 -0
- package/dist/validation/relation.js +55 -0
- package/dist/validation/specification.js +190 -0
- package/dist/validation/view-predicates/fqn-expr-with.js +43 -0
- package/dist/validation/view-predicates/fqn-ref-expr.js +51 -0
- package/dist/validation/view-predicates/incoming.js +16 -0
- package/dist/validation/view-predicates/index.js +6 -0
- package/dist/validation/view-predicates/outgoing.js +20 -0
- package/dist/validation/view-predicates/relation-expr.js +46 -0
- package/dist/validation/view-predicates/relation-with.js +16 -0
- package/dist/validation/view.d.ts +1 -1
- package/dist/validation/view.js +42 -0
- package/dist/view-utils/assignNavigateTo.js +27 -0
- package/dist/view-utils/index.d.ts +1 -0
- package/dist/view-utils/index.js +2 -0
- package/dist/view-utils/manual-layout.d.ts +6 -0
- package/dist/view-utils/manual-layout.js +149 -0
- package/dist/views/ConfigurableLayouter.js +51 -0
- package/dist/views/LikeC4ManualLayouts.d.ts +42 -0
- package/dist/views/LikeC4ManualLayouts.js +209 -0
- package/dist/views/{likec4-views.d.ts → LikeC4Views.d.ts} +32 -10
- package/dist/views/LikeC4Views.js +216 -0
- package/dist/views/index.d.ts +4 -1
- package/dist/views/index.js +11 -0
- package/dist/workspace/AstNodeDescriptionProvider.js +18 -0
- package/dist/workspace/IndexManager.js +21 -0
- package/dist/workspace/LangiumDocuments.d.ts +4 -3
- package/dist/workspace/LangiumDocuments.js +72 -0
- package/dist/workspace/ProjectsManager.d.ts +25 -16
- package/dist/workspace/ProjectsManager.js +469 -0
- package/dist/workspace/WorkspaceManager.d.ts +3 -2
- package/dist/workspace/WorkspaceManager.js +98 -0
- package/dist/workspace/index.js +5 -0
- package/likec4lib/package.json +1 -1
- package/package.json +30 -28
- package/protocol/package.json +1 -1
- package/dist/LikeC4LanguageServices.mjs +0 -197
- package/dist/Rpc.mjs +0 -296
- package/dist/ast.mjs +0 -221
- package/dist/browser-worker.mjs +0 -2
- package/dist/browser.mjs +0 -32
- package/dist/documentation/documentation-provider.mjs +0 -48
- package/dist/documentation/index.mjs +0 -1
- package/dist/empty.mjs +0 -1
- package/dist/filesystem/ChokidarWatcher.mjs +0 -68
- package/dist/filesystem/FileSystemWatcher.mjs +0 -11
- package/dist/filesystem/LikeC4FileSystem.mjs +0 -64
- package/dist/filesystem/index.mjs +0 -19
- package/dist/formatting/LikeC4Formatter.mjs +0 -511
- package/dist/formatting/utils.mjs +0 -15
- package/dist/generated/ast.mjs +0 -2150
- package/dist/generated/module.mjs +0 -23
- package/dist/index.mjs +0 -50
- package/dist/logger.mjs +0 -82
- package/dist/lsp/CodeLensProvider.mjs +0 -42
- package/dist/lsp/CompletionProvider.mjs +0 -208
- package/dist/lsp/DocumentHighlightProvider.mjs +0 -10
- package/dist/lsp/DocumentLinkProvider.mjs +0 -53
- package/dist/lsp/DocumentSymbolProvider.mjs +0 -287
- package/dist/lsp/HoverProvider.mjs +0 -104
- package/dist/lsp/RenameProvider.mjs +0 -6
- package/dist/lsp/SemanticTokenProvider.mjs +0 -276
- package/dist/lsp/index.mjs +0 -7
- package/dist/mcp/MCPServerFactory.mjs +0 -70
- package/dist/mcp/NoopLikeC4MCPServer.mjs +0 -17
- package/dist/mcp/interfaces.mjs +0 -4
- package/dist/mcp/server/StdioLikeC4MCPServer.mjs +0 -46
- package/dist/mcp/server/StreamableLikeC4MCPServer.mjs +0 -153
- package/dist/mcp/server/WithMCPServer.mjs +0 -58
- package/dist/mcp/tools/_common.mjs +0 -42
- package/dist/mcp/tools/find-relationships.mjs +0 -151
- package/dist/mcp/tools/list-projects.mjs +0 -62
- package/dist/mcp/tools/open-view.mjs +0 -52
- package/dist/mcp/tools/read-deployment.mjs +0 -130
- package/dist/mcp/tools/read-element.mjs +0 -198
- package/dist/mcp/tools/read-project-summary.mjs +0 -178
- package/dist/mcp/tools/read-view.mjs +0 -205
- package/dist/mcp/tools/search-element.mjs +0 -171
- package/dist/mcp/utils.mjs +0 -47
- package/dist/model/builder/MergedExtends.mjs +0 -76
- package/dist/model/builder/MergedSpecification.mjs +0 -205
- package/dist/model/builder/assignTagColors.d.ts +0 -7
- package/dist/model/builder/assignTagColors.mjs +0 -51
- package/dist/model/builder/buildModel.mjs +0 -226
- package/dist/model/deployments-index.mjs +0 -100
- package/dist/model/fqn-index.mjs +0 -243
- package/dist/model/index.mjs +0 -6
- package/dist/model/model-builder.mjs +0 -285
- package/dist/model/model-locator.mjs +0 -239
- package/dist/model/model-parser-where.mjs +0 -81
- package/dist/model/model-parser.mjs +0 -127
- package/dist/model/parser/Base.mjs +0 -376
- package/dist/model/parser/DeploymentModelParser.mjs +0 -212
- package/dist/model/parser/DeploymentViewParser.mjs +0 -95
- package/dist/model/parser/FqnRefParser.mjs +0 -398
- package/dist/model/parser/GlobalsParser.mjs +0 -82
- package/dist/model/parser/ImportsParser.mjs +0 -28
- package/dist/model/parser/ModelParser.mjs +0 -190
- package/dist/model/parser/PredicatesParser.mjs +0 -45
- package/dist/model/parser/SpecificationParser.mjs +0 -120
- package/dist/model/parser/ValueConverter.mjs +0 -12
- package/dist/model/parser/ViewsParser.mjs +0 -490
- package/dist/model-change/ModelChanges.mjs +0 -89
- package/dist/model-change/changeElementStyle.mjs +0 -143
- package/dist/model-change/changeViewLayout.mjs +0 -32
- package/dist/model-change/saveManualLayout.d.ts +0 -11
- package/dist/model-change/saveManualLayout.mjs +0 -27
- package/dist/module.mjs +0 -180
- package/dist/protocol.mjs +0 -65
- package/dist/references/index.mjs +0 -3
- package/dist/references/name-provider.mjs +0 -39
- package/dist/references/scope-computation.mjs +0 -312
- package/dist/references/scope-provider.mjs +0 -239
- package/dist/shared/NodeKindProvider.mjs +0 -110
- package/dist/shared/index.mjs +0 -2
- package/dist/test/index.mjs +0 -1
- package/dist/test/testServices.mjs +0 -200
- package/dist/utils/disposable.mjs +0 -25
- package/dist/utils/elementRef.mjs +0 -20
- package/dist/utils/fqnRef.mjs +0 -57
- package/dist/utils/index.mjs +0 -33
- package/dist/utils/printDocs.mjs +0 -1
- package/dist/utils/projectId.mjs +0 -16
- package/dist/utils/stringHash.mjs +0 -5
- package/dist/validation/DocumentValidator.mjs +0 -16
- package/dist/validation/_shared.mjs +0 -25
- package/dist/validation/deployment-checks.mjs +0 -146
- package/dist/validation/dynamic-view.mjs +0 -67
- package/dist/validation/element-ref.mjs +0 -12
- package/dist/validation/element.mjs +0 -50
- package/dist/validation/imports.mjs +0 -25
- package/dist/validation/index.mjs +0 -180
- package/dist/validation/property-checks.mjs +0 -107
- package/dist/validation/relation.mjs +0 -53
- package/dist/validation/specification.mjs +0 -173
- package/dist/validation/view-predicates/fqn-expr-with.mjs +0 -43
- package/dist/validation/view-predicates/fqn-ref-expr.mjs +0 -53
- package/dist/validation/view-predicates/incoming.mjs +0 -16
- package/dist/validation/view-predicates/index.mjs +0 -6
- package/dist/validation/view-predicates/outgoing.mjs +0 -20
- package/dist/validation/view-predicates/relation-expr.mjs +0 -39
- package/dist/validation/view-predicates/relation-with.mjs +0 -16
- package/dist/validation/view.mjs +0 -25
- package/dist/view-utils/assignNavigateTo.mjs +0 -25
- package/dist/view-utils/index.mjs +0 -1
- package/dist/view-utils/manual-layout.mjs +0 -99
- package/dist/views/configurable-layouter.mjs +0 -51
- package/dist/views/index.mjs +0 -1
- package/dist/views/likec4-views.mjs +0 -166
- package/dist/workspace/AstNodeDescriptionProvider.mjs +0 -17
- package/dist/workspace/IndexManager.mjs +0 -17
- package/dist/workspace/LangiumDocuments.mjs +0 -53
- package/dist/workspace/ProjectsManager.mjs +0 -360
- package/dist/workspace/WorkspaceManager.mjs +0 -83
- package/dist/workspace/index.mjs +0 -5
- /package/dist/views/{configurable-layouter.d.ts → ConfigurableLayouter.d.ts} +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GraphvizWasmAdapter, QueueGraphvizLayoter } 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';
|
|
6
|
+
function graphvizBinPath() {
|
|
7
|
+
try {
|
|
8
|
+
return which.sync('dot');
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
logger.error('Error checking for native Graphviz:', { error });
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export const ConfigurableLayouter = {
|
|
16
|
+
likec4: {
|
|
17
|
+
Layouter(services) {
|
|
18
|
+
logger.debug('Creating ConfigurableLayouter');
|
|
19
|
+
const layouter = new QueueGraphvizLayoter();
|
|
20
|
+
services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
|
|
21
|
+
logger.debug('Configuration update: {update}', { update });
|
|
22
|
+
if (update.section !== services.LanguageMetaData.languageId) {
|
|
23
|
+
logger.debug(`Ignoring configuration update as it is not for ${services.LanguageMetaData.languageId}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const { mode, path } = update.configuration.graphviz ?? {
|
|
28
|
+
mode: 'wasm',
|
|
29
|
+
path: '',
|
|
30
|
+
};
|
|
31
|
+
if (mode !== 'wasm') {
|
|
32
|
+
let binaryPath = isEmpty(path) ? graphvizBinPath() : path;
|
|
33
|
+
if (!isEmpty(binaryPath)) {
|
|
34
|
+
layouter.changePort(new GraphvizBinaryAdapter(binaryPath));
|
|
35
|
+
logger.info `use graphviz binary: ${binaryPath}`;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
logger.warn(`No Graphviz binaries found on PATH, use graphviz wasm`);
|
|
39
|
+
services.shared.lsp.Connection?.window.showWarningMessage('No Graphviz binaries found on PATH, set path to binaries in settings.');
|
|
40
|
+
}
|
|
41
|
+
layouter.changePort(new GraphvizWasmAdapter());
|
|
42
|
+
logger.info('use graphviz wasm');
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.error('Failed to update configuration', { error });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return layouter;
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { LayoutedView, ProjectId, ViewId } from '@likec4/core';
|
|
2
|
+
import { URI, WorkspaceCache } from 'langium';
|
|
3
|
+
import { type Location } from 'vscode-languageserver-types';
|
|
4
|
+
import type { LikeC4Services } from '../module';
|
|
5
|
+
import type { Project } from '../workspace/ProjectsManager';
|
|
6
|
+
/**
|
|
7
|
+
* @todo sync with vscode extension watchers
|
|
8
|
+
* (search for ".likec4.snap" references)
|
|
9
|
+
*/
|
|
10
|
+
export declare const isManualLayoutFile: (path: string) => boolean;
|
|
11
|
+
export interface LikeC4ManualLayouts {
|
|
12
|
+
read(project: Project): Promise<Record<ViewId, LayoutedView> | null>;
|
|
13
|
+
write(project: Project, layouted: LayoutedView): Promise<Location>;
|
|
14
|
+
remove(project: Project, view: ViewId): Promise<Location | null>;
|
|
15
|
+
clearCaches(): void;
|
|
16
|
+
}
|
|
17
|
+
export interface LikeC4ManualLayoutsModuleContext {
|
|
18
|
+
manualLayouts: (services: LikeC4Services) => LikeC4ManualLayouts;
|
|
19
|
+
}
|
|
20
|
+
export declare const WithLikeC4ManualLayouts: LikeC4ManualLayoutsModuleContext;
|
|
21
|
+
export declare class DefaultLikeC4ManualLayouts implements LikeC4ManualLayouts {
|
|
22
|
+
private services;
|
|
23
|
+
protected cache: WorkspaceCache<ProjectId, Promise<Record<ViewId, LayoutedView> | null>>;
|
|
24
|
+
constructor(services: LikeC4Services);
|
|
25
|
+
read(project: Project): Promise<Record<ViewId, LayoutedView> | null>;
|
|
26
|
+
write(project: Project, layouted: LayoutedView): Promise<Location>;
|
|
27
|
+
remove(project: Project, view: ViewId): Promise<Location | null>;
|
|
28
|
+
clearCaches(): void;
|
|
29
|
+
/**
|
|
30
|
+
* When we save snapshot - it may contain fullpath to icons on the machine it was created,
|
|
31
|
+
* that is wrong when opened on another.
|
|
32
|
+
*
|
|
33
|
+
* Prepares a snapshot for writing by converting absolute icon paths to relative paths.
|
|
34
|
+
* Absolute paths starting with 'file://' are converted to relative paths prefixed with 'file://./'
|
|
35
|
+
*/
|
|
36
|
+
protected normalizeIconPathsForWrite(layouted: LayoutedView, projectUri: URI): LayoutedView;
|
|
37
|
+
/**
|
|
38
|
+
* Postprocesses a snapshot after reading by converting relative icon paths back to absolute paths.
|
|
39
|
+
* Relative paths prefixed with 'file://./' are converted to absolute paths based on project folder.
|
|
40
|
+
*/
|
|
41
|
+
protected resolveIconPathsAfterRead(layouted: LayoutedView, projectUri: URI): LayoutedView;
|
|
42
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import JSON5 from 'json5';
|
|
2
|
+
import { DocumentState, URI, UriUtils, WorkspaceCache } from 'langium';
|
|
3
|
+
import { indexBy, prop } from 'remeda';
|
|
4
|
+
import { Position, Range, } from 'vscode-languageserver-types';
|
|
5
|
+
import { logger as rootLogger } from '../logger';
|
|
6
|
+
const layoutsLogger = rootLogger.getChild('manual-layouts');
|
|
7
|
+
/**
|
|
8
|
+
* @todo sync with vscode extension watchers
|
|
9
|
+
* (search for ".likec4.snap" references)
|
|
10
|
+
*/
|
|
11
|
+
export const isManualLayoutFile = (path) => path.endsWith('.likec4.snap');
|
|
12
|
+
function fileName(view) {
|
|
13
|
+
return `${view}.likec4.snap`;
|
|
14
|
+
}
|
|
15
|
+
function getManualLayoutsOutDir(project) {
|
|
16
|
+
return UriUtils.resolvePath(project.folderUri, project.config.manualLayouts?.outDir ?? '.likec4');
|
|
17
|
+
}
|
|
18
|
+
export const WithLikeC4ManualLayouts = {
|
|
19
|
+
manualLayouts: (services) => new DefaultLikeC4ManualLayouts(services),
|
|
20
|
+
};
|
|
21
|
+
const RELATIVE_PATH_PREFIX = 'file://./';
|
|
22
|
+
export class DefaultLikeC4ManualLayouts {
|
|
23
|
+
services;
|
|
24
|
+
cache;
|
|
25
|
+
constructor(services) {
|
|
26
|
+
this.services = services;
|
|
27
|
+
this.cache = new WorkspaceCache(services.shared, DocumentState.Validated);
|
|
28
|
+
}
|
|
29
|
+
async read(project) {
|
|
30
|
+
return await this.cache.get(project.id, async () => {
|
|
31
|
+
const logger = layoutsLogger.getChild(project.id);
|
|
32
|
+
const fs = this.services.shared.workspace.FileSystemProvider;
|
|
33
|
+
const outDir = getManualLayoutsOutDir(project);
|
|
34
|
+
const manualLayouts = [];
|
|
35
|
+
try {
|
|
36
|
+
const files = await fs.scanDirectory(outDir, isManualLayoutFile);
|
|
37
|
+
if (files.length === 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
if (file.isFile) {
|
|
42
|
+
try {
|
|
43
|
+
const content = await fs.readFile(file.uri);
|
|
44
|
+
const parsed = JSON5.parse(content);
|
|
45
|
+
const resolved = this.resolveIconPathsAfterRead(parsed, project.folderUri);
|
|
46
|
+
manualLayouts.push({
|
|
47
|
+
...resolved,
|
|
48
|
+
_layout: 'manual',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.warn(`Failed to read view snapshot ${file.uri.fsPath}`, { err });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (manualLayouts.length) {
|
|
57
|
+
logger.debug `read manual layouts for ${project.id}, found ${manualLayouts.length}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
logger.warn(`Failed to read manual layouts for ${project.folderUri.fsPath}`, { err });
|
|
62
|
+
}
|
|
63
|
+
if (manualLayouts.length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return indexBy(manualLayouts, prop('id'));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async write(project, layouted) {
|
|
70
|
+
const logger = layoutsLogger.getChild(project.id);
|
|
71
|
+
const outDir = getManualLayoutsOutDir(project);
|
|
72
|
+
const file = UriUtils.joinPath(outDir, fileName(layouted.id));
|
|
73
|
+
// Ensure the manualLayout field is omitted (may exist in migration)
|
|
74
|
+
if ('manualLayout' in layouted) {
|
|
75
|
+
const { manualLayout: _, ...rest } = layouted;
|
|
76
|
+
layouted = rest;
|
|
77
|
+
}
|
|
78
|
+
// Normalize icon paths before writing
|
|
79
|
+
layouted = this.normalizeIconPathsForWrite(layouted, project.folderUri);
|
|
80
|
+
const content = JSON5.stringify(layouted, {
|
|
81
|
+
space: 2,
|
|
82
|
+
quote: '\'',
|
|
83
|
+
});
|
|
84
|
+
const location = {
|
|
85
|
+
uri: file.toString(),
|
|
86
|
+
range: Range.create(Position.create(0, 0), Position.create(content.split('\n').length - 1, 1)),
|
|
87
|
+
};
|
|
88
|
+
logger.debug `write snapshot of ${layouted.id} in project ${project.id} to ${file.fsPath}`;
|
|
89
|
+
const fs = this.services.shared.workspace.FileSystemProvider;
|
|
90
|
+
try {
|
|
91
|
+
await fs.writeFile(file, content + '\n');
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
logger.warn(`Failed to write snapshot ${layouted.id} to ${file.fsPath}`, { err });
|
|
95
|
+
}
|
|
96
|
+
const projectCachesPromise = this.cache.get(project.id);
|
|
97
|
+
if (projectCachesPromise) {
|
|
98
|
+
const projectCaches = await projectCachesPromise;
|
|
99
|
+
if (projectCaches) {
|
|
100
|
+
logger.debug `update snapshot cache of ${layouted.id} in project ${project.id}`;
|
|
101
|
+
projectCaches[layouted.id] = layouted;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
logger.debug `clean cache of project ${project.id}`;
|
|
105
|
+
// Cache was null, remove it entirely
|
|
106
|
+
this.cache.delete(project.id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.services.likec4.ModelBuilder.clearCache();
|
|
110
|
+
return location;
|
|
111
|
+
}
|
|
112
|
+
async remove(project, view) {
|
|
113
|
+
const logger = layoutsLogger.getChild(project.id);
|
|
114
|
+
const outDir = getManualLayoutsOutDir(project);
|
|
115
|
+
const file = UriUtils.joinPath(outDir, fileName(view));
|
|
116
|
+
logger.debug `delete snapshot of ${view} in project ${project.id}. File: ${file.fsPath}`;
|
|
117
|
+
const location = {
|
|
118
|
+
uri: file.toString(),
|
|
119
|
+
range: Range.create(0, 0, 0, 0),
|
|
120
|
+
};
|
|
121
|
+
try {
|
|
122
|
+
const fs = this.services.shared.workspace.FileSystemProvider;
|
|
123
|
+
if (!(await fs.deleteFile(file))) {
|
|
124
|
+
logger.warn `Snapshot ${view} did not exist at ${file.fsPath}`;
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
logger.warn(`Failed to delete snapshot ${view} from ${file.fsPath}`, { err });
|
|
130
|
+
}
|
|
131
|
+
const projectCachesPromise = this.cache.get(project.id);
|
|
132
|
+
if (projectCachesPromise) {
|
|
133
|
+
const projectCaches = await projectCachesPromise;
|
|
134
|
+
if (projectCaches) {
|
|
135
|
+
logger.debug `clean cached view ${view} in project ${project.id}`;
|
|
136
|
+
delete projectCaches[view];
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
logger.debug `reset empty cache of project ${project.id}`;
|
|
140
|
+
// Cache was null, remove it entirely
|
|
141
|
+
this.cache.delete(project.id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.services.likec4.ModelBuilder.clearCache();
|
|
145
|
+
return location;
|
|
146
|
+
}
|
|
147
|
+
clearCaches() {
|
|
148
|
+
this.cache.clear();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* When we save snapshot - it may contain fullpath to icons on the machine it was created,
|
|
152
|
+
* that is wrong when opened on another.
|
|
153
|
+
*
|
|
154
|
+
* Prepares a snapshot for writing by converting absolute icon paths to relative paths.
|
|
155
|
+
* Absolute paths starting with 'file://' are converted to relative paths prefixed with 'file://./'
|
|
156
|
+
*/
|
|
157
|
+
normalizeIconPathsForWrite(layouted, projectUri) {
|
|
158
|
+
const nodes = layouted.nodes.map((node) => {
|
|
159
|
+
if (!node.icon || typeof node.icon !== 'string') {
|
|
160
|
+
return node;
|
|
161
|
+
}
|
|
162
|
+
// Check if icon is an absolute file path
|
|
163
|
+
if (node.icon.startsWith('file://')) {
|
|
164
|
+
const iconUri = URI.parse(node.icon);
|
|
165
|
+
// Get relative path from project folder to icon
|
|
166
|
+
const relativePath = UriUtils.relative(projectUri, iconUri);
|
|
167
|
+
// If icon is outside of project folder - leave it as is,
|
|
168
|
+
// to avoid security issues on reading snapshots on another machine
|
|
169
|
+
if (relativePath.startsWith('..')) {
|
|
170
|
+
return node;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
...node,
|
|
174
|
+
icon: `${RELATIVE_PATH_PREFIX}${relativePath}`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return node;
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
...layouted,
|
|
181
|
+
nodes: nodes,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Postprocesses a snapshot after reading by converting relative icon paths back to absolute paths.
|
|
186
|
+
* Relative paths prefixed with 'file://./' are converted to absolute paths based on project folder.
|
|
187
|
+
*/
|
|
188
|
+
resolveIconPathsAfterRead(layouted, projectUri) {
|
|
189
|
+
const nodes = layouted.nodes.map((node) => {
|
|
190
|
+
if (!node.icon || typeof node.icon !== 'string') {
|
|
191
|
+
return node;
|
|
192
|
+
}
|
|
193
|
+
// Check if icon is a relative file path
|
|
194
|
+
if (node.icon.startsWith(RELATIVE_PATH_PREFIX)) {
|
|
195
|
+
const relativePath = node.icon.substring(RELATIVE_PATH_PREFIX.length);
|
|
196
|
+
const absoluteUri = UriUtils.joinPath(projectUri, relativePath);
|
|
197
|
+
return {
|
|
198
|
+
...node,
|
|
199
|
+
icon: absoluteUri.toString(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return node;
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
...layouted,
|
|
206
|
+
nodes: nodes,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import type { ComputedView, DiagramView, ProjectId, ViewId } from '@likec4/core';
|
|
1
|
+
import type { ComputedView, DiagramView, LayoutedView, LayoutType, ProjectId, ViewId } from '@likec4/core';
|
|
2
2
|
import { type QueueGraphvizLayoter, GraphvizLayouter } from '@likec4/layouts';
|
|
3
3
|
import type { CancellationToken } from 'vscode-languageserver';
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
5
|
export type GraphvizOut = {
|
|
6
6
|
readonly dot: string;
|
|
7
|
-
readonly diagram:
|
|
7
|
+
readonly diagram: LayoutedView;
|
|
8
8
|
};
|
|
9
9
|
type GraphvizSvgOut = {
|
|
10
10
|
readonly id: ViewId;
|
|
11
11
|
readonly dot: string;
|
|
12
12
|
readonly svg: string;
|
|
13
13
|
};
|
|
14
|
+
type LayoutViewParams = {
|
|
15
|
+
viewId: ViewId;
|
|
16
|
+
/**
|
|
17
|
+
* Type of layout to apply
|
|
18
|
+
* - 'manual' - applies manual layout if any
|
|
19
|
+
* - 'auto' - returns latest version with drifts from manual layout if any
|
|
20
|
+
* - undefined - returns latest layout as is
|
|
21
|
+
*/
|
|
22
|
+
layoutType?: LayoutType | undefined;
|
|
23
|
+
projectId?: ProjectId | undefined;
|
|
24
|
+
cancelToken?: CancellationToken | undefined;
|
|
25
|
+
};
|
|
14
26
|
export interface LikeC4Views {
|
|
15
27
|
readonly layouter: GraphvizLayouter;
|
|
16
28
|
/**
|
|
@@ -18,17 +30,21 @@ export interface LikeC4Views {
|
|
|
18
30
|
*/
|
|
19
31
|
computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
|
|
20
32
|
/**
|
|
21
|
-
*
|
|
22
|
-
* Result includes dot and diagram
|
|
33
|
+
* Layouts all views (ignoring any manual snapshots)
|
|
23
34
|
*/
|
|
24
35
|
layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut[]>;
|
|
25
36
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
37
|
+
* Layouts a view.
|
|
38
|
+
* If layoutType is 'manual' - applies manual layout if any.
|
|
39
|
+
* If layoutType is 'auto' - returns latest version with drifts from manual layout if any
|
|
40
|
+
* If not specified - returns latest layout as is
|
|
41
|
+
*
|
|
42
|
+
* If view not found in model, but there is a snapshot - it will be returned (with empty DOT)
|
|
28
43
|
*/
|
|
29
|
-
layoutView(
|
|
44
|
+
layoutView(params: LayoutViewParams): Promise<GraphvizOut | null>;
|
|
30
45
|
/**
|
|
31
|
-
* Returns diagrams
|
|
46
|
+
* Returns diagrams.
|
|
47
|
+
* If diagram has manual layout, it will be used.
|
|
32
48
|
*/
|
|
33
49
|
diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<DiagramView>>;
|
|
34
50
|
/**
|
|
@@ -53,15 +69,21 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
|
|
|
53
69
|
constructor(services: LikeC4Services);
|
|
54
70
|
get layouter(): QueueGraphvizLayoter;
|
|
55
71
|
computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
|
|
72
|
+
private _layoutAllViews;
|
|
56
73
|
layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut[]>;
|
|
57
|
-
layoutView(viewId
|
|
58
|
-
diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<
|
|
74
|
+
layoutView({ viewId, layoutType, projectId, cancelToken, }: LayoutViewParams): Promise<GraphvizOut | null>;
|
|
75
|
+
diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<LayoutedView>>;
|
|
59
76
|
viewsAsGraphvizOut(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<GraphvizSvgOut>>;
|
|
60
77
|
/**
|
|
61
78
|
* Open a view in the preview panel.
|
|
62
79
|
*/
|
|
63
80
|
openView(viewId: ViewId, projectId: ProjectId): Promise<void>;
|
|
64
81
|
private reportViewError;
|
|
82
|
+
/**
|
|
83
|
+
* Applies manual layout or calculates drifts from snapshot
|
|
84
|
+
* if layoutType is specified
|
|
85
|
+
*/
|
|
86
|
+
private withLayoutType;
|
|
65
87
|
private viewSucceed;
|
|
66
88
|
}
|
|
67
89
|
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { _layout, applyManualLayout, calcDriftsFromSnapshot, invariant } from '@likec4/core';
|
|
2
|
+
import { GraphvizLayouter } from '@likec4/layouts';
|
|
3
|
+
import { loggable } from '@likec4/log';
|
|
4
|
+
import { interruptAndCheck } from 'langium';
|
|
5
|
+
import { isTruthy, unique, values } from 'remeda';
|
|
6
|
+
import { logger as rootLogger, logWarnError } from '../logger';
|
|
7
|
+
import { performanceMark } from '../utils';
|
|
8
|
+
const viewsLogger = rootLogger.getChild('views');
|
|
9
|
+
export class DefaultLikeC4Views {
|
|
10
|
+
services;
|
|
11
|
+
cache = new WeakMap();
|
|
12
|
+
/**
|
|
13
|
+
* Set of viewIds with reported errors
|
|
14
|
+
* value is `${projectId}-${viewId}`
|
|
15
|
+
*/
|
|
16
|
+
viewsWithReportedErrors = new Set();
|
|
17
|
+
ModelBuilder;
|
|
18
|
+
constructor(services) {
|
|
19
|
+
this.services = services;
|
|
20
|
+
this.ModelBuilder = services.likec4.ModelBuilder;
|
|
21
|
+
}
|
|
22
|
+
get layouter() {
|
|
23
|
+
return this.services.likec4.Layouter;
|
|
24
|
+
}
|
|
25
|
+
async computedViews(projectId, cancelToken) {
|
|
26
|
+
const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
|
|
27
|
+
return values(likeC4Model.$data.views);
|
|
28
|
+
}
|
|
29
|
+
async _layoutAllViews(likeC4Model, cancelToken) {
|
|
30
|
+
const views = values(likeC4Model.$data.views);
|
|
31
|
+
if (views.length === 0) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const m0 = performanceMark();
|
|
35
|
+
const projectId = likeC4Model.project.id;
|
|
36
|
+
const logger = viewsLogger.getChild(projectId);
|
|
37
|
+
logger.debug `layoutAll: ${views.length} views`;
|
|
38
|
+
const tasks = [];
|
|
39
|
+
const styles = likeC4Model.$styles;
|
|
40
|
+
const results = [];
|
|
41
|
+
//
|
|
42
|
+
for (const view of views) {
|
|
43
|
+
let cached = this.cache.get(view);
|
|
44
|
+
if (cached) {
|
|
45
|
+
logger.debug `layout ${view.id} from cache`;
|
|
46
|
+
results.push(cached);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
tasks.push({
|
|
50
|
+
view,
|
|
51
|
+
styles,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (tasks.length > 0) {
|
|
55
|
+
await this.layouter.batchLayout({
|
|
56
|
+
batch: tasks,
|
|
57
|
+
cancelToken,
|
|
58
|
+
onSuccess: (task, result) => {
|
|
59
|
+
results.push(this.viewSucceed(task.view, likeC4Model, result));
|
|
60
|
+
},
|
|
61
|
+
onError: (task, error) => {
|
|
62
|
+
logger.warn(`Fail layout view ${task.view.id}`, { error });
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (cancelToken && cancelToken.isCancellationRequested) {
|
|
67
|
+
await interruptAndCheck(cancelToken);
|
|
68
|
+
}
|
|
69
|
+
if (results.length !== views.length) {
|
|
70
|
+
logger.warn `layouted ${results.length} of ${views.length} views in ${m0.pretty}`;
|
|
71
|
+
}
|
|
72
|
+
else if (results.length > 0) {
|
|
73
|
+
logger.debug `layouted all ${results.length} views in ${m0.pretty}`;
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
async layoutAllViews(projectId, cancelToken) {
|
|
78
|
+
const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
|
|
79
|
+
return await this._layoutAllViews(likeC4Model, cancelToken);
|
|
80
|
+
}
|
|
81
|
+
async layoutView({ viewId, layoutType, projectId, cancelToken, }) {
|
|
82
|
+
const model = await this.ModelBuilder.computeModel(projectId, cancelToken);
|
|
83
|
+
const view = model.findView(viewId)?.$view;
|
|
84
|
+
projectId = model.project.id;
|
|
85
|
+
const logger = viewsLogger.getChild(projectId);
|
|
86
|
+
if (!view) {
|
|
87
|
+
logger.warn `layoutView ${viewId} not found`;
|
|
88
|
+
const snapshot = model.findManualLayout(viewId);
|
|
89
|
+
if (snapshot) {
|
|
90
|
+
logger.debug `found manual layout for ${viewId}`;
|
|
91
|
+
let diagram = { ...snapshot };
|
|
92
|
+
diagram.drifts = [
|
|
93
|
+
'not-exists',
|
|
94
|
+
];
|
|
95
|
+
diagram._layout = 'manual';
|
|
96
|
+
return {
|
|
97
|
+
diagram: diagram,
|
|
98
|
+
dot: '# manual layout',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const m0 = performanceMark();
|
|
105
|
+
const out = this.cache.get(view) ?? await this.layouter.layout({
|
|
106
|
+
view,
|
|
107
|
+
styles: model.$styles,
|
|
108
|
+
});
|
|
109
|
+
if (this.cache.has(view)) {
|
|
110
|
+
logger.debug `layout ${viewId} from cache`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.viewSucceed(view, model, out);
|
|
114
|
+
logger.debug(`layout {viewId} in ${m0.pretty}`, { viewId });
|
|
115
|
+
}
|
|
116
|
+
if (isTruthy(layoutType)) {
|
|
117
|
+
return {
|
|
118
|
+
dot: out.dot,
|
|
119
|
+
diagram: this.withLayoutType(out.diagram, model, layoutType),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
const errMessage = loggable(e);
|
|
126
|
+
logger.warn(errMessage);
|
|
127
|
+
this.reportViewError(view, projectId, errMessage);
|
|
128
|
+
return Promise.reject(e);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async diagrams(projectId, cancelToken) {
|
|
132
|
+
const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
|
|
133
|
+
const layouted = await this._layoutAllViews(likeC4Model, cancelToken);
|
|
134
|
+
return layouted.map(({ diagram }) => {
|
|
135
|
+
// Apply manual layout if any
|
|
136
|
+
return this.withLayoutType(diagram, likeC4Model, 'manual');
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async viewsAsGraphvizOut(projectId, cancelToken) {
|
|
140
|
+
const KEY = 'All-LayoutedViews-DotWithSvg';
|
|
141
|
+
const cache = this.services.shared.workspace.Cache;
|
|
142
|
+
if (cache.has(KEY)) {
|
|
143
|
+
return await Promise.resolve(cache.get(KEY));
|
|
144
|
+
}
|
|
145
|
+
const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
|
|
146
|
+
const views = values(likeC4Model.$data.views);
|
|
147
|
+
if (views.length === 0) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const tasks = views.map(async (view) => {
|
|
151
|
+
const { dot, svg } = await this.layouter.svg({
|
|
152
|
+
view,
|
|
153
|
+
styles: likeC4Model.$styles,
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
id: view.id,
|
|
157
|
+
dot,
|
|
158
|
+
svg,
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
const succeed = [];
|
|
162
|
+
const settledResult = await Promise.allSettled(tasks);
|
|
163
|
+
for (const result of settledResult) {
|
|
164
|
+
if (result.status === 'fulfilled') {
|
|
165
|
+
succeed.push(result.value);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
logWarnError(result.reason);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
cache.set(KEY, succeed);
|
|
172
|
+
return succeed;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Open a view in the preview panel.
|
|
176
|
+
*/
|
|
177
|
+
async openView(viewId, projectId) {
|
|
178
|
+
await this.services.Rpc.openView({ viewId, projectId });
|
|
179
|
+
}
|
|
180
|
+
reportViewError(view, projectId, error) {
|
|
181
|
+
const key = `${projectId}-${view.id}`;
|
|
182
|
+
this.cache.delete(view);
|
|
183
|
+
if (!this.viewsWithReportedErrors.has(key)) {
|
|
184
|
+
this.services.shared.lsp.Connection?.window.showErrorMessage(`LikeC4: ${error}`);
|
|
185
|
+
this.viewsWithReportedErrors.add(key);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Applies manual layout or calculates drifts from snapshot
|
|
190
|
+
* if layoutType is specified
|
|
191
|
+
*/
|
|
192
|
+
withLayoutType(layouted, likec4model, layoutType) {
|
|
193
|
+
if (!layoutType) {
|
|
194
|
+
return layouted;
|
|
195
|
+
}
|
|
196
|
+
const snapshot = likec4model.findManualLayout(layouted.id);
|
|
197
|
+
if (!snapshot) {
|
|
198
|
+
return layouted;
|
|
199
|
+
}
|
|
200
|
+
if (layoutType === 'manual') {
|
|
201
|
+
if (layouted[_layout] === 'manual') {
|
|
202
|
+
viewsLogger.error(`View ${layouted.id} already has manual layout, this should not happen`);
|
|
203
|
+
return layouted;
|
|
204
|
+
}
|
|
205
|
+
return applyManualLayout(layouted, snapshot);
|
|
206
|
+
}
|
|
207
|
+
return calcDriftsFromSnapshot(layouted, snapshot);
|
|
208
|
+
}
|
|
209
|
+
viewSucceed(view, likec4model, result) {
|
|
210
|
+
const projectId = likec4model.project.id;
|
|
211
|
+
const key = `${projectId}-${view.id}`;
|
|
212
|
+
this.viewsWithReportedErrors.delete(key);
|
|
213
|
+
this.cache.set(view, result);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
}
|
package/dist/views/index.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import type { LikeC4ManualLayouts, LikeC4ManualLayoutsModuleContext } from './LikeC4ManualLayouts';
|
|
2
|
+
export { DefaultLikeC4Views, type LikeC4Views } from './LikeC4Views';
|
|
3
|
+
export type { LikeC4ManualLayouts, LikeC4ManualLayoutsModuleContext };
|
|
4
|
+
export declare const NoopLikeC4ManualLayouts: LikeC4ManualLayoutsModuleContext;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { DefaultLikeC4Views } from './LikeC4Views';
|
|
2
|
+
export const NoopLikeC4ManualLayouts = {
|
|
3
|
+
manualLayouts: () => {
|
|
4
|
+
return {
|
|
5
|
+
read: () => Promise.resolve(null),
|
|
6
|
+
write: () => Promise.reject(new Error('NoopLikeC4ManualLayouts: write operation is not supported')),
|
|
7
|
+
remove: () => Promise.resolve(null),
|
|
8
|
+
clearCaches: () => { },
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AstUtils, DefaultAstNodeDescriptionProvider, } from 'langium';
|
|
2
|
+
import { isLikeC4Builtin } from '../likec4lib';
|
|
3
|
+
export class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
|
|
4
|
+
services;
|
|
5
|
+
constructor(services) {
|
|
6
|
+
super(services);
|
|
7
|
+
this.services = services;
|
|
8
|
+
}
|
|
9
|
+
createDescription(node, name, document) {
|
|
10
|
+
document ??= AstUtils.getDocument(node);
|
|
11
|
+
const description = super.createDescription(node, name, document);
|
|
12
|
+
if (!isLikeC4Builtin(document.uri)) {
|
|
13
|
+
document.likec4ProjectId ??= this.services.shared.workspace.ProjectsManager.belongsTo(document);
|
|
14
|
+
description.likec4ProjectId = document.likec4ProjectId;
|
|
15
|
+
}
|
|
16
|
+
return description;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DefaultIndexManager, stream } from 'langium';
|
|
2
|
+
export class IndexManager extends DefaultIndexManager {
|
|
3
|
+
services;
|
|
4
|
+
constructor(services) {
|
|
5
|
+
super(services);
|
|
6
|
+
this.services = services;
|
|
7
|
+
}
|
|
8
|
+
async updateContent(document, cancelToken) {
|
|
9
|
+
const projects = this.services.workspace.ProjectsManager;
|
|
10
|
+
// Ensure the document is assigned to a project
|
|
11
|
+
document.likec4ProjectId = projects.belongsTo(document);
|
|
12
|
+
await super.updateContent(document, cancelToken);
|
|
13
|
+
}
|
|
14
|
+
projectElements(projectId, nodeType, uris) {
|
|
15
|
+
const projects = this.services.workspace.ProjectsManager;
|
|
16
|
+
let documentUris = stream(this.symbolIndex.keys());
|
|
17
|
+
return documentUris
|
|
18
|
+
.filter(uri => projects.belongsTo(uri) === projectId && (!uris || uris.has(uri)))
|
|
19
|
+
.flatMap(uri => this.getFileDescriptions(uri, nodeType));
|
|
20
|
+
}
|
|
21
|
+
}
|