@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.
Files changed (301) hide show
  1. package/browser/package.json +1 -1
  2. package/browser-worker/package.json +1 -1
  3. package/dist/LikeC4LanguageServices.d.ts +13 -34
  4. package/dist/LikeC4LanguageServices.js +152 -0
  5. package/dist/Rpc.js +257 -0
  6. package/dist/ast.d.ts +5 -1
  7. package/dist/ast.js +257 -0
  8. package/dist/browser-worker.js +4 -0
  9. package/dist/browser.js +35 -0
  10. package/dist/bundled.js +44 -0
  11. package/dist/bundled.mjs +3568 -3900
  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.d.ts +2 -0
  16. package/dist/filesystem/ChokidarWatcher.js +108 -0
  17. package/dist/filesystem/FileSystemWatcher.js +14 -0
  18. package/dist/filesystem/LikeC4FileSystem.d.ts +1 -2
  19. package/dist/filesystem/LikeC4FileSystem.js +126 -0
  20. package/dist/filesystem/index.d.ts +26 -0
  21. package/dist/filesystem/index.js +29 -0
  22. package/dist/formatting/LikeC4Formatter.js +637 -0
  23. package/dist/formatting/utils.js +18 -0
  24. package/dist/generated/ast.js +2155 -0
  25. package/dist/generated/{grammar.mjs → grammar.js} +6 -2
  26. package/dist/generated/module.d.ts +6 -1
  27. package/dist/generated/module.js +27 -0
  28. package/dist/generated-lib/{icons.mjs → icons.js} +11 -7
  29. package/dist/index.d.ts +10 -1
  30. package/dist/index.js +55 -0
  31. package/dist/{likec4lib.mjs → likec4lib.js} +3 -3
  32. package/dist/logger.js +81 -0
  33. package/dist/lsp/CodeActionProvider.d.ts +14 -0
  34. package/dist/lsp/CodeActionProvider.js +33 -0
  35. package/dist/lsp/CodeLensProvider.js +44 -0
  36. package/dist/lsp/CompletionProvider.d.ts +3 -1
  37. package/dist/lsp/CompletionProvider.js +200 -0
  38. package/dist/lsp/DocumentHighlightProvider.js +10 -0
  39. package/dist/lsp/DocumentLinkProvider.js +58 -0
  40. package/dist/lsp/DocumentSymbolProvider.js +306 -0
  41. package/dist/lsp/HoverProvider.js +106 -0
  42. package/dist/lsp/RenameProvider.js +6 -0
  43. package/dist/lsp/SemanticTokenProvider.js +257 -0
  44. package/dist/lsp/index.d.ts +1 -0
  45. package/dist/lsp/index.js +9 -0
  46. package/dist/mcp/MCPServerFactory.js +73 -0
  47. package/dist/mcp/NoopLikeC4MCPServer.js +17 -0
  48. package/dist/mcp/interfaces.js +5 -0
  49. package/dist/mcp/server/StdioLikeC4MCPServer.js +51 -0
  50. package/dist/mcp/server/StreamableLikeC4MCPServer.js +145 -0
  51. package/dist/mcp/server/WithMCPServer.js +56 -0
  52. package/dist/mcp/tools/_common.d.ts +8 -7
  53. package/dist/mcp/tools/_common.js +49 -0
  54. package/dist/mcp/tools/find-relationships.d.ts +7 -8
  55. package/dist/mcp/tools/find-relationships.js +150 -0
  56. package/dist/mcp/tools/list-projects.d.ts +3 -3
  57. package/dist/mcp/tools/list-projects.js +62 -0
  58. package/dist/mcp/tools/open-view.d.ts +6 -7
  59. package/dist/mcp/tools/open-view.js +52 -0
  60. package/dist/mcp/tools/read-deployment.d.ts +6 -7
  61. package/dist/mcp/tools/read-deployment.js +132 -0
  62. package/dist/mcp/tools/read-element.d.ts +6 -7
  63. package/dist/mcp/tools/read-element.js +194 -0
  64. package/dist/mcp/tools/read-project-summary.d.ts +5 -6
  65. package/dist/mcp/tools/read-project-summary.js +176 -0
  66. package/dist/mcp/tools/read-view.d.ts +6 -7
  67. package/dist/mcp/tools/read-view.js +203 -0
  68. package/dist/mcp/tools/search-element.d.ts +3 -3
  69. package/dist/mcp/tools/search-element.js +177 -0
  70. package/dist/mcp/utils.d.ts +2 -2
  71. package/dist/mcp/utils.js +48 -0
  72. package/dist/model/builder/MergedExtends.js +74 -0
  73. package/dist/model/builder/MergedSpecification.js +175 -0
  74. package/dist/model/builder/buildModel.js +176 -0
  75. package/dist/model/deployments-index.js +102 -0
  76. package/dist/model/fqn-index.d.ts +1 -2
  77. package/dist/model/fqn-index.js +247 -0
  78. package/dist/model/index.js +6 -0
  79. package/dist/model/model-builder.d.ts +13 -11
  80. package/dist/model/model-builder.js +232 -0
  81. package/dist/model/model-locator.d.ts +6 -5
  82. package/dist/model/model-locator.js +240 -0
  83. package/dist/model/model-parser-where.js +81 -0
  84. package/dist/model/model-parser.d.ts +309 -304
  85. package/dist/model/model-parser.js +126 -0
  86. package/dist/model/parser/Base.d.ts +2 -2
  87. package/dist/model/parser/Base.js +367 -0
  88. package/dist/model/parser/DeploymentModelParser.d.ts +2 -2
  89. package/dist/model/parser/DeploymentModelParser.js +176 -0
  90. package/dist/model/parser/DeploymentViewParser.d.ts +3 -3
  91. package/dist/model/parser/DeploymentViewParser.js +86 -0
  92. package/dist/model/parser/FqnRefParser.d.ts +2 -2
  93. package/dist/model/parser/FqnRefParser.js +382 -0
  94. package/dist/model/parser/GlobalsParser.d.ts +6 -6
  95. package/dist/model/parser/GlobalsParser.js +84 -0
  96. package/dist/model/parser/ImportsParser.d.ts +11 -12
  97. package/dist/model/parser/ImportsParser.js +24 -0
  98. package/dist/model/parser/ModelParser.d.ts +2 -2
  99. package/dist/model/parser/ModelParser.js +165 -0
  100. package/dist/model/parser/PredicatesParser.d.ts +2 -2
  101. package/dist/model/parser/PredicatesParser.js +45 -0
  102. package/dist/model/parser/SpecificationParser.d.ts +2 -2
  103. package/dist/model/parser/SpecificationParser.js +113 -0
  104. package/dist/model/parser/ValueConverter.js +12 -0
  105. package/dist/model/parser/ViewsParser.d.ts +3 -3
  106. package/dist/model/parser/ViewsParser.js +479 -0
  107. package/dist/model-change/ModelChanges.d.ts +8 -5
  108. package/dist/model-change/ModelChanges.js +129 -0
  109. package/dist/model-change/changeElementStyle.js +134 -0
  110. package/dist/model-change/changeViewLayout.d.ts +2 -2
  111. package/dist/model-change/changeViewLayout.js +28 -0
  112. package/dist/model-change/removeManualLayoutV1.d.ts +7 -0
  113. package/dist/model-change/removeManualLayoutV1.js +27 -0
  114. package/dist/module.d.ts +10 -5
  115. package/dist/module.js +143 -0
  116. package/dist/protocol.d.ts +34 -27
  117. package/dist/protocol.js +123 -0
  118. package/dist/references/index.js +3 -0
  119. package/dist/references/name-provider.js +37 -0
  120. package/dist/references/scope-computation.js +288 -0
  121. package/dist/references/scope-provider.d.ts +3 -3
  122. package/dist/references/scope-provider.js +242 -0
  123. package/dist/shared/NodeKindProvider.js +57 -0
  124. package/dist/shared/{WorkspaceSymbolProvider.mjs → WorkspaceSymbolProvider.js} +1 -1
  125. package/dist/shared/index.js +2 -0
  126. package/dist/test/index.js +1 -0
  127. package/dist/test/testServices.d.ts +16 -16
  128. package/dist/test/testServices.js +210 -0
  129. package/dist/utils/disposable.js +26 -0
  130. package/dist/utils/elementRef.d.ts +1 -1
  131. package/dist/utils/elementRef.js +27 -0
  132. package/dist/utils/fqnRef.js +63 -0
  133. package/dist/utils/index.js +35 -0
  134. package/dist/utils/printDocs.js +1 -0
  135. package/dist/utils/projectId.js +16 -0
  136. package/dist/utils/stringHash.js +5 -0
  137. package/dist/validation/DocumentValidator.js +17 -0
  138. package/dist/validation/_shared.js +26 -0
  139. package/dist/validation/deployment-checks.js +140 -0
  140. package/dist/validation/dynamic-view.js +67 -0
  141. package/dist/validation/element-ref.js +12 -0
  142. package/dist/validation/element.js +49 -0
  143. package/dist/validation/imports.js +46 -0
  144. package/dist/validation/index.d.ts +1 -1
  145. package/dist/validation/index.js +157 -0
  146. package/dist/validation/property-checks.js +108 -0
  147. package/dist/validation/relation.js +55 -0
  148. package/dist/validation/specification.js +190 -0
  149. package/dist/validation/view-predicates/fqn-expr-with.js +43 -0
  150. package/dist/validation/view-predicates/fqn-ref-expr.js +51 -0
  151. package/dist/validation/view-predicates/incoming.js +16 -0
  152. package/dist/validation/view-predicates/index.js +6 -0
  153. package/dist/validation/view-predicates/outgoing.js +20 -0
  154. package/dist/validation/view-predicates/relation-expr.js +46 -0
  155. package/dist/validation/view-predicates/relation-with.js +16 -0
  156. package/dist/validation/view.d.ts +1 -1
  157. package/dist/validation/view.js +42 -0
  158. package/dist/view-utils/assignNavigateTo.js +27 -0
  159. package/dist/view-utils/index.d.ts +1 -0
  160. package/dist/view-utils/index.js +2 -0
  161. package/dist/view-utils/manual-layout.d.ts +6 -0
  162. package/dist/view-utils/manual-layout.js +149 -0
  163. package/dist/views/ConfigurableLayouter.js +51 -0
  164. package/dist/views/LikeC4ManualLayouts.d.ts +42 -0
  165. package/dist/views/LikeC4ManualLayouts.js +209 -0
  166. package/dist/views/{likec4-views.d.ts → LikeC4Views.d.ts} +32 -10
  167. package/dist/views/LikeC4Views.js +216 -0
  168. package/dist/views/index.d.ts +4 -1
  169. package/dist/views/index.js +11 -0
  170. package/dist/workspace/AstNodeDescriptionProvider.js +18 -0
  171. package/dist/workspace/IndexManager.js +21 -0
  172. package/dist/workspace/LangiumDocuments.d.ts +4 -3
  173. package/dist/workspace/LangiumDocuments.js +72 -0
  174. package/dist/workspace/ProjectsManager.d.ts +25 -16
  175. package/dist/workspace/ProjectsManager.js +469 -0
  176. package/dist/workspace/WorkspaceManager.d.ts +3 -2
  177. package/dist/workspace/WorkspaceManager.js +98 -0
  178. package/dist/workspace/index.js +5 -0
  179. package/likec4lib/package.json +1 -1
  180. package/package.json +30 -28
  181. package/protocol/package.json +1 -1
  182. package/dist/LikeC4LanguageServices.mjs +0 -197
  183. package/dist/Rpc.mjs +0 -296
  184. package/dist/ast.mjs +0 -221
  185. package/dist/browser-worker.mjs +0 -2
  186. package/dist/browser.mjs +0 -32
  187. package/dist/documentation/documentation-provider.mjs +0 -48
  188. package/dist/documentation/index.mjs +0 -1
  189. package/dist/empty.mjs +0 -1
  190. package/dist/filesystem/ChokidarWatcher.mjs +0 -68
  191. package/dist/filesystem/FileSystemWatcher.mjs +0 -11
  192. package/dist/filesystem/LikeC4FileSystem.mjs +0 -64
  193. package/dist/filesystem/index.mjs +0 -19
  194. package/dist/formatting/LikeC4Formatter.mjs +0 -511
  195. package/dist/formatting/utils.mjs +0 -15
  196. package/dist/generated/ast.mjs +0 -2150
  197. package/dist/generated/module.mjs +0 -23
  198. package/dist/index.mjs +0 -50
  199. package/dist/logger.mjs +0 -82
  200. package/dist/lsp/CodeLensProvider.mjs +0 -42
  201. package/dist/lsp/CompletionProvider.mjs +0 -208
  202. package/dist/lsp/DocumentHighlightProvider.mjs +0 -10
  203. package/dist/lsp/DocumentLinkProvider.mjs +0 -53
  204. package/dist/lsp/DocumentSymbolProvider.mjs +0 -287
  205. package/dist/lsp/HoverProvider.mjs +0 -104
  206. package/dist/lsp/RenameProvider.mjs +0 -6
  207. package/dist/lsp/SemanticTokenProvider.mjs +0 -276
  208. package/dist/lsp/index.mjs +0 -7
  209. package/dist/mcp/MCPServerFactory.mjs +0 -70
  210. package/dist/mcp/NoopLikeC4MCPServer.mjs +0 -17
  211. package/dist/mcp/interfaces.mjs +0 -4
  212. package/dist/mcp/server/StdioLikeC4MCPServer.mjs +0 -46
  213. package/dist/mcp/server/StreamableLikeC4MCPServer.mjs +0 -153
  214. package/dist/mcp/server/WithMCPServer.mjs +0 -58
  215. package/dist/mcp/tools/_common.mjs +0 -42
  216. package/dist/mcp/tools/find-relationships.mjs +0 -151
  217. package/dist/mcp/tools/list-projects.mjs +0 -62
  218. package/dist/mcp/tools/open-view.mjs +0 -52
  219. package/dist/mcp/tools/read-deployment.mjs +0 -130
  220. package/dist/mcp/tools/read-element.mjs +0 -198
  221. package/dist/mcp/tools/read-project-summary.mjs +0 -178
  222. package/dist/mcp/tools/read-view.mjs +0 -205
  223. package/dist/mcp/tools/search-element.mjs +0 -171
  224. package/dist/mcp/utils.mjs +0 -47
  225. package/dist/model/builder/MergedExtends.mjs +0 -76
  226. package/dist/model/builder/MergedSpecification.mjs +0 -205
  227. package/dist/model/builder/assignTagColors.d.ts +0 -7
  228. package/dist/model/builder/assignTagColors.mjs +0 -51
  229. package/dist/model/builder/buildModel.mjs +0 -226
  230. package/dist/model/deployments-index.mjs +0 -100
  231. package/dist/model/fqn-index.mjs +0 -243
  232. package/dist/model/index.mjs +0 -6
  233. package/dist/model/model-builder.mjs +0 -285
  234. package/dist/model/model-locator.mjs +0 -239
  235. package/dist/model/model-parser-where.mjs +0 -81
  236. package/dist/model/model-parser.mjs +0 -127
  237. package/dist/model/parser/Base.mjs +0 -376
  238. package/dist/model/parser/DeploymentModelParser.mjs +0 -212
  239. package/dist/model/parser/DeploymentViewParser.mjs +0 -95
  240. package/dist/model/parser/FqnRefParser.mjs +0 -398
  241. package/dist/model/parser/GlobalsParser.mjs +0 -82
  242. package/dist/model/parser/ImportsParser.mjs +0 -28
  243. package/dist/model/parser/ModelParser.mjs +0 -190
  244. package/dist/model/parser/PredicatesParser.mjs +0 -45
  245. package/dist/model/parser/SpecificationParser.mjs +0 -120
  246. package/dist/model/parser/ValueConverter.mjs +0 -12
  247. package/dist/model/parser/ViewsParser.mjs +0 -490
  248. package/dist/model-change/ModelChanges.mjs +0 -89
  249. package/dist/model-change/changeElementStyle.mjs +0 -143
  250. package/dist/model-change/changeViewLayout.mjs +0 -32
  251. package/dist/model-change/saveManualLayout.d.ts +0 -11
  252. package/dist/model-change/saveManualLayout.mjs +0 -27
  253. package/dist/module.mjs +0 -180
  254. package/dist/protocol.mjs +0 -65
  255. package/dist/references/index.mjs +0 -3
  256. package/dist/references/name-provider.mjs +0 -39
  257. package/dist/references/scope-computation.mjs +0 -312
  258. package/dist/references/scope-provider.mjs +0 -239
  259. package/dist/shared/NodeKindProvider.mjs +0 -110
  260. package/dist/shared/index.mjs +0 -2
  261. package/dist/test/index.mjs +0 -1
  262. package/dist/test/testServices.mjs +0 -200
  263. package/dist/utils/disposable.mjs +0 -25
  264. package/dist/utils/elementRef.mjs +0 -20
  265. package/dist/utils/fqnRef.mjs +0 -57
  266. package/dist/utils/index.mjs +0 -33
  267. package/dist/utils/printDocs.mjs +0 -1
  268. package/dist/utils/projectId.mjs +0 -16
  269. package/dist/utils/stringHash.mjs +0 -5
  270. package/dist/validation/DocumentValidator.mjs +0 -16
  271. package/dist/validation/_shared.mjs +0 -25
  272. package/dist/validation/deployment-checks.mjs +0 -146
  273. package/dist/validation/dynamic-view.mjs +0 -67
  274. package/dist/validation/element-ref.mjs +0 -12
  275. package/dist/validation/element.mjs +0 -50
  276. package/dist/validation/imports.mjs +0 -25
  277. package/dist/validation/index.mjs +0 -180
  278. package/dist/validation/property-checks.mjs +0 -107
  279. package/dist/validation/relation.mjs +0 -53
  280. package/dist/validation/specification.mjs +0 -173
  281. package/dist/validation/view-predicates/fqn-expr-with.mjs +0 -43
  282. package/dist/validation/view-predicates/fqn-ref-expr.mjs +0 -53
  283. package/dist/validation/view-predicates/incoming.mjs +0 -16
  284. package/dist/validation/view-predicates/index.mjs +0 -6
  285. package/dist/validation/view-predicates/outgoing.mjs +0 -20
  286. package/dist/validation/view-predicates/relation-expr.mjs +0 -39
  287. package/dist/validation/view-predicates/relation-with.mjs +0 -16
  288. package/dist/validation/view.mjs +0 -25
  289. package/dist/view-utils/assignNavigateTo.mjs +0 -25
  290. package/dist/view-utils/index.mjs +0 -1
  291. package/dist/view-utils/manual-layout.mjs +0 -99
  292. package/dist/views/configurable-layouter.mjs +0 -51
  293. package/dist/views/index.mjs +0 -1
  294. package/dist/views/likec4-views.mjs +0 -166
  295. package/dist/workspace/AstNodeDescriptionProvider.mjs +0 -17
  296. package/dist/workspace/IndexManager.mjs +0 -17
  297. package/dist/workspace/LangiumDocuments.mjs +0 -53
  298. package/dist/workspace/ProjectsManager.mjs +0 -360
  299. package/dist/workspace/WorkspaceManager.mjs +0 -83
  300. package/dist/workspace/index.mjs +0 -5
  301. /package/dist/views/{configurable-layouter.d.ts → ConfigurableLayouter.d.ts} +0 -0
