@likec4/language-server 1.43.0 → 1.44.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.
Files changed (299) hide show
  1. package/browser/package.json +1 -1
  2. package/browser-worker/package.json +1 -1
  3. package/dist/LikeC4LanguageServices.d.ts +12 -19
  4. package/dist/LikeC4LanguageServices.js +182 -0
  5. package/dist/Rpc.js +245 -0
  6. package/dist/ast.d.ts +5 -1
  7. package/dist/ast.js +253 -0
  8. package/dist/browser-worker.js +4 -0
  9. package/dist/browser.js +35 -0
  10. package/dist/bundled.js +42 -0
  11. package/dist/bundled.mjs +3542 -3896
  12. package/dist/documentation/documentation-provider.js +51 -0
  13. package/dist/documentation/index.js +1 -0
  14. package/dist/empty.js +2 -0
  15. package/dist/filesystem/ChokidarWatcher.js +97 -0
  16. package/dist/filesystem/FileSystemWatcher.js +14 -0
  17. package/dist/filesystem/LikeC4FileSystem.d.ts +1 -2
  18. package/dist/filesystem/LikeC4FileSystem.js +126 -0
  19. package/dist/filesystem/index.d.ts +26 -0
  20. package/dist/filesystem/index.js +29 -0
  21. package/dist/formatting/LikeC4Formatter.js +637 -0
  22. package/dist/formatting/utils.js +18 -0
  23. package/dist/generated/ast.js +2155 -0
  24. package/dist/generated/{grammar.mjs → grammar.js} +6 -2
  25. package/dist/generated/module.d.ts +6 -1
  26. package/dist/generated/module.js +27 -0
  27. package/dist/generated-lib/{icons.mjs → icons.js} +11 -7
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.js +53 -0
  30. package/dist/{likec4lib.mjs → likec4lib.js} +3 -3
  31. package/dist/logger.js +81 -0
  32. package/dist/lsp/CodeActionProvider.d.ts +14 -0
  33. package/dist/lsp/CodeActionProvider.js +33 -0
  34. package/dist/lsp/CodeLensProvider.js +44 -0
  35. package/dist/lsp/CompletionProvider.d.ts +3 -1
  36. package/dist/lsp/CompletionProvider.js +200 -0
  37. package/dist/lsp/DocumentHighlightProvider.js +10 -0
  38. package/dist/lsp/DocumentLinkProvider.js +58 -0
  39. package/dist/lsp/DocumentSymbolProvider.js +306 -0
  40. package/dist/lsp/HoverProvider.js +106 -0
  41. package/dist/lsp/RenameProvider.js +6 -0
  42. package/dist/lsp/SemanticTokenProvider.js +257 -0
  43. package/dist/lsp/index.d.ts +1 -0
  44. package/dist/lsp/index.js +9 -0
  45. package/dist/mcp/MCPServerFactory.js +73 -0
  46. package/dist/mcp/NoopLikeC4MCPServer.js +17 -0
  47. package/dist/mcp/interfaces.js +5 -0
  48. package/dist/mcp/server/StdioLikeC4MCPServer.js +47 -0
  49. package/dist/mcp/server/StreamableLikeC4MCPServer.js +145 -0
  50. package/dist/mcp/server/WithMCPServer.js +56 -0
  51. package/dist/mcp/tools/_common.d.ts +8 -7
  52. package/dist/mcp/tools/_common.js +49 -0
  53. package/dist/mcp/tools/find-relationships.d.ts +7 -8
  54. package/dist/mcp/tools/find-relationships.js +150 -0
  55. package/dist/mcp/tools/list-projects.d.ts +3 -3
  56. package/dist/mcp/tools/list-projects.js +62 -0
  57. package/dist/mcp/tools/open-view.d.ts +6 -7
  58. package/dist/mcp/tools/open-view.js +52 -0
  59. package/dist/mcp/tools/read-deployment.d.ts +6 -7
  60. package/dist/mcp/tools/read-deployment.js +132 -0
  61. package/dist/mcp/tools/read-element.d.ts +6 -7
  62. package/dist/mcp/tools/read-element.js +194 -0
  63. package/dist/mcp/tools/read-project-summary.d.ts +5 -6
  64. package/dist/mcp/tools/read-project-summary.js +176 -0
  65. package/dist/mcp/tools/read-view.d.ts +6 -7
  66. package/dist/mcp/tools/read-view.js +203 -0
  67. package/dist/mcp/tools/search-element.d.ts +3 -3
  68. package/dist/mcp/tools/search-element.js +177 -0
  69. package/dist/mcp/utils.d.ts +2 -2
  70. package/dist/mcp/utils.js +48 -0
  71. package/dist/model/builder/MergedExtends.js +74 -0
  72. package/dist/model/builder/MergedSpecification.js +175 -0
  73. package/dist/model/builder/buildModel.js +176 -0
  74. package/dist/model/deployments-index.js +102 -0
  75. package/dist/model/fqn-index.js +250 -0
  76. package/dist/model/index.js +6 -0
  77. package/dist/model/model-builder.d.ts +13 -11
  78. package/dist/model/model-builder.js +234 -0
  79. package/dist/model/model-locator.d.ts +6 -5
  80. package/dist/model/model-locator.js +240 -0
  81. package/dist/model/model-parser-where.js +81 -0
  82. package/dist/model/model-parser.d.ts +309 -304
  83. package/dist/model/model-parser.js +119 -0
  84. package/dist/model/parser/Base.d.ts +2 -2
  85. package/dist/model/parser/Base.js +367 -0
  86. package/dist/model/parser/DeploymentModelParser.d.ts +2 -2
  87. package/dist/model/parser/DeploymentModelParser.js +176 -0
  88. package/dist/model/parser/DeploymentViewParser.d.ts +3 -3
  89. package/dist/model/parser/DeploymentViewParser.js +86 -0
  90. package/dist/model/parser/FqnRefParser.d.ts +2 -2
  91. package/dist/model/parser/FqnRefParser.js +382 -0
  92. package/dist/model/parser/GlobalsParser.d.ts +6 -6
  93. package/dist/model/parser/GlobalsParser.js +84 -0
  94. package/dist/model/parser/ImportsParser.d.ts +11 -12
  95. package/dist/model/parser/ImportsParser.js +24 -0
  96. package/dist/model/parser/ModelParser.d.ts +2 -2
  97. package/dist/model/parser/ModelParser.js +165 -0
  98. package/dist/model/parser/PredicatesParser.d.ts +2 -2
  99. package/dist/model/parser/PredicatesParser.js +45 -0
  100. package/dist/model/parser/SpecificationParser.d.ts +2 -2
  101. package/dist/model/parser/SpecificationParser.js +109 -0
  102. package/dist/model/parser/ValueConverter.js +12 -0
  103. package/dist/model/parser/ViewsParser.d.ts +3 -3
  104. package/dist/model/parser/ViewsParser.js +477 -0
  105. package/dist/model-change/ModelChanges.d.ts +6 -3
  106. package/dist/model-change/ModelChanges.js +102 -0
  107. package/dist/model-change/changeElementStyle.js +134 -0
  108. package/dist/model-change/changeViewLayout.d.ts +2 -2
  109. package/dist/model-change/changeViewLayout.js +28 -0
  110. package/dist/model-change/removeManualLayoutV1.d.ts +7 -0
  111. package/dist/model-change/removeManualLayoutV1.js +27 -0
  112. package/dist/module.d.ts +10 -5
  113. package/dist/module.js +143 -0
  114. package/dist/protocol.d.ts +1 -17
  115. package/dist/protocol.js +114 -0
  116. package/dist/references/index.js +3 -0
  117. package/dist/references/name-provider.js +37 -0
  118. package/dist/references/scope-computation.js +288 -0
  119. package/dist/references/scope-provider.d.ts +3 -3
  120. package/dist/references/scope-provider.js +242 -0
  121. package/dist/shared/NodeKindProvider.js +57 -0
  122. package/dist/shared/{WorkspaceSymbolProvider.mjs → WorkspaceSymbolProvider.js} +1 -1
  123. package/dist/shared/index.js +2 -0
  124. package/dist/test/index.js +1 -0
  125. package/dist/test/testServices.d.ts +16 -16
  126. package/dist/test/testServices.js +210 -0
  127. package/dist/utils/disposable.js +26 -0
  128. package/dist/utils/elementRef.d.ts +1 -1
  129. package/dist/utils/elementRef.js +27 -0
  130. package/dist/utils/fqnRef.js +63 -0
  131. package/dist/utils/index.js +35 -0
  132. package/dist/utils/printDocs.js +1 -0
  133. package/dist/utils/projectId.js +16 -0
  134. package/dist/utils/stringHash.js +5 -0
  135. package/dist/validation/DocumentValidator.js +17 -0
  136. package/dist/validation/_shared.js +26 -0
  137. package/dist/validation/deployment-checks.js +140 -0
  138. package/dist/validation/dynamic-view.js +67 -0
  139. package/dist/validation/element-ref.js +12 -0
  140. package/dist/validation/element.js +49 -0
  141. package/dist/validation/imports.js +46 -0
  142. package/dist/validation/index.d.ts +1 -1
  143. package/dist/validation/index.js +157 -0
  144. package/dist/validation/property-checks.js +108 -0
  145. package/dist/validation/relation.js +55 -0
  146. package/dist/validation/specification.js +190 -0
  147. package/dist/validation/view-predicates/fqn-expr-with.js +43 -0
  148. package/dist/validation/view-predicates/fqn-ref-expr.js +51 -0
  149. package/dist/validation/view-predicates/incoming.js +16 -0
  150. package/dist/validation/view-predicates/index.js +6 -0
  151. package/dist/validation/view-predicates/outgoing.js +20 -0
  152. package/dist/validation/view-predicates/relation-expr.js +46 -0
  153. package/dist/validation/view-predicates/relation-with.js +16 -0
  154. package/dist/validation/view.d.ts +1 -1
  155. package/dist/validation/view.js +42 -0
  156. package/dist/view-utils/assignNavigateTo.js +27 -0
  157. package/dist/view-utils/index.d.ts +1 -0
  158. package/dist/view-utils/index.js +2 -0
  159. package/dist/view-utils/manual-layout.d.ts +6 -0
  160. package/dist/view-utils/manual-layout.js +151 -0
  161. package/dist/views/ConfigurableLayouter.js +51 -0
  162. package/dist/views/LikeC4ManualLayouts.d.ts +28 -0
  163. package/dist/views/LikeC4ManualLayouts.js +132 -0
  164. package/dist/views/{likec4-views.d.ts → LikeC4Views.d.ts} +9 -8
  165. package/dist/views/LikeC4Views.js +200 -0
  166. package/dist/views/index.d.ts +4 -1
  167. package/dist/views/index.js +11 -0
  168. package/dist/workspace/AstNodeDescriptionProvider.js +15 -0
  169. package/dist/workspace/IndexManager.js +21 -0
  170. package/dist/workspace/LangiumDocuments.d.ts +1 -1
  171. package/dist/workspace/LangiumDocuments.js +58 -0
  172. package/dist/workspace/ProjectsManager.d.ts +8 -3
  173. package/dist/workspace/ProjectsManager.js +373 -0
  174. package/dist/workspace/WorkspaceManager.d.ts +3 -2
  175. package/dist/workspace/WorkspaceManager.js +93 -0
  176. package/dist/workspace/index.js +5 -0
  177. package/likec4lib/package.json +1 -1
  178. package/package.json +25 -24
  179. package/protocol/package.json +1 -1
  180. package/dist/LikeC4LanguageServices.mjs +0 -197
  181. package/dist/Rpc.mjs +0 -296
  182. package/dist/ast.mjs +0 -221
  183. package/dist/browser-worker.mjs +0 -2
  184. package/dist/browser.mjs +0 -32
  185. package/dist/documentation/documentation-provider.mjs +0 -48
  186. package/dist/documentation/index.mjs +0 -1
  187. package/dist/empty.mjs +0 -1
  188. package/dist/filesystem/ChokidarWatcher.mjs +0 -68
  189. package/dist/filesystem/FileSystemWatcher.mjs +0 -11
  190. package/dist/filesystem/LikeC4FileSystem.mjs +0 -64
  191. package/dist/filesystem/index.mjs +0 -19
  192. package/dist/formatting/LikeC4Formatter.mjs +0 -511
  193. package/dist/formatting/utils.mjs +0 -15
  194. package/dist/generated/ast.mjs +0 -2150
  195. package/dist/generated/module.mjs +0 -23
  196. package/dist/index.mjs +0 -50
  197. package/dist/logger.mjs +0 -82
  198. package/dist/lsp/CodeLensProvider.mjs +0 -42
  199. package/dist/lsp/CompletionProvider.mjs +0 -208
  200. package/dist/lsp/DocumentHighlightProvider.mjs +0 -10
  201. package/dist/lsp/DocumentLinkProvider.mjs +0 -53
  202. package/dist/lsp/DocumentSymbolProvider.mjs +0 -287
  203. package/dist/lsp/HoverProvider.mjs +0 -104
  204. package/dist/lsp/RenameProvider.mjs +0 -6
  205. package/dist/lsp/SemanticTokenProvider.mjs +0 -276
  206. package/dist/lsp/index.mjs +0 -7
  207. package/dist/mcp/MCPServerFactory.mjs +0 -70
  208. package/dist/mcp/NoopLikeC4MCPServer.mjs +0 -17
  209. package/dist/mcp/interfaces.mjs +0 -4
  210. package/dist/mcp/server/StdioLikeC4MCPServer.mjs +0 -46
  211. package/dist/mcp/server/StreamableLikeC4MCPServer.mjs +0 -153
  212. package/dist/mcp/server/WithMCPServer.mjs +0 -58
  213. package/dist/mcp/tools/_common.mjs +0 -42
  214. package/dist/mcp/tools/find-relationships.mjs +0 -151
  215. package/dist/mcp/tools/list-projects.mjs +0 -62
  216. package/dist/mcp/tools/open-view.mjs +0 -52
  217. package/dist/mcp/tools/read-deployment.mjs +0 -130
  218. package/dist/mcp/tools/read-element.mjs +0 -198
  219. package/dist/mcp/tools/read-project-summary.mjs +0 -178
  220. package/dist/mcp/tools/read-view.mjs +0 -205
  221. package/dist/mcp/tools/search-element.mjs +0 -171
  222. package/dist/mcp/utils.mjs +0 -47
  223. package/dist/model/builder/MergedExtends.mjs +0 -76
  224. package/dist/model/builder/MergedSpecification.mjs +0 -205
  225. package/dist/model/builder/assignTagColors.d.ts +0 -7
  226. package/dist/model/builder/assignTagColors.mjs +0 -51
  227. package/dist/model/builder/buildModel.mjs +0 -226
  228. package/dist/model/deployments-index.mjs +0 -100
  229. package/dist/model/fqn-index.mjs +0 -243
  230. package/dist/model/index.mjs +0 -6
  231. package/dist/model/model-builder.mjs +0 -285
  232. package/dist/model/model-locator.mjs +0 -239
  233. package/dist/model/model-parser-where.mjs +0 -81
  234. package/dist/model/model-parser.mjs +0 -127
  235. package/dist/model/parser/Base.mjs +0 -376
  236. package/dist/model/parser/DeploymentModelParser.mjs +0 -212
  237. package/dist/model/parser/DeploymentViewParser.mjs +0 -95
  238. package/dist/model/parser/FqnRefParser.mjs +0 -398
  239. package/dist/model/parser/GlobalsParser.mjs +0 -82
  240. package/dist/model/parser/ImportsParser.mjs +0 -28
  241. package/dist/model/parser/ModelParser.mjs +0 -190
  242. package/dist/model/parser/PredicatesParser.mjs +0 -45
  243. package/dist/model/parser/SpecificationParser.mjs +0 -120
  244. package/dist/model/parser/ValueConverter.mjs +0 -12
  245. package/dist/model/parser/ViewsParser.mjs +0 -490
  246. package/dist/model-change/ModelChanges.mjs +0 -89
  247. package/dist/model-change/changeElementStyle.mjs +0 -143
  248. package/dist/model-change/changeViewLayout.mjs +0 -32
  249. package/dist/model-change/saveManualLayout.d.ts +0 -11
  250. package/dist/model-change/saveManualLayout.mjs +0 -27
  251. package/dist/module.mjs +0 -180
  252. package/dist/protocol.mjs +0 -65
  253. package/dist/references/index.mjs +0 -3
  254. package/dist/references/name-provider.mjs +0 -39
  255. package/dist/references/scope-computation.mjs +0 -312
  256. package/dist/references/scope-provider.mjs +0 -239
  257. package/dist/shared/NodeKindProvider.mjs +0 -110
  258. package/dist/shared/index.mjs +0 -2
  259. package/dist/test/index.mjs +0 -1
  260. package/dist/test/testServices.mjs +0 -200
  261. package/dist/utils/disposable.mjs +0 -25
  262. package/dist/utils/elementRef.mjs +0 -20
  263. package/dist/utils/fqnRef.mjs +0 -57
  264. package/dist/utils/index.mjs +0 -33
  265. package/dist/utils/printDocs.mjs +0 -1
  266. package/dist/utils/projectId.mjs +0 -16
  267. package/dist/utils/stringHash.mjs +0 -5
  268. package/dist/validation/DocumentValidator.mjs +0 -16
  269. package/dist/validation/_shared.mjs +0 -25
  270. package/dist/validation/deployment-checks.mjs +0 -146
  271. package/dist/validation/dynamic-view.mjs +0 -67
  272. package/dist/validation/element-ref.mjs +0 -12
  273. package/dist/validation/element.mjs +0 -50
  274. package/dist/validation/imports.mjs +0 -25
  275. package/dist/validation/index.mjs +0 -180
  276. package/dist/validation/property-checks.mjs +0 -107
  277. package/dist/validation/relation.mjs +0 -53
  278. package/dist/validation/specification.mjs +0 -173
  279. package/dist/validation/view-predicates/fqn-expr-with.mjs +0 -43
  280. package/dist/validation/view-predicates/fqn-ref-expr.mjs +0 -53
  281. package/dist/validation/view-predicates/incoming.mjs +0 -16
  282. package/dist/validation/view-predicates/index.mjs +0 -6
  283. package/dist/validation/view-predicates/outgoing.mjs +0 -20
  284. package/dist/validation/view-predicates/relation-expr.mjs +0 -39
  285. package/dist/validation/view-predicates/relation-with.mjs +0 -16
  286. package/dist/validation/view.mjs +0 -25
  287. package/dist/view-utils/assignNavigateTo.mjs +0 -25
  288. package/dist/view-utils/index.mjs +0 -1
  289. package/dist/view-utils/manual-layout.mjs +0 -99
  290. package/dist/views/configurable-layouter.mjs +0 -51
  291. package/dist/views/index.mjs +0 -1
  292. package/dist/views/likec4-views.mjs +0 -166
  293. package/dist/workspace/AstNodeDescriptionProvider.mjs +0 -17
  294. package/dist/workspace/IndexManager.mjs +0 -17
  295. package/dist/workspace/LangiumDocuments.mjs +0 -53
  296. package/dist/workspace/ProjectsManager.mjs +0 -360
  297. package/dist/workspace/WorkspaceManager.mjs +0 -83
  298. package/dist/workspace/index.mjs +0 -5
  299. /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,28 @@
