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