@@ -0,0 +1,479 @@
1
+ import * as c4 from '@likec4/core';
2
+ import { invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core';
3
+ import { loggable } from '@likec4/log';
4
+ import { filter, find, isDefined, isEmpty, isNonNullish, isNumber, isTruthy, last, mapToObj, pipe } from 'remeda';
5
+ import { ast, parseMarkdownAsString, toAutoLayout, toColor, ViewOps, } from '../../ast';
6
+ import { logger as mainLogger } from '../../logger';
7
+ import { stringHash } from '../../utils';
8
+ import { elementRef } from '../../utils/elementRef';
9
+ import { parseViewManualLayout } from '../../view-utils/manual-layout';
10
+ import { removeIndent, toSingleLine } from './Base';
11
+ const logger = mainLogger.getChild('ViewsParser');
12
+ export function ViewsParser(B) {
13
+ return class ViewsParser extends B {
14
+ parseViews() {
15
+ const isValid = this.isValid;
16
+ for (const viewBlock of this.doc.parseResult.value.views) {
17
+ const localStyles = viewBlock.styles.flatMap(s => {
18
+ try {
19
+ return isValid(s) ? this.parseViewRuleStyleOrGlobalRef(s) : [];
20
+ }
21
+ catch (e) {
22
+ logger.warn(loggable(e));
23
+ return [];
24
+ }
25
+ });
26
+ // Common folder for all views in the block
27
+ const folder = viewBlock.folder && !isEmpty(viewBlock.folder.trim()) ? toSingleLine(viewBlock.folder) : null;
28
+ for (const view of viewBlock.views) {
29
+ try {
30
+ if (!isValid(view)) {
31
+ continue;
32
+ }
33
+ switch (true) {
34
+ case ast.isElementView(view):
35
+ this.doc.c4Views.push(this.parseElementView(view, localStyles));
36
+ break;
37
+ case ast.isDynamicView(view):
38
+ this.doc.c4Views.push(this.parseDynamicElementView(view, localStyles));
39
+ break;
40
+ case ast.isDeploymentView(view):
41
+ this.doc.c4Views.push(this.parseDeploymentView(view));
42
+ break;
43
+ default:
44
+ nonexhaustive(view);
45
+ }
46
+ if (folder) {
47
+ const view = this.doc.c4Views.at(-1);
48
+ view.title = folder + ' / ' + (view.title || view.id);
49
+ }
50
+ }
51
+ catch (e) {
52
+ logger.warn(loggable(e));
53
+ }
54
+ }
55
+ }
56
+ }
57
+ parseElementView(astNode, additionalStyles) {
58
+ const body = astNode.body;
59
+ invariant(body, 'ElementView body is not defined');
60
+ const astPath = this.getAstNodePath(astNode);
61
+ let viewOf = null;
62
+ if ('viewOf' in astNode) {
63
+ const viewOfEl = elementRef(astNode.viewOf);
64
+ const _viewOf = viewOfEl && this.resolveFqn(viewOfEl);
65
+ if (!_viewOf) {
66
+ const viewId = astNode.name ?? 'unnamed';
67
+ const msg = astNode.viewOf.$cstNode?.text ?? '<unknown>';
68
+ logger.warn(`viewOf {viewId} not resolved {msg}`, { msg, viewId });
69
+ }
70
+ else {
71
+ viewOf = _viewOf;
72
+ }
73
+ }
74
+ let id = astNode.name;
75
+ if (!id) {
76
+ id = 'view_' + stringHash(this.doc.uri.toString(), astPath, viewOf ?? '');
77
+ }
78
+ const { title = null, description = null } = this.parseBaseProps(pipe(body.props, filter(p => this.isValid(p)), filter(ast.isViewStringProperty), mapToObj(p => [p.key, p.value])));
79
+ const tags = this.convertTags(body);
80
+ const links = this.convertLinks(body);
81
+ const manualLayout = parseViewManualLayout(astNode);
82
+ const view = {
83
+ [c4._type]: 'element',
84
+ id: id,
85
+ astPath,
86
+ title: toSingleLine(title) ?? null,
87
+ description,
88
+ tags,
89
+ links: isNonEmptyArray(links) ? links : null,
90
+ rules: [
91
+ ...additionalStyles,
92
+ ...body.rules.flatMap(n => {
93
+ try {
94
+ return this.isValid(n) ? this.parseElementViewRule(n) : [];
95
+ }
96
+ catch (e) {
97
+ logger.warn(loggable(e));
98
+ return [];
99
+ }
100
+ }),
101
+ ],
102
+ ...(viewOf && { viewOf }),
103
+ ...(manualLayout && { manualLayout }),
104
+ };
105
+ ViewOps.writeId(astNode, view.id);
106
+ if ('extends' in astNode) {
107
+ const extendsView = astNode.extends.view.ref;
108
+ invariant(extendsView?.name, 'view extends is not resolved: ' + astNode.$cstNode?.text);
109
+ return Object.assign(view, {
110
+ extends: extendsView.name,
111
+ });
112
+ }
113
+ return view;
114
+ }
115
+ parseElementViewRule(astRule) {
116
+ if (ast.isViewRulePredicate(astRule)) {
117
+ return this.parseViewRulePredicate(astRule);
118
+ }
119
+ if (ast.isViewRuleGlobalPredicateRef(astRule)) {
120
+ return this.parseViewRuleGlobalPredicateRef(astRule);
121
+ }
122
+ if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
123
+ return this.parseViewRuleStyleOrGlobalRef(astRule);
124
+ }
125
+ if (ast.isViewRuleAutoLayout(astRule)) {
126
+ return toAutoLayout(astRule);
127
+ }
128
+ if (ast.isViewRuleGroup(astRule)) {
129
+ return this.parseViewRuleGroup(astRule);
130
+ }
131
+ nonexhaustive(astRule);
132
+ }
133
+ parseViewRulePredicate(astNode) {
134
+ const exprs = [];
135
+ let predicate = astNode.exprs;
136
+ while (predicate) {
137
+ const { value, prev } = predicate;
138
+ try {
139
+ if (isTruthy(value) && this.isValid(value)) {
140
+ const expr = this.parsePredicate(value);
141
+ exprs.unshift(expr);
142
+ }
143
+ }
144
+ catch (e) {
145
+ logger.warn(loggable(e));
146
+ }
147
+ if (!prev) {
148
+ break;
149
+ }
150
+ predicate = prev;
151
+ }
152
+ return astNode.isInclude ? { include: exprs } : { exclude: exprs };
153
+ }
154
+ parseViewRuleGlobalPredicateRef(astRule) {
155
+ return {
156
+ predicateId: astRule.predicate.$refText,
157
+ };
158
+ }
159
+ parseViewRuleStyleOrGlobalRef(astRule) {
160
+ if (ast.isViewRuleStyle(astRule)) {
161
+ return this.parseViewRuleStyle(astRule);
162
+ }
163
+ if (ast.isViewRuleGlobalStyle(astRule)) {
164
+ return this.parseViewRuleGlobalStyle(astRule);
165
+ }
166
+ nonexhaustive(astRule);
167
+ }
168
+ parseViewRuleGroup(astNode) {
169
+ const groupRules = [];
170
+ for (const rule of astNode.groupRules) {
171
+ try {
172
+ if (!this.isValid(rule)) {
173
+ continue;
174
+ }
175
+ if (ast.isViewRulePredicate(rule)) {
176
+ groupRules.push(this.parseViewRulePredicate(rule));
177
+ continue;
178
+ }
179
+ if (ast.isViewRuleGroup(rule)) {
180
+ groupRules.push(this.parseViewRuleGroup(rule));
181
+ continue;
182
+ }
183
+ nonexhaustive(rule);
184
+ }
185
+ catch (e) {
186
+ logger.warn(loggable(e));
187
+ }
188
+ }
189
+ return {
190
+ title: toSingleLine(astNode.title) ?? null,
191
+ groupRules,
192
+ ...this.parseStyleProps(astNode.props),
193
+ };
194
+ }
195
+ parseViewRuleStyle(astRule) {
196
+ const targets = this.parseFqnExpressions(astRule.targets).filter((e) => c4.ModelExpression.isFqnExpr(e));
197
+ const style = this.parseStyleProps(astRule.props.filter(ast.isStyleProperty));
198
+ const notation = removeIndent(parseMarkdownAsString(astRule.props.find(ast.isNotationProperty)?.value));
199
+ return {
200
+ targets,
201
+ style,
202
+ ...(notation && { notation }),
203
+ };
204
+ }
205
+ parseViewRuleGlobalStyle(astRule) {
206
+ return {
207
+ styleId: astRule.style.$refText,
208
+ };
209
+ }
210
+ parseDynamicElementView(astNode, additionalStyles) {
211
+ const body = astNode.body;
212
+ invariant(body, 'DynamicElementView body is not defined');
213
+ // only valid props
214
+ const isValid = this.isValid;
215
+ const props = body.props.filter(isValid);
216
+ const astPath = this.getAstNodePath(astNode);
217
+ let id = astNode.name;
218
+ if (!id) {
219
+ id = 'dynamic_' + stringHash(this.doc.uri.toString(), astPath);
220
+ }
221
+ const { title = null, description = null } = this.parseBaseProps(pipe(props, filter(ast.isViewStringProperty), mapToObj(p => [p.key, p.value])));
222
+ const tags = this.convertTags(body);
223
+ const links = this.convertLinks(body);
224
+ ViewOps.writeId(astNode, id);
225
+ const manualLayout = parseViewManualLayout(astNode);
226
+ const variant = find(props, ast.isDynamicViewDisplayVariantProperty)?.value;
227
+ return {
228
+ [c4._type]: 'dynamic',
229
+ id: id,
230
+ astPath,
231
+ title: toSingleLine(title) ?? null,
232
+ description,
233
+ tags,
234
+ links: isNonEmptyArray(links) ? links : null,
235
+ variant,
236
+ rules: [
237
+ ...additionalStyles,
238
+ ...body.rules.flatMap(n => {
239
+ try {
240
+ return isValid(n) ? this.parseDynamicViewRule(n) : [];
241
+ }
242
+ catch (e) {
243
+ logger.warn(loggable(e));
244
+ return [];
245
+ }
246
+ }, []),
247
+ ],
248
+ steps: body.steps.reduce((acc, n) => {
249
+ try {
250
+ if (isValid(n)) {
251
+ if (ast.isDynamicViewParallelSteps(n)) {
252
+ acc.push(this.parseDynamicParallelSteps(n));
253
+ }
254
+ else {
255
+ acc.push(this.parseDynamicStep(n));
256
+ }
257
+ }
258
+ }
259
+ catch (e) {
260
+ logger.warn(loggable(e));
261
+ }
262
+ return acc;
263
+ }, []),
264
+ ...(manualLayout && { manualLayout }),
265
+ };
266
+ }
267
+ parseDynamicViewRule(astRule) {
268
+ if (ast.isDynamicViewIncludePredicate(astRule)) {
269
+ return this.parseDynamicViewIncludePredicate(astRule);
270
+ }
271
+ if (ast.isDynamicViewGlobalPredicateRef(astRule)) {
272
+ return this.parseViewRuleGlobalPredicateRef(astRule);
273
+ }
274
+ if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
275
+ return this.parseViewRuleStyleOrGlobalRef(astRule);
276
+ }
277
+ if (ast.isViewRuleAutoLayout(astRule)) {
278
+ return toAutoLayout(astRule);
279
+ }
280
+ nonexhaustive(astRule);
281
+ }
282
+ parseDynamicViewIncludePredicate(astRule) {
283
+ const include = [];
284
+ let iter = astRule.exprs;
285
+ while (iter) {
286
+ try {
287
+ if (isNonNullish(iter.value) && this.isValid(iter.value)) {
288
+ if (ast.isFqnExprOrWith(iter.value)) {
289
+ const c4expr = this.parseElementPredicate(iter.value);
290
+ include.unshift(c4expr);
291
+ }
292
+ }
293
+ }
294
+ catch (e) {
295
+ logger.warn(loggable(e));
296
+ }
297
+ iter = iter.prev;
298
+ }
299
+ return { include };
300
+ }
301
+ parseDynamicParallelSteps(node) {
302
+ const parallelId = pathInsideDynamicView(node);
303
+ const __parallel = node.steps.map(step => this.parseDynamicStep(step));
304
+ invariant(isNonEmptyArray(__parallel), 'Dynamic parallel steps must have at least one step');
305
+ return {
306
+ parallelId,
307
+ __parallel,
308
+ };
309
+ }
310
+ /**
311
+ * @returns non-empty array in case of step chain A -> B -> C
312
+ */
313
+ parseDynamicStep(node) {
314
+ if (ast.isDynamicStepSingle(node)) {
315
+ invariant(this.isValid(node));
316
+ return this.parseDynamicStepSingle(node);
317
+ }
318
+ const __series = this.recursiveParseDynamicStepChain(node);
319
+ invariant(isNonEmptyArray(__series), 'Dynamic step chain must have at least one step');
320
+ return {
321
+ seriesId: pathInsideDynamicView(node),
322
+ __series,
323
+ };
324
+ }
325
+ recursiveParseDynamicStepChain(node, callstack) {
326
+ if (ast.isDynamicStepSingle(node.source)) {
327
+ if (!this.isValid(node.source)) {
328
+ return [];
329
+ }
330
+ const previous = this.parseDynamicStepSingle(node.source);
331
+ // Head of the chain cannot be backward
332
+ if (previous.isBackward) {
333
+ return [];
334
+ }
335
+ const thisStep = {
336
+ ...this.parseAbstractDynamicStep(node),
337
+ source: previous.target,
338
+ };
339
+ // if target is the same as source of previous step, then it is a backward step
340
+ // A -> B -> A
341
+ if (thisStep.target === previous.source) {
342
+ thisStep.isBackward = true;
343
+ }
344
+ else if (callstack) {
345
+ callstack.push([previous.source, previous.target]);
346
+ callstack.push([thisStep.source, thisStep.target]);
347
+ }
348
+ return [previous, thisStep];
349
+ }
350
+ callstack ??= [];
351
+ const allprevious = this.recursiveParseDynamicStepChain(node.source, callstack);
352
+ if (!isNonEmptyArray(allprevious) || !this.isValid(node)) {
353
+ return [];
354
+ }
355
+ const previous = last(allprevious);
356
+ const thisStep = {
357
+ ...this.parseAbstractDynamicStep(node),
358
+ source: previous.target,
359
+ };
360
+ const index = callstack.findIndex(([source, target]) => source === thisStep.target && target === thisStep.source);
361
+ if (index !== -1) {
362
+ thisStep.isBackward = true;
363
+ callstack.splice(index, callstack.length - index);
364
+ }
365
+ else {
366
+ callstack.push([thisStep.source, thisStep.target]);
367
+ }
368
+ return [...allprevious, thisStep];
369
+ }
370
+ parseDynamicStepSingle(node) {
371
+ const sourceEl = elementRef(node.source);
372
+ if (!sourceEl) {
373
+ throw new Error('Invalid reference to source');
374
+ }
375
+ let baseStep = {
376
+ ...this.parseAbstractDynamicStep(node),
377
+ source: this.resolveFqn(sourceEl),
378
+ };
379
+ if (node.isBackward) {
380
+ baseStep = {
381
+ ...baseStep,
382
+ source: baseStep.target,
383
+ target: baseStep.source,
384
+ isBackward: true,
385
+ };
386
+ }
387
+ return baseStep;
388
+ }
389
+ parseAbstractDynamicStep(astnode) {
390
+ const targetEl = elementRef(astnode.target);
391
+ if (!targetEl) {
392
+ throw new Error('Invalid reference to target');
393
+ }
394
+ const step = {
395
+ target: this.resolveFqn(targetEl),
396
+ astPath: pathInsideDynamicView(astnode),
397
+ };
398
+ const title = removeIndent(astnode.title);
399
+ if (title) {
400
+ step.title = title;
401
+ }
402
+ const kind = astnode.kind?.ref?.name ?? astnode.dotKind?.kind.ref?.name;
403
+ if (kind) {
404
+ step.kind = kind;
405
+ }
406
+ for (const prop of astnode.custom?.props ?? []) {
407
+ try {
408
+ switch (true) {
409
+ case ast.isRelationNavigateToProperty(prop): {
410
+ const viewId = prop.value.view.ref?.name;
411
+ if (isTruthy(viewId)) {
412
+ step.navigateTo = viewId;
413
+ }
414
+ break;
415
+ }
416
+ case ast.isRelationStringProperty(prop):
417
+ case ast.isNotationProperty(prop): {
418
+ if (isDefined(prop.value)) {
419
+ if (prop.key === 'description') {
420
+ const value = removeIndent(prop.value);
421
+ if (value) {
422
+ step.description = value;
423
+ }
424
+ }
425
+ else {
426
+ step[prop.key] = removeIndent(parseMarkdownAsString(prop.value)) ?? '';
427
+ }
428
+ }
429
+ break;
430
+ }
431
+ case ast.isNotesProperty(prop): {
432
+ if (isDefined(prop.value)) {
433
+ step[prop.key] = removeIndent(prop.value);
434
+ }
435
+ break;
436
+ }
437
+ case ast.isArrowProperty(prop): {
438
+ if (isDefined(prop.value)) {
439
+ step[prop.key] = prop.value;
440
+ }
441
+ break;
442
+ }
443
+ case ast.isColorProperty(prop): {
444
+ const value = toColor(prop);
445
+ if (isDefined(value)) {
446
+ step[prop.key] = value;
447
+ }
448
+ break;
449
+ }
450
+ case ast.isLineProperty(prop): {
451
+ if (isDefined(prop.value)) {
452
+ step[prop.key] = prop.value;
453
+ }
454
+ break;
455
+ }
456
+ default:
457
+ nonexhaustive(prop);
458
+ }
459
+ }
460
+ catch (e) {
461
+ logger.warn(loggable(e));
462
+ }
463
+ }
464
+ return step;
465
+ }
466
+ };
467
+ }
468
+ function pathInsideDynamicView(_node) {
469
+ let node = _node;
470
+ let path = [];
471
+ while (!ast.isDynamicViewBody(node)) {
472
+ if (isNumber(node.$containerIndex)) {
473
+ path.unshift(`@${node.$containerIndex}`);
474
+ }
475
+ path.unshift(`/${node.$containerProperty ?? '__invalid__'}`);
476
+ node = node.$container;
477
+ }
478
+ return path.join('');
479
+ }
@@ -1,14 +1,17 @@
1
- import { Location, Range, TextEdit } from 'vscode-languageserver-types';
2
- import type { ParsedLikeC4LangiumDocument } from '../ast';
1
+ import { type ViewChange } from '@likec4/core';
2
+ import { Range, TextEdit } from 'vscode-languageserver-types';
3
+ import type { ViewLocateResult } from '../model';
3
4
  import type { LikeC4Services } from '../module';