1
+ import type { LayoutedView, ViewId } from '@likec4/core';
2
+ import { type Location } from 'vscode-languageserver-types';
3
+ import type { LikeC4Services } from '../module';
4
+ import type { Project } from '../workspace/ProjectsManager';
5
+ /**
6
+ * @todo sync with vscode extension watchers
7
+ * (search for ".likec4.snap" references)
8
+ */
9
+ export declare const isManualLayoutFile: (path: string) => boolean;
10
+ export interface LikeC4ManualLayouts {
11
+ read(project: Project): Promise<Record<ViewId, LayoutedView> | null>;
12
+ write(project: Project, layouted: LayoutedView): Promise<Location>;
13
+ remove(project: Project, view: ViewId): Promise<Location | null>;
14
+ clearCaches(): void;
15
+ }
16
+ export interface LikeC4ManualLayoutsModuleContext {
17
+ manualLayouts: (services: LikeC4Services) => LikeC4ManualLayouts;
18
+ }
19
+ export declare const WithLikeC4ManualLayouts: LikeC4ManualLayoutsModuleContext;
20
+ export declare class DefaultLikeC4ManualLayouts implements LikeC4ManualLayouts {
21
+ private services;
22
+ private manualLayouts;
23
+ constructor(services: LikeC4Services);
24
+ read(project: Project): Promise<Record<ViewId, LayoutedView> | null>;
25
+ write(project: Project, layouted: LayoutedView): Promise<Location>;
26
+ remove(project: Project, view: ViewId): Promise<Location | null>;
27
+ clearCaches(): void;
28
+ }
@@ -0,0 +1,132 @@
1
+ import { getOrCreate } from '@likec4/core/utils';
2
+ import JSON5 from 'json5';
3
+ import { UriUtils } from 'langium';
4
+ import { indexBy, prop } from 'remeda';
5
+ import { Position, Range, } from 'vscode-languageserver-types';
6
+ import { logger as rootLogger } from '../logger';
7
+ const logger = rootLogger.getChild('manual-layouts');
8
+ /**
9
+ * @todo sync with vscode extension watchers
10
+ * (search for ".likec4.snap" references)
11
+ */
12
+ export const isManualLayoutFile = (path) => path.endsWith('.likec4.snap');
13
+ function fileName(view) {
14
+ return `${view}.likec4.snap`;
15
+ }
16
+ function getManualLayoutsOutDir(project) {
17
+ return UriUtils.resolvePath(project.folderUri, project.config.manualLayouts?.outDir ?? '.likec4');
18
+ }
19
+ export const WithLikeC4ManualLayouts = {
20
+ manualLayouts: (services) => new DefaultLikeC4ManualLayouts(services),
21
+ };
22
+ export class DefaultLikeC4ManualLayouts {
23
+ services;
24
+ manualLayouts = new WeakMap();
25
+ constructor(services) {
26
+ this.services = services;
27
+ }
28
+ async read(project) {
29
+ return await getOrCreate(this.manualLayouts, project.folderUri, async (_) => {
30
+ const fs = this.services.shared.workspace.FileSystemProvider;
31
+ const outDir = getManualLayoutsOutDir(project);
32
+ const manualLayouts = [];
33
+ try {
34
+ const files = await fs.scanDirectory(outDir, isManualLayoutFile);
35
+ if (files.length === 0) {
36
+ return null;
37
+ }
38
+ for (const file of files) {
39
+ if (file.isFile) {
40
+ try {
41
+ const content = await fs.readFile(file.uri);
42
+ manualLayouts.push({
43
+ ...JSON5.parse(content),
44
+ _layout: 'manual',
45
+ });
46
+ }
47
+ catch (err) {
48
+ logger.warn(`Failed to read view snapshot ${file.uri.fsPath}`, { err });
49
+ }
50
+ }
51
+ }
52
+ if (manualLayouts.length) {
53
+ logger.debug `read manual layouts for ${project.id}, found ${manualLayouts.length}`;
54
+ }
55
+ }
56
+ catch (err) {
57
+ logger.warn(`Failed to read manual layouts for ${project.folderUri.fsPath}`, { err });
58
+ }
59
+ if (manualLayouts.length === 0) {
60
+ return null;
61
+ }
62
+ return indexBy(manualLayouts, prop('id'));
63
+ });
64
+ }
65
+ async write(project, layouted) {
66
+ const outDir = getManualLayoutsOutDir(project);
67
+ const file = UriUtils.joinPath(outDir, fileName(layouted.id));
68
+ // Ensure the manualLayout field is omitted (may exist in migration)
69
+ if ('manualLayout' in layouted) {
70
+ const { manualLayout: _, ...rest } = layouted;
71
+ layouted = rest;
72
+ }
73
+ const content = JSON5.stringify(layouted, {
74
+ space: 2,
75
+ quote: '\'',
76
+ });
77
+ const location = {
78
+ uri: file.toString(),
79
+ range: Range.create(Position.create(0, 0), Position.create(content.split('\n').length - 1, 1)),
80
+ };
81
+ logger.debug `write snapshot of ${layouted.id} in project ${project.id} to ${file.fsPath}`;
82
+ const fs = this.services.shared.workspace.FileSystemProvider;
83
+ try {
84
+ await fs.writeFile(file, content);
85
+ }
86
+ catch (err) {
87
+ logger.warn(`Failed to write snapshot ${layouted.id} to ${file.fsPath}`, { err });
88
+ }
89
+ const projectCaches = await this.read(project);
90
+ if (projectCaches) {
91
+ logger.debug `update snapshot cache of ${layouted.id} in project ${project.id}`;
92
+ projectCaches[layouted.id] = layouted;
93
+ }
94
+ else {
95
+ this.manualLayouts.delete(project.folderUri);
96
+ }
97
+ this.services.likec4.ModelBuilder.clearCache();
98
+ return location;
99
+ }
100
+ async remove(project, view) {
101
+ const outDir = getManualLayoutsOutDir(project);
102
+ const file = UriUtils.joinPath(outDir, fileName(view));
103
+ logger.debug `delete snapshot of ${view} in project ${project.id}. File: ${file.fsPath}`;
104
+ const location = {
105
+ uri: file.toString(),
106
+ range: Range.create(0, 0, 0, 0),
107
+ };
108
+ try {
109
+ const fs = this.services.shared.workspace.FileSystemProvider;
110
+ if (!(await fs.deleteFile(file))) {
111
+ logger.warn `Snapshot ${view} did not exist at ${file.fsPath}`;
112
+ return null;
113
+ }
114
+ }
115
+ catch (err) {
116
+ logger.warn(`Failed to delete snapshot ${view} from ${file.fsPath}`, { err });
117
+ }
118
+ const projectCaches = await this.read(project);
119
+ if (projectCaches) {
120
+ logger.debug `clean cache of ${view} in project ${project.id}`;
121
+ delete projectCaches[view];
122
+ }
123
+ else {
124
+ this.manualLayouts.delete(project.folderUri);
125
+ }
126
+ this.services.likec4.ModelBuilder.clearCache();
127
+ return location;
128
+ }
129
+ clearCaches() {
130
+ this.manualLayouts = new WeakMap();
131
+ }
132
+ }
@@ -1,10 +1,10 @@
1
- import type { ComputedView, DiagramView, ProjectId, ViewId } from '@likec4/core';
1
+ import type { ComputedView, DiagramView, LayoutedView, 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: DiagramView;
7
+ readonly diagram: LayoutedView;
8
8
  };
9
9
  type GraphvizSvgOut = {
10
10
  readonly id: ViewId;
@@ -18,17 +18,17 @@ export interface LikeC4Views {
18
18
  */
19
19
  computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
20
20
  /**
21
- * Returns all layouted views (i.e. views with layout computed)
22
- * Result includes dot and diagram
21
+ * Returns all layouted views (without manual layout)
23
22
  */
24
23
  layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut[]>;
25
24
  /**
26
- * Returns layouted view (i.e. view with layout computed)
27
- * Result includes dot and diagram
25
+ * Layouts a view (from sources, i.e. without manual layout)
26
+ * If view not found in model, but there is a snapshot - it will be returned (with empty DOT)
28
27
  */
29
28
  layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
30
29
  /**
31
- * Returns diagrams (i.e. views with layout computed)
30
+ * Returns diagrams.
31
+ * If diagram has manual layout, it will be used.
32
32
  */
33
33
  diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<DiagramView>>;
34
34
  /**
@@ -53,9 +53,10 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
53
53
  constructor(services: LikeC4Services);
54
54
  get layouter(): QueueGraphvizLayoter;
55
55
  computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
56
+ private _layoutAllViews;
56
57
  layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut[]>;
57
58
  layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
58
- diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<DiagramView>>;
59
+ diagrams(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<LayoutedView>>;
59
60
  viewsAsGraphvizOut(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<GraphvizSvgOut>>;
60
61
  /**
61
62
  * Open a view in the preview panel.
@@ -0,0 +1,200 @@
1
+ import { applyLayoutDriftReasons, applyManualLayout } from '@likec4/core/model';
2
+ import { GraphvizLayouter } from '@likec4/layouts';
3
+ import { loggable } from '@likec4/log';
4
+ import { interruptAndCheck } from 'langium';
5
+ import { 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, 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 project = this.services.shared.workspace.ProjectsManager.getProject(projectId);
89
+ const manualLayouts = await this.services.likec4.ManualLayouts.read(project);
90
+ const snapshot = manualLayouts?.[viewId];
91
+ if (snapshot) {
92
+ logger.debug `found manual layout for ${viewId}`;
93
+ return {
94
+ diagram: {
95
+ ...snapshot,
96
+ _layout: 'manual',
97
+ drifts: snapshot.drifts
98
+ ? unique([
99
+ ...snapshot.drifts,
100
+ 'not-exists',
101
+ ])
102
+ : ['not-exists'],
103
+ },
104
+ dot: '# manual layout',
105
+ };
106
+ }
107
+ return null;
108
+ }
109
+ let cached = this.cache.get(view);
110
+ if (cached) {
111
+ logger.debug `layout ${viewId} from cache`;
112
+ return await Promise.resolve().then(() => cached);
113
+ }
114
+ try {
115
+ const m0 = performanceMark();
116
+ const result = await this.layouter.layout({
117
+ view,
118
+ styles: model.$styles,
119
+ });
120
+ logger.debug(`layout {viewId} ready in ${m0.pretty}`, { viewId });
121
+ return this.viewSucceed(view, model, result);
122
+ }
123
+ catch (e) {
124
+ const errMessage = loggable(e);
125
+ logger.warn(errMessage);
126
+ this.reportViewError(view, projectId, errMessage);
127
+ return Promise.reject(e);
128
+ }
129
+ }
130
+ async diagrams(projectId, cancelToken) {
131
+ const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
132
+ const layouted = await this._layoutAllViews(likeC4Model, cancelToken);
133
+ return layouted.map(({ diagram }) => {
134
+ const manualLayout = likeC4Model.$data.manualLayouts?.[diagram.id];
135
+ if (manualLayout) {
136
+ return applyManualLayout(diagram, manualLayout);
137
+ }
138
+ return diagram;
139
+ });
140
+ }
141
+ async viewsAsGraphvizOut(projectId, cancelToken) {
142
+ const KEY = 'All-LayoutedViews-DotWithSvg';
143
+ const cache = this.services.shared.workspace.Cache;
144
+ if (cache.has(KEY)) {
145
+ return await Promise.resolve(cache.get(KEY));
146
+ }
147
+ const likeC4Model = await this.ModelBuilder.computeModel(projectId, cancelToken);
148
+ const views = values(likeC4Model.$data.views);
149
+ if (views.length === 0) {
150
+ return [];
151
+ }
152
+ const tasks = views.map(async (view) => {
153
+ const { dot, svg } = await this.layouter.svg({
154
+ view,
155
+ styles: likeC4Model.$styles,
156
+ });
157
+ return {
158
+ id: view.id,
159
+ dot,
160
+ svg,
161
+ };
162
+ });
163
+ const succeed = [];
164
+ const settledResult = await Promise.allSettled(tasks);
165
+ for (const result of settledResult) {
166
+ if (result.status === 'fulfilled') {
167
+ succeed.push(result.value);
168
+ }
169
+ else {
170
+ logWarnError(result.reason);
171
+ }
172
+ }
173
+ cache.set(KEY, succeed);
174
+ return succeed;
175
+ }
176
+ /**
177
+ * Open a view in the preview panel.
178
+ */
179
+ async openView(viewId, projectId) {
180
+ await this.services.Rpc.openView({ viewId, projectId });
181
+ }
182
+ reportViewError(view, projectId, error) {
183
+ const key = `${projectId}-${view.id}`;
184
+ if (!this.viewsWithReportedErrors.has(key)) {
185
+ this.services.shared.lsp.Connection?.window.showErrorMessage(`LikeC4: ${error}`);
186
+ this.viewsWithReportedErrors.add(key);
187
+ }
188
+ }
189
+ viewSucceed(view, likec4model, result) {
190
+ const projectId = likec4model.project.id;
191
+ const key = `${projectId}-${view.id}`;
192
+ const snapshot = likec4model.$data.manualLayouts?.[view.id];
193
+ if (snapshot) {
194
+ result.diagram = applyLayoutDriftReasons(result.diagram, snapshot);
195
+ }
196
+ this.viewsWithReportedErrors.delete(key);
197
+ this.cache.set(view, result);
198
+ return result;
199
+ }
200
+ }
@@ -1 +1,4 @@
1
- export { DefaultLikeC4Views, type LikeC4Views } from './likec4-views';
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,15 @@
1
+ import { AstUtils, DefaultAstNodeDescriptionProvider, } from 'langium';
2
+ export class AstNodeDescriptionProvider extends DefaultAstNodeDescriptionProvider {
3
+ services;
4
+ constructor(services) {
5
+ super(services);
6
+ this.services = services;
7
+ }
8
+ createDescription(node, name, document) {
9
+ const doc = document ?? AstUtils.getDocument(node);
10
+ const description = super.createDescription(node, name, document);
11
+ doc.likec4ProjectId ??= this.services.shared.workspace.ProjectsManager.belongsTo(doc.uri);
12
+ description.likec4ProjectId = doc.likec4ProjectId;
13
+ return description;
14
+ }
15
+ }
@@ -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.uri);
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
+ }
@@ -5,7 +5,7 @@ import { type LikeC4LangiumDocument } from '../ast';
5
5
  import type { LikeC4SharedServices } from '../module';