4
5
  import type { ChangeView } from '../protocol';
5
6
  export declare class LikeC4ModelChanges {
6
7
  private services;
7
8
  private locator;
8
9
  constructor(services: LikeC4Services);
9
- applyChange(changeView: ChangeView.Params): Promise<Location | null>;
10
- protected convertToTextEdit({ viewId, projectId, change }: ChangeView.Params): {
11
- doc: ParsedLikeC4LangiumDocument;
10
+ applyChange(changeView: ChangeView.Params): Promise<ChangeView.Res>;
11
+ protected convertToTextEdit({ lookup, change }: {
12
+ lookup: ViewLocateResult;
13
+ change: Exclude<ViewChange, ViewChange.SaveViewSnapshot | ViewChange.ResetManualLayout>;
14
+ }): {
12
15
  modifiedRange: Range;
13
16
  edits: TextEdit[];
14
17
  };
@@ -0,0 +1,129 @@
1
+ import { invariant, nonexhaustive } from '@likec4/core';
2
+ import { loggable, wrapError } from '@likec4/log';
3
+ import { Range, TextEdit } from 'vscode-languageserver-types';
4
+ import { logger as mainLogger } from '../logger';
5
+ import { changeElementStyle } from './changeElementStyle';
6
+ import { changeViewLayout } from './changeViewLayout';
7
+ import { removeManualLayoutV1 } from './removeManualLayoutV1';
8
+ const logger = mainLogger.getChild('model-changes');
9
+ export class LikeC4ModelChanges {
10
+ services;
11
+ locator;
12
+ constructor(services) {
13
+ this.services = services;
14
+ this.locator = services.likec4.ModelLocator;
15
+ }
16
+ async applyChange(changeView) {
17
+ const lspConnection = this.services.shared.lsp.Connection;
18
+ invariant(lspConnection, 'LSP Connection not available');
19
+ let result = null;
20
+ try {
21
+ await this.services.shared.workspace.WorkspaceLock.write(async () => {
22
+ let { viewId, projectId: _projectId, change } = changeView;
23
+ const project = this.services.shared.workspace.ProjectsManager.ensureProject(_projectId);
24
+ logger.debug `Applying model change ${change.op} to view ${viewId} in project ${project.id}`;
25
+ const lookup = this.locator.locateViewAst(viewId, project.id);
26
+ if (!lookup) {
27
+ throw new Error(`View ${viewId} not found in project ${project.id}`);
28
+ }
29
+ const textDocument = {
30
+ uri: lookup.doc.textDocument.uri,
31
+ version: lookup.doc.textDocument.version,
32
+ };
33
+ // TODO refactor to use separate methods for save/reset operations
34
+ if (change.op === 'save-view-snapshot') {
35
+ invariant(viewId === change.layout.id, 'View ID does not match');
36
+ // If there is an existing manual layout v1
37
+ if (lookup.view.manualLayout) {
38
+ // We clean it up
39
+ await removeManualLayoutV1(this.services, { lookup }).catch(err => {
40
+ logger.warn(`Failed to remove manual layout v1 for view ${viewId} in project ${project.id}`, { err });
41
+ });
42
+ }
43
+ const location = await this.services.likec4.ManualLayouts.write(project, change.layout);
44
+ result = {
45
+ success: true,
46
+ location,
47
+ };
48
+ return;
49
+ }
50
+ if (change.op === 'reset-manual-layout') {
51
+ // If there is an existing manual layout v1
52
+ if (lookup.view.manualLayout) {
53
+ // We clean it up
54
+ await removeManualLayoutV1(this.services, { lookup }).catch(err => {
55
+ logger.warn(`Failed to remove manual layout v1 for view ${viewId} in project ${project.id}`, { err });
56
+ });
57
+ }
58
+ const location = await this.services.likec4.ManualLayouts.remove(project, viewId);
59
+ result = {
60
+ success: true,
61
+ location,
62
+ };
63
+ return;
64
+ }
65
+ const { edits, modifiedRange } = this.convertToTextEdit({
66
+ lookup,
67
+ change,
68
+ });
69
+ if (!edits.length) {
70
+ return;
71
+ }
72
+ const applyResult = await lspConnection.workspace.applyEdit({
73
+ label: `LikeC4 - change view ${changeView.viewId}`,
74
+ edit: {
75
+ changes: {
76
+ [textDocument.uri]: edits,
77
+ },
78
+ },
79
+ });
80
+ if (!applyResult.applied) {
81
+ lspConnection.window.showErrorMessage(`Failed to apply changes ${applyResult.failureReason}`);
82
+ return;
83
+ }
84
+ result = {
85
+ success: true,
86
+ location: {
87
+ uri: textDocument.uri,
88
+ range: modifiedRange,
89
+ },
90
+ };
91
+ });
92
+ }
93
+ catch (err) {
94
+ const error = loggable(wrapError(err, `Failed to apply change ${changeView.change.op} ${changeView.viewId}:\n`));
95
+ logger.error(error);
96
+ result = {
97
+ success: false,
98
+ error,
99
+ };
100
+ }
101
+ return result ?? {
102
+ success: false,
103
+ error: 'Unknown error applying model change',
104
+ };
105
+ }
106
+ convertToTextEdit({ lookup, change }) {
107
+ switch (change.op) {
108
+ case 'change-element-style': {
109
+ return changeElementStyle(this.services, {
110
+ ...lookup,
111
+ targets: change.targets,
112
+ style: change.style,
113
+ });
114
+ }
115
+ case 'change-autolayout': {
116
+ const edit = changeViewLayout(this.services, {
117
+ ...lookup,
118
+ layout: change.layout,
119
+ });
120
+ return {
121
+ modifiedRange: edit.range,
122
+ edits: [edit],
123
+ };
124
+ }
125
+ default:
126
+ nonexhaustive(change);
127
+ }
128
+ }
129
+ }