6
6
  export declare class LangiumDocuments extends DefaultLangiumDocuments {
7
7
  protected services: LikeC4SharedServices;
8
- protected compare: any;
8
+ protected compare: (a: string | undefined, b: string | undefined) => number;
9
9
  constructor(services: LikeC4SharedServices);
10
10
  addDocument(document: LangiumDocument): void;
11
11
  /**
@@ -0,0 +1,58 @@
1
+ import { compareNaturalHierarchically } from '@likec4/core/utils';
2
+ import { DefaultLangiumDocuments } from 'langium';
3
+ import { groupBy, prop } from 'remeda';
4
+ import { isLikeC4LangiumDocument } from '../ast';
5
+ import { isLikeC4Builtin } from '../likec4lib';
6
+ /**
7
+ * Compare function for document paths to ensure consistent order
8
+ */
9
+ const compare = compareNaturalHierarchically('/', true);
10
+ const ensureOrder = (a, b) => compare(a.uri.path, b.uri.path);
11
+ export class LangiumDocuments extends DefaultLangiumDocuments {
12
+ services;
13
+ compare = compareNaturalHierarchically('/', true);
14
+ constructor(services) {
15
+ super(services);
16
+ this.services = services;
17
+ }
18
+ addDocument(document) {
19
+ const uriString = document.uri.toString();
20
+ if (this.documentMap.has(uriString)) {
21
+ throw new Error(`A document with the URI '${uriString}' is already present.`);
22
+ }
23
+ const docs = [...this.documentMap.values(), document].sort(ensureOrder);
24
+ // Clear and re-add documents to ensure consistent order
25
+ this.documentMap.clear();
26
+ for (const doc of docs) {
27
+ this.documentMap.set(doc.uri.toString(), doc);
28
+ }
29
+ }
30
+ /**
31
+ * Returns all user documents, excluding built-in documents.
32
+ */
33
+ get allExcludingBuiltin() {
34
+ const projects = this.services.workspace.ProjectsManager;
35
+ return super.all.filter((doc) => {
36
+ if (!isLikeC4LangiumDocument(doc) || isLikeC4Builtin(doc.uri)) {
37
+ return false;
38
+ }
39
+ doc.likec4ProjectId = projects.belongsTo(doc.uri);
40
+ return !projects.isExcluded(doc);
41
+ });
42
+ }
43
+ projectDocuments(projectId) {
44
+ return this.allExcludingBuiltin.filter(doc => doc.likec4ProjectId === projectId);
45
+ }
46
+ groupedByProject() {
47
+ return groupBy(this.allExcludingBuiltin.toArray(), prop('likec4ProjectId'));
48
+ }
49
+ resetProjectIds() {
50
+ const projects = this.services.workspace.ProjectsManager;
51
+ this.all.forEach(doc => {
52
+ if (!isLikeC4LangiumDocument(doc) || isLikeC4Builtin(doc.uri)) {
53
+ return;
54
+ }
55
+ doc.likec4ProjectId = projects.belongsTo(doc);
56
+ });
57
+ }
58
+ }
@@ -29,7 +29,7 @@ export declare class ProjectsManager {
29
29
  * The global project ID used for all documents
30
30
  * that are not part of a specific project.
31
31
  */
32
- static readonly DefaultProjectId: scalar.ProjectId;
32
+ static readonly DefaultProjectId: scalar.ProjectId<string>;
33
33
  constructor(services: LikeC4SharedServices);
34
34
  /**
35
35
  * Returns:
@@ -48,11 +48,15 @@ export declare class ProjectsManager {
48
48
  * If there are multiple projects and default project is not set, throws an error
49
49
  */
50
50
  ensureProjectId(projectId?: scalar.ProjectId | undefined): scalar.ProjectId;
51
+ /**
52
+ * Validates and ensures the project.
53
+ */
54
+ ensureProject(projectId?: scalar.ProjectId | undefined): Project;
51
55
  hasMultipleProjects(): boolean;
52
56
  /**
53
57
  * Checks if the specified document should be excluded from processing.
54
58
  */
55
- checkIfExcluded(document: LangiumDocument | URI | string): boolean;
59
+ isExcluded(document: LangiumDocument | URI | string): boolean;
56
60
  /**
57
61
  * Checks if it is a config file and it is not excluded by default exclude pattern
58
62
  *
@@ -80,10 +84,11 @@ export declare class ProjectsManager {
80
84
  private _registerProject;
81
85
  belongsTo(document: LangiumDocument | URI | string): scalar.ProjectId;
82
86
  reloadProjects(): Promise<void>;
87
+ protected _reloadProjects(): Promise<void>;
83
88
  protected uniqueProjectId(name: string): scalar.ProjectId;
84
89
  protected resetProjectIds(): void;
85
90
  protected rebuidDocuments(cancelToken?: Cancellation.CancellationToken): Promise<void>;
86
- protected findProjectForDocument(documentUri: string): any;
91
+ protected findProjectForDocument(documentUri: string): Pick<ProjectData, "id" | "exclude" | "config">;
87
92
  protected get mappingsToProject(): WorkspaceCache<string, Pick<ProjectData, 'id' | 'config' | 'exclude'>>;
88
93
  }
89
94
  export {};