@hydranium/cli 1.0.0-next.38 → 1.0.0-next.381

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 (339) hide show
  1. package/README.md +102 -204
  2. package/bin/hydranium-cli.js +13 -0
  3. package/heap-analysis/heap/core/load.mjs +2 -2
  4. package/heap-analysis/heap/langium/langium.classifier.mjs +22 -11
  5. package/lib/cli-version.js.map +1 -1
  6. package/lib/cli.js +7 -2
  7. package/lib/cli.js.map +1 -1
  8. package/lib/commands/analyze-heap-args.d.ts.map +1 -1
  9. package/lib/commands/analyze-heap-args.js +1 -2
  10. package/lib/commands/analyze-heap-args.js.map +1 -1
  11. package/lib/commands/analyze-heap.d.ts +3 -0
  12. package/lib/commands/analyze-heap.d.ts.map +1 -1
  13. package/lib/commands/analyze-heap.js +2 -1
  14. package/lib/commands/analyze-heap.js.map +1 -1
  15. package/lib/commands/ast-ground-truth-args.d.ts +3 -2
  16. package/lib/commands/ast-ground-truth-args.d.ts.map +1 -1
  17. package/lib/commands/ast-ground-truth-args.js +11 -9
  18. package/lib/commands/ast-ground-truth-args.js.map +1 -1
  19. package/lib/commands/ast-ground-truth-driver.js +4 -2
  20. package/lib/commands/ast-ground-truth-driver.js.map +1 -1
  21. package/lib/commands/ast-ground-truth.js +1 -1
  22. package/lib/commands/ast-ground-truth.js.map +1 -1
  23. package/lib/commands/client-session.d.ts +17 -0
  24. package/lib/commands/client-session.d.ts.map +1 -0
  25. package/lib/commands/client-session.js +27 -0
  26. package/lib/commands/client-session.js.map +1 -0
  27. package/lib/commands/create-args.d.ts +17 -0
  28. package/lib/commands/create-args.d.ts.map +1 -0
  29. package/lib/commands/create-args.js +53 -0
  30. package/lib/commands/create-args.js.map +1 -0
  31. package/lib/commands/create.d.ts +38 -0
  32. package/lib/commands/create.d.ts.map +1 -0
  33. package/lib/commands/create.js +43 -0
  34. package/lib/commands/create.js.map +1 -0
  35. package/lib/commands/delete-args.d.ts +17 -0
  36. package/lib/commands/delete-args.d.ts.map +1 -0
  37. package/lib/commands/delete-args.js +49 -0
  38. package/lib/commands/delete-args.js.map +1 -0
  39. package/lib/commands/delete.d.ts +30 -0
  40. package/lib/commands/delete.d.ts.map +1 -0
  41. package/lib/commands/delete.js +32 -0
  42. package/lib/commands/delete.js.map +1 -0
  43. package/lib/commands/generate-ast-builder.d.ts.map +1 -1
  44. package/lib/commands/generate-ast-builder.js +1 -10
  45. package/lib/commands/generate-ast-builder.js.map +1 -1
  46. package/lib/commands/generate-languages.d.ts +35 -0
  47. package/lib/commands/generate-languages.d.ts.map +1 -0
  48. package/lib/commands/generate-languages.js +105 -0
  49. package/lib/commands/generate-languages.js.map +1 -0
  50. package/lib/commands/generate-transfer-builder.d.ts +33 -0
  51. package/lib/commands/generate-transfer-builder.d.ts.map +1 -0
  52. package/lib/commands/generate-transfer-builder.js +86 -0
  53. package/lib/commands/generate-transfer-builder.js.map +1 -0
  54. package/lib/commands/generate-transfer-model-args.d.ts.map +1 -1
  55. package/lib/commands/generate-transfer-model-args.js +20 -5
  56. package/lib/commands/generate-transfer-model-args.js.map +1 -1
  57. package/lib/commands/generate-transfer-model-config.d.ts.map +1 -1
  58. package/lib/commands/generate-transfer-model-config.js +8 -3
  59. package/lib/commands/generate-transfer-model-config.js.map +1 -1
  60. package/lib/commands/generate-transfer-model.d.ts +40 -9
  61. package/lib/commands/generate-transfer-model.d.ts.map +1 -1
  62. package/lib/commands/generate-transfer-model.js +219 -67
  63. package/lib/commands/generate-transfer-model.js.map +1 -1
  64. package/lib/commands/generated-module-path.d.ts +16 -0
  65. package/lib/commands/generated-module-path.d.ts.map +1 -0
  66. package/lib/commands/generated-module-path.js +21 -0
  67. package/lib/commands/generated-module-path.js.map +1 -0
  68. package/lib/commands/harness-args.d.ts +44 -42
  69. package/lib/commands/harness-args.d.ts.map +1 -1
  70. package/lib/commands/harness-args.js +109 -27
  71. package/lib/commands/harness-args.js.map +1 -1
  72. package/lib/commands/headless-harness.d.ts +37 -20
  73. package/lib/commands/headless-harness.d.ts.map +1 -1
  74. package/lib/commands/headless-harness.js +71 -19
  75. package/lib/commands/headless-harness.js.map +1 -1
  76. package/lib/commands/init-args.d.ts +2 -0
  77. package/lib/commands/init-args.d.ts.map +1 -1
  78. package/lib/commands/init-args.js +74 -31
  79. package/lib/commands/init-args.js.map +1 -1
  80. package/lib/commands/init-hosts.d.ts +24 -0
  81. package/lib/commands/init-hosts.d.ts.map +1 -0
  82. package/lib/commands/init-hosts.js +37 -0
  83. package/lib/commands/init-hosts.js.map +1 -0
  84. package/lib/commands/init-prompt.d.ts +3 -3
  85. package/lib/commands/init-prompt.d.ts.map +1 -1
  86. package/lib/commands/init-prompt.js +4 -5
  87. package/lib/commands/init-prompt.js.map +1 -1
  88. package/lib/commands/init-templates.d.ts +26 -14
  89. package/lib/commands/init-templates.d.ts.map +1 -1
  90. package/lib/commands/init-templates.js +2989 -746
  91. package/lib/commands/init-templates.js.map +1 -1
  92. package/lib/commands/init-wizard.d.ts +3 -0
  93. package/lib/commands/init-wizard.d.ts.map +1 -1
  94. package/lib/commands/init-wizard.js +46 -31
  95. package/lib/commands/init-wizard.js.map +1 -1
  96. package/lib/commands/init-workspace.d.ts +13 -11
  97. package/lib/commands/init-workspace.d.ts.map +1 -1
  98. package/lib/commands/init-workspace.js +21 -7
  99. package/lib/commands/init-workspace.js.map +1 -1
  100. package/lib/commands/init.d.ts +58 -22
  101. package/lib/commands/init.d.ts.map +1 -1
  102. package/lib/commands/init.js +206 -48
  103. package/lib/commands/init.js.map +1 -1
  104. package/lib/commands/kebab.d.ts +15 -0
  105. package/lib/commands/kebab.d.ts.map +1 -0
  106. package/lib/commands/kebab.js +17 -0
  107. package/lib/commands/kebab.js.map +1 -0
  108. package/lib/commands/lint-grammar-args.d.ts +3 -2
  109. package/lib/commands/lint-grammar-args.d.ts.map +1 -1
  110. package/lib/commands/lint-grammar-args.js +13 -10
  111. package/lib/commands/lint-grammar-args.js.map +1 -1
  112. package/lib/commands/lint-grammar-driver.js +2 -1
  113. package/lib/commands/lint-grammar-driver.js.map +1 -1
  114. package/lib/commands/lint-grammar-report.js.map +1 -1
  115. package/lib/commands/lint-grammar.js +1 -1
  116. package/lib/commands/lint-grammar.js.map +1 -1
  117. package/lib/commands/measure-memory-args.d.ts +3 -2
  118. package/lib/commands/measure-memory-args.d.ts.map +1 -1
  119. package/lib/commands/measure-memory-args.js +22 -11
  120. package/lib/commands/measure-memory-args.js.map +1 -1
  121. package/lib/commands/measure-memory-driver.js +5 -3
  122. package/lib/commands/measure-memory-driver.js.map +1 -1
  123. package/lib/commands/measure-memory.js +1 -1
  124. package/lib/commands/measure-memory.js.map +1 -1
  125. package/lib/commands/model-docs-args.d.ts +4 -3
  126. package/lib/commands/model-docs-args.d.ts.map +1 -1
  127. package/lib/commands/model-docs-args.js +12 -10
  128. package/lib/commands/model-docs-args.js.map +1 -1
  129. package/lib/commands/model-docs-driver.js +2 -1
  130. package/lib/commands/model-docs-driver.js.map +1 -1
  131. package/lib/commands/model-docs.js +1 -1
  132. package/lib/commands/model-docs.js.map +1 -1
  133. package/lib/commands/projects-args.d.ts.map +1 -1
  134. package/lib/commands/projects-args.js +5 -5
  135. package/lib/commands/projects-args.js.map +1 -1
  136. package/lib/commands/projects.d.ts +3 -4
  137. package/lib/commands/projects.d.ts.map +1 -1
  138. package/lib/commands/projects.js +2 -2
  139. package/lib/commands/projects.js.map +1 -1
  140. package/lib/commands/query-args.d.ts.map +1 -1
  141. package/lib/commands/query-args.js +6 -5
  142. package/lib/commands/query-args.js.map +1 -1
  143. package/lib/commands/query.d.ts +6 -5
  144. package/lib/commands/query.d.ts.map +1 -1
  145. package/lib/commands/query.js +5 -4
  146. package/lib/commands/query.js.map +1 -1
  147. package/lib/commands/reflect-args.d.ts +3 -2
  148. package/lib/commands/reflect-args.d.ts.map +1 -1
  149. package/lib/commands/reflect-args.js +13 -10
  150. package/lib/commands/reflect-args.js.map +1 -1
  151. package/lib/commands/reflect-driver.js +2 -1
  152. package/lib/commands/reflect-driver.js.map +1 -1
  153. package/lib/commands/reflect-report.js.map +1 -1
  154. package/lib/commands/reflect.js +1 -1
  155. package/lib/commands/reflect.js.map +1 -1
  156. package/lib/commands/save-args.d.ts.map +1 -1
  157. package/lib/commands/save-args.js +7 -4
  158. package/lib/commands/save-args.js.map +1 -1
  159. package/lib/commands/save.d.ts +8 -4
  160. package/lib/commands/save.d.ts.map +1 -1
  161. package/lib/commands/save.js +12 -23
  162. package/lib/commands/save.js.map +1 -1
  163. package/lib/commands/validate-args.d.ts +3 -2
  164. package/lib/commands/validate-args.d.ts.map +1 -1
  165. package/lib/commands/validate-args.js +13 -10
  166. package/lib/commands/validate-args.js.map +1 -1
  167. package/lib/commands/validate-driver.js +2 -1
  168. package/lib/commands/validate-driver.js.map +1 -1
  169. package/lib/commands/validate-report.js.map +1 -1
  170. package/lib/commands/validate.js +1 -1
  171. package/lib/commands/validate.js.map +1 -1
  172. package/lib/commands/watch-args.d.ts.map +1 -1
  173. package/lib/commands/watch-args.js +6 -4
  174. package/lib/commands/watch-args.js.map +1 -1
  175. package/lib/commands/watch.d.ts +3 -4
  176. package/lib/commands/watch.d.ts.map +1 -1
  177. package/lib/commands/watch.js +20 -2
  178. package/lib/commands/watch.js.map +1 -1
  179. package/lib/driver-heap.d.ts +52 -0
  180. package/lib/driver-heap.d.ts.map +1 -0
  181. package/lib/driver-heap.js +51 -0
  182. package/lib/driver-heap.js.map +1 -0
  183. package/lib/index.d.ts +2 -0
  184. package/lib/index.d.ts.map +1 -1
  185. package/lib/index.js +2 -0
  186. package/lib/index.js.map +1 -1
  187. package/lib/log-level.d.ts +21 -11
  188. package/lib/log-level.d.ts.map +1 -1
  189. package/lib/log-level.js +25 -14
  190. package/lib/log-level.js.map +1 -1
  191. package/lib/spawn-data-server.d.ts +9 -4
  192. package/lib/spawn-data-server.d.ts.map +1 -1
  193. package/lib/spawn-data-server.js +5 -4
  194. package/lib/spawn-data-server.js.map +1 -1
  195. package/lib/stderr-connection-logger.d.ts +12 -0
  196. package/lib/stderr-connection-logger.d.ts.map +1 -0
  197. package/lib/stderr-connection-logger.js +16 -0
  198. package/lib/stderr-connection-logger.js.map +1 -0
  199. package/lib/testing/echo-server.js +9 -1
  200. package/lib/testing/echo-server.js.map +1 -1
  201. package/package.json +90 -39
  202. package/src/cli.ts +7 -2
  203. package/src/commands/analyze-heap-args.ts +1 -2
  204. package/src/commands/analyze-heap.ts +4 -1
  205. package/src/commands/ast-ground-truth-args.ts +15 -12
  206. package/src/commands/ast-ground-truth-driver.ts +4 -2
  207. package/src/commands/ast-ground-truth.ts +1 -1
  208. package/src/commands/client-session.ts +33 -0
  209. package/src/commands/create-args.ts +68 -0
  210. package/src/commands/create.ts +83 -0
  211. package/src/commands/delete-args.ts +64 -0
  212. package/src/commands/delete.ts +61 -0
  213. package/src/commands/generate-ast-builder.ts +1 -11
  214. package/src/commands/generate-languages.ts +132 -0
  215. package/src/commands/generate-transfer-builder.ts +101 -0
  216. package/src/commands/generate-transfer-model-args.ts +20 -5
  217. package/src/commands/generate-transfer-model-config.ts +8 -3
  218. package/src/commands/generate-transfer-model.ts +291 -65
  219. package/src/commands/generated-module-path.ts +22 -0
  220. package/src/commands/harness-args.ts +99 -31
  221. package/src/commands/headless-harness.ts +89 -28
  222. package/src/commands/init-args.ts +76 -31
  223. package/src/commands/init-hosts.ts +51 -0
  224. package/src/commands/init-prompt.ts +5 -6
  225. package/src/commands/init-templates.ts +3161 -759
  226. package/src/commands/init-wizard.ts +55 -31
  227. package/src/commands/init-workspace.ts +31 -16
  228. package/src/commands/init.ts +266 -63
  229. package/src/commands/kebab.ts +17 -0
  230. package/src/commands/lint-grammar-args.ts +17 -13
  231. package/src/commands/lint-grammar-driver.ts +2 -1
  232. package/src/commands/lint-grammar.ts +1 -1
  233. package/src/commands/measure-memory-args.ts +27 -14
  234. package/src/commands/measure-memory-driver.ts +5 -3
  235. package/src/commands/measure-memory.ts +1 -1
  236. package/src/commands/model-docs-args.ts +16 -13
  237. package/src/commands/model-docs-driver.ts +2 -1
  238. package/src/commands/model-docs.ts +1 -1
  239. package/src/commands/projects-args.ts +5 -4
  240. package/src/commands/projects.ts +4 -6
  241. package/src/commands/query-args.ts +6 -4
  242. package/src/commands/query.ts +9 -9
  243. package/src/commands/reflect-args.ts +17 -13
  244. package/src/commands/reflect-driver.ts +2 -1
  245. package/src/commands/reflect.ts +1 -1
  246. package/src/commands/save-args.ts +7 -3
  247. package/src/commands/save.ts +24 -27
  248. package/src/commands/validate-args.ts +17 -13
  249. package/src/commands/validate-driver.ts +2 -1
  250. package/src/commands/validate.ts +1 -1
  251. package/src/commands/watch-args.ts +6 -3
  252. package/src/commands/watch.ts +28 -7
  253. package/src/driver-heap.ts +65 -0
  254. package/src/index.ts +2 -0
  255. package/src/log-level.ts +42 -14
  256. package/src/spawn-data-server.ts +17 -5
  257. package/src/stderr-connection-logger.ts +18 -0
  258. package/src/testing/echo-server.ts +11 -2
  259. package/templates/client/gitignore +3 -0
  260. package/templates/client/src/__GRAMMAR_ID__-diagram-module.ts +82 -0
  261. package/templates/editorconfig +15 -0
  262. package/templates/gitignore +1 -0
  263. package/templates/host-browser/esbuild.mjs +82 -0
  264. package/templates/host-browser/gitignore +4 -0
  265. package/templates/host-browser/hydranium-mark.svg +40 -0
  266. package/templates/host-browser/index.html +287 -0
  267. package/templates/host-browser/playwright.config.mts +24 -0
  268. package/templates/host-browser/src/data-status.ts +15 -0
  269. package/templates/host-browser/src/diagram-save.ts +60 -0
  270. package/templates/host-browser/src/diagram.ts +118 -0
  271. package/templates/host-browser/src/editor.ts +102 -0
  272. package/templates/host-browser/src/log.ts +14 -0
  273. package/templates/host-browser/src/monaco-editor-core-worker.d.ts +7 -0
  274. package/templates/host-browser/src/monaco-editor-worker.ts +10 -0
  275. package/templates/host-browser/src/posix-path-shim.ts +23 -0
  276. package/templates/host-browser/src/shown-file.ts +61 -0
  277. package/templates/host-browser/src/workspace.ts +5 -0
  278. package/templates/host-browser/tsconfig.e2e.json +16 -0
  279. package/templates/host-browser/tsconfig.page.json +16 -0
  280. package/templates/host-browser/tsconfig.worker.json +16 -0
  281. package/templates/host-theia-browser/esbuild.mjs +24 -0
  282. package/templates/host-theia-browser/gitignore +9 -0
  283. package/templates/host-theia-browser/playwright.config.mts +42 -0
  284. package/templates/host-theia-browser/start.mjs +50 -0
  285. package/templates/host-theia-browser/tsconfig.e2e.json +16 -0
  286. package/templates/host-theia-electron/esbuild.mjs +27 -0
  287. package/templates/host-theia-electron/gitignore +8 -0
  288. package/templates/host-theia-electron/playwright.config.mts +12 -0
  289. package/templates/host-theia-electron/start.mjs +47 -0
  290. package/templates/host-theia-electron/tsconfig.e2e.json +16 -0
  291. package/templates/host-vscode/esbuild.mjs +38 -0
  292. package/templates/host-vscode/gitignore +7 -0
  293. package/templates/host-vscode/playwright.config.mts +14 -0
  294. package/templates/host-vscode/src/__GRAMMAR_ID__-diagram-editor.ts +19 -0
  295. package/templates/host-vscode/src/show-model-command.ts +24 -0
  296. package/templates/host-vscode/src/webview/__GRAMMAR_ID__-diagram.ts +18 -0
  297. package/templates/host-vscode/src/webview/diagram.css +40 -0
  298. package/templates/host-vscode/start.mjs +22 -0
  299. package/templates/host-vscode/test/install-vscode.mts +21 -0
  300. package/templates/host-vscode/test/vscode-version.mts +10 -0
  301. package/templates/host-vscode/tsconfig.e2e.json +17 -0
  302. package/templates/host-vscode/tsconfig.json +19 -0
  303. package/templates/host-vscode/tsconfig.webview.json +17 -0
  304. package/templates/oxlintrc.json +4 -0
  305. package/templates/protocol/gitignore +3 -0
  306. package/templates/protocol/src/__GRAMMAR_ID__-diagram-types.ts +47 -0
  307. package/templates/server/gitignore +6 -0
  308. package/templates/server/src/glsp/__GRAMMAR_ID__/create-node-operation-handler.ts +62 -0
  309. package/templates/server/src/glsp/__GRAMMAR_ID__/delete-operation-handler.ts +63 -0
  310. package/templates/server/src/glsp/__GRAMMAR_ID__/gmodel-factory.ts +144 -0
  311. package/templates/server/src/glsp/__GRAMMAR_ID__/state.ts +18 -0
  312. package/templates/server/src/glsp/__GRAMMAR_ID__/storage.ts +16 -0
  313. package/templates/server/src/glsp/__GRAMMAR_ID__/submission-handler.ts +24 -0
  314. package/templates/server/src/grammar/common.langium +12 -0
  315. package/templates/server/src/language-server/__GRAMMAR_ID__-serializer.ts +53 -0
  316. package/templates/server/src/language-server/__GRAMMAR_ID__-validation.ts +30 -0
  317. package/templates/server/src/language-server/ast.ts +23 -0
  318. package/templates/server/src/node/services.ts +16 -0
  319. package/templates/server/test/__GRAMMAR_ID__-diagram.test.ts +388 -0
  320. package/templates/server/test/tsconfig.json +4 -0
  321. package/templates/server/tsconfig.test.json +10 -0
  322. package/templates/server/vitest.config.ts +14 -0
  323. package/templates/theia/gitignore +3 -0
  324. package/templates/theia/src/browser/__GRAMMAR_ID__-diagram-frontend-module.ts +65 -0
  325. package/templates/theia/src/browser/__PROJECT_ID__-connection-preload-module.ts +15 -0
  326. package/templates/theia/src/browser/__PROJECT_ID__-data-connection.ts +15 -0
  327. package/templates/theia/src/browser/__PROJECT_ID__-data-port.ts +11 -0
  328. package/templates/theia/src/browser/__PROJECT_ID__-glsp-client-contribution.ts +13 -0
  329. package/templates/theia/src/browser/__PROJECT_ID__-show-model-command.ts +51 -0
  330. package/templates/theia/src/common/__PROJECT_ID__-glsp-contribution.ts +3 -0
  331. package/templates/theia/src/node/__PROJECT_ID__-connection-backend-module.ts +12 -0
  332. package/templates/theia/src/node/__PROJECT_ID__-data-backend-module.ts +15 -0
  333. package/templates/theia/src/node/__PROJECT_ID__-glsp-backend-module.ts +16 -0
  334. package/templates/theia/style/diagram.css +20 -0
  335. package/templates/theia/tsconfig.json +24 -0
  336. package/templates/vscode-servers/gitignore +4 -0
  337. package/templates/vscode-servers/tsconfig.json +19 -0
  338. package/templates/workspace/nodes.__EXTENSION__ +5 -0
  339. package/templates/workspace/references.__EXTENSION__ +2 -0
@@ -0,0 +1,388 @@
1
+ // The starter diagram, run the way a client runs it: an in-process GLSP server
2
+ // opens a document. A create-node operation adds a node, and an undo and a redo
3
+ // take it out and put it back; every step must keep the nodes the document
4
+ // already had, and its comments, since the diagram writes the document from its
5
+ // AST, which carries none. A delete removes a node with the references to it, or
6
+ // an edge's reference. A reference into another document resolves, even
7
+ // when the diagram opened its document before the workspace was up, and draws
8
+ // an edge to an external node standing for its target. Each node is placed a
9
+ // row above its target, and a cycle does not stop the placing. A diagnostic
10
+ // reaches the diagram as a marker on its node.
11
+
12
+ import 'reflect-metadata';
13
+ import {
14
+ type Action,
15
+ CreateNodeOperation,
16
+ DeleteElementOperation,
17
+ GEdge,
18
+ GModelFactory,
19
+ GNode,
20
+ ModelValidator,
21
+ RedoAction,
22
+ ServerModule,
23
+ SetDirtyStateAction,
24
+ UndoAction
25
+ } from '@eclipse-glsp/server';
26
+ import { initializeWorkspaceProgrammatically } from '@hydranium/core';
27
+ import { makeScratchWorkspace, type ScratchWorkspace } from '@hydranium/core/testing/node';
28
+ import { HydraniumGlspAppModule } from '@hydranium/glsp-server';
29
+ import { type GlspHarness, makeGlspHarness } from '@hydranium/glsp-server/testing';
30
+ import { URI } from '@hydranium/langium';
31
+ import { waitFor } from '@hydranium/protocol/testing';
32
+ import {
33
+ __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
34
+ __GRAMMAR_ID_CONST___EDGE_TYPE,
35
+ __GRAMMAR_ID_CONST___EXTERNAL_NODE_TYPE,
36
+ __GRAMMAR_ID_CONST___NODE_TYPE
37
+ } from '__PROTOCOL_PACKAGE__';
38
+ import { afterEach, describe, expect, it } from 'vitest';
39
+ import { __GRAMMAR__DiagramModule } from '../src/glsp/__GRAMMAR_ID__/diagram-module.js';
40
+ import { __GRAMMAR__GModelFactory } from '../src/glsp/__GRAMMAR_ID__/gmodel-factory.js';
41
+ import type { __GRAMMAR__GlspState } from '../src/glsp/__GRAMMAR_ID__/state.js';
42
+ import { is__ENTRY_RULE__ } from '../src/language-server/ast.js';
43
+ import { createServices } from '../src/node/services.js';
44
+
45
+ let workspace: ScratchWorkspace | undefined;
46
+ let diagram: GlspHarness<__GRAMMAR__GlspState> | undefined;
47
+
48
+ afterEach(() => {
49
+ diagram?.dispose();
50
+ workspace?.dispose();
51
+ diagram = undefined;
52
+ workspace = undefined;
53
+ });
54
+
55
+ /** Send `action` and wait for the dirty state the server answers it with, which it sends once the edit is written. */
56
+ async function send(harness: GlspHarness<__GRAMMAR__GlspState>, action: Action, reason: string): Promise<void> {
57
+ const before = harness.actions.length;
58
+ harness.dispatch(action);
59
+ await waitFor(() => harness.actions.slice(before).some(sent => SetDirtyStateAction.is(sent) && sent.reason === reason), {
60
+ message: `no '${reason}' dirty state`
61
+ });
62
+ }
63
+
64
+ describe('__GRAMMAR__ diagram', () => {
65
+ it('creates a node after the existing ones, keeping their comments, and undoes and redoes it', async () => {
66
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
67
+ const original = '// keep this comment\nnode first -> second\n// about second\nnode second';
68
+ const file = workspace.write('model.__EXTENSION__', original);
69
+ const { shared } = createServices();
70
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
71
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
72
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
73
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
74
+ appModules: [new HydraniumGlspAppModule({ shared })]
75
+ });
76
+ await diagram.start();
77
+ await diagram.openDocument(file);
78
+ const uri = workspace.uri('model.__EXTENSION__');
79
+ const text = (): string | undefined => shared.workspace.TextDocuments.get(uri)?.getText();
80
+
81
+ await send(diagram, CreateNodeOperation.create(__GRAMMAR_ID_CONST___NODE_TYPE), 'operation');
82
+ const created = text();
83
+ await send(diagram, UndoAction.create(), 'undo');
84
+ const undone = text();
85
+ await send(diagram, RedoAction.create(), 'redo');
86
+
87
+ expect({ created, undone, redone: text() }).toEqual({
88
+ created: `${original}\nnode Node`,
89
+ undone: original,
90
+ redone: `${original}\nnode Node`
91
+ });
92
+ });
93
+
94
+ it('deletes a node with the references to it, and an edge with its reference, undoably', async () => {
95
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
96
+ const original = 'node first -> second\nnode second\nnode third -> first';
97
+ const file = workspace.write('model.__EXTENSION__', original);
98
+ const { shared } = createServices();
99
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
100
+ const harness = makeGlspHarness<__GRAMMAR__GlspState>({
101
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
102
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
103
+ appModules: [new HydraniumGlspAppModule({ shared })]
104
+ });
105
+ diagram = harness;
106
+ await harness.start();
107
+ await harness.openDocument(file);
108
+ const uri = workspace.uri('model.__EXTENSION__');
109
+ const text = (): string | undefined => shared.workspace.TextDocuments.get(uri)?.getText();
110
+ const idOf = (type: string, index: number): string => harness.state.root.children.filter(child => child.type === type)[index].id;
111
+
112
+ await send(harness, DeleteElementOperation.create([idOf(__GRAMMAR_ID_CONST___NODE_TYPE, 1)]), 'operation');
113
+ const nodeDeleted = text();
114
+ await send(harness, UndoAction.create(), 'undo');
115
+ const undone = text();
116
+ // The edge third -> first.
117
+ await send(harness, DeleteElementOperation.create([idOf(__GRAMMAR_ID_CONST___EDGE_TYPE, 1)]), 'operation');
118
+
119
+ expect({ nodeDeleted, undone, edgeDeleted: text() }).toEqual({
120
+ nodeDeleted: 'node first\nnode third -> first',
121
+ undone: original,
122
+ edgeDeleted: 'node first -> second\nnode second\nnode third'
123
+ });
124
+ });
125
+
126
+ it('deletes the drawn node of a repeated name, keeping the references the remaining node resolves', async () => {
127
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
128
+ const file = workspace.write('model.__EXTENSION__', 'node first -> second\nnode first\nnode second -> first');
129
+ const { shared } = createServices();
130
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
131
+ const harness = makeGlspHarness<__GRAMMAR__GlspState>({
132
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
133
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
134
+ appModules: [new HydraniumGlspAppModule({ shared })]
135
+ });
136
+ diagram = harness;
137
+ await harness.start();
138
+ await harness.openDocument(file);
139
+
140
+ await send(harness, DeleteElementOperation.create([harness.state.root.children[0].id]), 'operation');
141
+
142
+ expect(shared.workspace.TextDocuments.get(workspace.uri('model.__EXTENSION__'))?.getText()).toBe('node first\nnode second -> first');
143
+ });
144
+
145
+ it('deletes a node, keeping the references another document declares the name for', async () => {
146
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
147
+ workspace.write('other.__EXTENSION__', 'node second');
148
+ const file = workspace.write('model.__EXTENSION__', 'node first -> second\nnode second');
149
+ const { shared } = createServices();
150
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
151
+ const harness = makeGlspHarness<__GRAMMAR__GlspState>({
152
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
153
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
154
+ appModules: [new HydraniumGlspAppModule({ shared })]
155
+ });
156
+ diagram = harness;
157
+ await harness.start();
158
+ await harness.openDocument(file);
159
+
160
+ await send(harness, DeleteElementOperation.create([harness.state.root.children[1].id]), 'operation');
161
+
162
+ expect(shared.workspace.TextDocuments.get(workspace.uri('model.__EXTENSION__'))?.getText()).toBe('node first -> second');
163
+ });
164
+
165
+ it("draws a repeated name once, as its first node with that node's edge", async () => {
166
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
167
+ const file = workspace.write('model.__EXTENSION__', 'node a\nnode a -> b\nnode b');
168
+ const { shared } = createServices();
169
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
170
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
171
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
172
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
173
+ appModules: [new HydraniumGlspAppModule({ shared })]
174
+ });
175
+ await diagram.start();
176
+ await diagram.openDocument(file);
177
+
178
+ // The first `a` references nothing, so no edge, and both nodes share a row.
179
+ const children = diagram.state.root.children;
180
+ expect(children.map(child => ({ type: child.type, position: child instanceof GNode ? child.position : undefined }))).toEqual([
181
+ { type: __GRAMMAR_ID_CONST___NODE_TYPE, position: { x: 0, y: 0 } },
182
+ { type: __GRAMMAR_ID_CONST___NODE_TYPE, position: { x: 200, y: 0 } }
183
+ ]);
184
+ });
185
+
186
+ it("draws a node named like another node's label or edge", async () => {
187
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
188
+ const file = workspace.write('model.__EXTENSION__', 'node a -> b\nnode b\nnode a_name\nnode a_target');
189
+ const { shared } = createServices();
190
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
191
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
192
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
193
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
194
+ appModules: [new HydraniumGlspAppModule({ shared })]
195
+ });
196
+ await diagram.start();
197
+ await diagram.openDocument(file);
198
+
199
+ expect(diagram.state.root.children.map(child => child.type)).toEqual([
200
+ __GRAMMAR_ID_CONST___NODE_TYPE,
201
+ __GRAMMAR_ID_CONST___NODE_TYPE,
202
+ __GRAMMAR_ID_CONST___NODE_TYPE,
203
+ __GRAMMAR_ID_CONST___NODE_TYPE,
204
+ __GRAMMAR_ID_CONST___EDGE_TYPE
205
+ ]);
206
+ });
207
+
208
+ it('draws a reference into another document as an edge to one external node', async () => {
209
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
210
+ workspace.write('declaring.__EXTENSION__', 'node second');
211
+ const file = workspace.write('referencing.__EXTENSION__', 'node first -> second\nnode third -> second');
212
+ const { shared } = createServices();
213
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
214
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
215
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
216
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
217
+ appModules: [new HydraniumGlspAppModule({ shared })]
218
+ });
219
+ await diagram.start();
220
+ await diagram.openDocument(file);
221
+
222
+ const children = diagram.state.root.children;
223
+ const external = children.find(child => child.type === __GRAMMAR_ID_CONST___EXTERNAL_NODE_TYPE);
224
+ expect(children.map(child => child.type)).toEqual([
225
+ __GRAMMAR_ID_CONST___NODE_TYPE,
226
+ __GRAMMAR_ID_CONST___NODE_TYPE,
227
+ __GRAMMAR_ID_CONST___EXTERNAL_NODE_TYPE,
228
+ __GRAMMAR_ID_CONST___EDGE_TYPE,
229
+ __GRAMMAR_ID_CONST___EDGE_TYPE
230
+ ]);
231
+ expect(external?.children[0]).toMatchObject({ text: 'second (declaring.__EXTENSION__)' });
232
+ // The two referencing nodes side by side in one row, their target in the next.
233
+ expect(children.filter(child => child instanceof GNode).map(node => node.position)).toEqual([
234
+ { x: 0, y: 0 },
235
+ { x: 200, y: 0 },
236
+ { x: 0, y: 90 }
237
+ ]);
238
+ const targets = children.filter(child => child instanceof GEdge).map(edge => edge.targetId);
239
+ expect(targets).toEqual([external?.id, external?.id]);
240
+ });
241
+
242
+ it('draws one external node per document when two documents declare the same name', async () => {
243
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
244
+ workspace.write('one.__EXTENSION__', 'node second');
245
+ workspace.write('two.__EXTENSION__', 'node second');
246
+ const file = workspace.write('referencing.__EXTENSION__', 'node first -> second\nnode third -> second');
247
+ const { shared } = createServices();
248
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
249
+ const harness = makeGlspHarness<__GRAMMAR__GlspState>({
250
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
251
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
252
+ appModules: [new HydraniumGlspAppModule({ shared })]
253
+ });
254
+ diagram = harness;
255
+ await harness.start();
256
+ await harness.openDocument(file);
257
+ // A name resolves to one node per document, so point each reference at a
258
+ // different document's node the way a qualified reference would.
259
+ const [first, third] = harness.state.sourceRoot.nodes;
260
+ for (const [node, declaring] of [
261
+ [first, 'one.__EXTENSION__'],
262
+ [third, 'two.__EXTENSION__']
263
+ ] as const) {
264
+ const declared = shared.workspace.LangiumDocuments.getDocument(URI.parse(workspace.uri(declaring)))?.parseResult.value;
265
+ if (!node.target || !is__ENTRY_RULE__(declared)) {
266
+ throw new Error(`no reference from ${node.name} or no model in ${declaring}`);
267
+ }
268
+ Object.defineProperty(node.target, 'ref', { value: declared.nodes[0] });
269
+ }
270
+ harness.sessionContainer.get<GModelFactory>(GModelFactory).createModel();
271
+
272
+ const children = harness.state.root.children;
273
+ const externals = children.filter(child => child.type === __GRAMMAR_ID_CONST___EXTERNAL_NODE_TYPE);
274
+ expect(externals.map(external => external.children[0])).toMatchObject([
275
+ { text: 'second (one.__EXTENSION__)' },
276
+ { text: 'second (two.__EXTENSION__)' }
277
+ ]);
278
+ const targets = children.filter(child => child instanceof GEdge).map(edge => edge.targetId);
279
+ expect(targets).toEqual(externals.map(external => external.id));
280
+ });
281
+
282
+ it('places each node a row above its target, cutting a cycle', async () => {
283
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
284
+ const file = workspace.write('cycle.__EXTENSION__', 'node a -> b\nnode b -> a\nnode c -> a');
285
+ const { shared } = createServices();
286
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
287
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
288
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
289
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
290
+ appModules: [new HydraniumGlspAppModule({ shared })]
291
+ });
292
+ await diagram.start();
293
+ await diagram.openDocument(file);
294
+
295
+ // The walk from `a` meets `a` again at `b`, so `b` ranks first; `c` references `a`.
296
+ const rows = diagram.state.root.children.filter(child => child instanceof GNode).map(node => node.position.y / 90);
297
+ expect(rows).toEqual([1, 2, 0]);
298
+ });
299
+
300
+ it('ranks a chain longer than the call stack is deep', () => {
301
+ // The ranking alone, since a document this long takes seconds to parse and link.
302
+ class Ranking extends __GRAMMAR__GModelFactory {
303
+ rankChain(targetOf: ReadonlyMap<string, string>, ids: readonly string[]): Map<string, number> {
304
+ return this.rank(targetOf, ids);
305
+ }
306
+ }
307
+ const ids = Array.from({ length: 100_000 }, (_, i) => `n${i}`);
308
+ const targetOf = new Map(ids.slice(0, -1).map((id, i) => [id, ids[i + 1]]));
309
+
310
+ const ranks = new Ranking().rankChain(targetOf, ids);
311
+
312
+ expect({ first: ranks.get(ids[0]), beforeLast: ranks.get(ids[ids.length - 2]) }).toEqual({ first: ids.length - 1, beforeLast: 1 });
313
+ });
314
+
315
+ it('keeps no route the client reports, which it computes before it sizes the nodes', async () => {
316
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
317
+ const file = workspace.write('loop.__EXTENSION__', 'node a -> a');
318
+ const { shared } = createServices();
319
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
320
+ const harness = makeGlspHarness<__GRAMMAR__GlspState>({
321
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
322
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
323
+ appModules: [new HydraniumGlspAppModule({ shared })]
324
+ });
325
+ diagram = harness;
326
+ await harness.start();
327
+ await harness.openDocument(file);
328
+ const edge = (): GEdge | undefined => harness.state.root.children.find(child => child instanceof GEdge);
329
+ const edgeId = edge()?.id ?? '';
330
+
331
+ // A self-reference's loop as the client routes it around a node not yet sized.
332
+ const corners = [
333
+ { x: 19, y: -0.75 },
334
+ { x: 19, y: 19 },
335
+ { x: -0.75, y: 19 }
336
+ ];
337
+ const routes = [{ elementId: edgeId, newRoutingPoints: [{ x: 0, y: 0 }, ...corners, { x: 0, y: 0 }] }];
338
+ await harness.answerBoundsRequest({ routes });
339
+
340
+ expect(edge()?.routingPoints).toEqual([]);
341
+ });
342
+
343
+ it('marks a diagnostic on the node it is about', async () => {
344
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
345
+ const file = workspace.write('duplicate.__EXTENSION__', 'node first\nnode first');
346
+ const { shared } = createServices();
347
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
348
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
349
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
350
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
351
+ appModules: [new HydraniumGlspAppModule({ shared })]
352
+ });
353
+ await diagram.start();
354
+ await diagram.openDocument(file);
355
+
356
+ // A workspace brought up programmatically does not validate; an editor's open does.
357
+ const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(file));
358
+ await shared.workspace.DocumentBuilder.build([document], { validation: true });
359
+
360
+ // The repeated name's warning, on the one node the diagram keeps for both.
361
+ const markers = await diagram.sessionContainer.get<ModelValidator>(ModelValidator).validate([diagram.state.root]);
362
+ expect(markers.map(marker => ({ elementId: marker.elementId, kind: marker.kind }))).toEqual([
363
+ { elementId: diagram.state.root.children[0].id, kind: 'warning' }
364
+ ]);
365
+ });
366
+
367
+ it('resolves a cross-document reference in a document opened before the workspace is up', async () => {
368
+ workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
369
+ workspace.write('declaring.__EXTENSION__', 'node second');
370
+ const file = workspace.write('referencing.__EXTENSION__', 'node first -> second');
371
+ const { shared } = createServices();
372
+ diagram = makeGlspHarness<__GRAMMAR__GlspState>({
373
+ serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
374
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
375
+ appModules: [new HydraniumGlspAppModule({ shared })]
376
+ });
377
+ await diagram.start();
378
+ // The open waits for the workspace. One that did not would have linked
379
+ // within this timer against a workspace holding only itself, and the
380
+ // bring-up does not relink it. Its budget covers the bring-up as well.
381
+ const opening = diagram.openDocument(file, 10_000);
382
+ await new Promise(resolve => setTimeout(resolve, 200));
383
+ await initializeWorkspaceProgrammatically(shared, workspace.root);
384
+ await opening;
385
+
386
+ expect(diagram.state.sourceRoot?.nodes[0].target?.ref?.name).toBe('second');
387
+ });
388
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../tsconfig.test.json",
3
+ "include": ["../src", ".", "../src/**/*.json"]
4
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "noEmit": true,
6
+ "isolatedModules": true,
7
+ "types": ["node"]
8
+ },
9
+ "include": ["src", "test", "src/**/*.json"]
10
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ // Vitest transforms TypeScript itself and resolves `.js` specifiers to their
4
+ // `.ts` source, so tests import from `../src/` with the same specifiers the
5
+ // compiled output uses. `include` is scoped to `test/` so `lib/` is never
6
+ // scanned; `npm test` runs `typecheck:test` first, because vitest's
7
+ // transform strips types without checking them.
8
+
9
+ export default defineConfig({
10
+ test: {
11
+ environment: 'node',
12
+ include: ['test/**/*.{test,spec}.ts']
13
+ }
14
+ });
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ lib/
3
+ *.tsbuildinfo
@@ -0,0 +1,65 @@
1
+ // The __GRAMMAR__ diagram in Theia: the client package's diagram, opened from
2
+ // the language's files by a diagram manager. Its GLSP client is the one the
3
+ // extension's frontend module binds for every diagram.
4
+
5
+ import type { ContainerConfiguration } from '@eclipse-glsp/client';
6
+ import type { GLSPDiagramLanguage } from '@eclipse-glsp/theia-integration/lib/common';
7
+ import {
8
+ AbstractHydraniumGlspDiagramConfiguration,
9
+ AbstractHydraniumGlspDiagramManager,
10
+ AbstractHydraniumGlspTheiaFrontendModule,
11
+ createGlspClientTheiaModule,
12
+ SkipClientContribution
13
+ } from '@hydranium/glsp-client-theia/browser';
14
+ import { type Container, ContainerModule, injectable } from '@theia/core/shared/inversify';
15
+ import { initialize__GRAMMAR__DiagramContainer } from '__CLIENT_PACKAGE__';
16
+ import { __GRAMMAR_ID_CONST___DIAGRAM_TYPE, __GRAMMAR_ID_CONST___LANGUAGE } from '__PROTOCOL_PACKAGE__';
17
+ import { __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID } from '../common/__PROJECT_ID__-glsp-contribution.js';
18
+ import '../../style/diagram.css';
19
+
20
+ export const __GRAMMAR__DiagramLanguage: GLSPDiagramLanguage = {
21
+ contributionId: __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID,
22
+ label: '__GRAMMAR__ Diagram',
23
+ diagramType: __GRAMMAR_ID_CONST___DIAGRAM_TYPE,
24
+ fileExtensions: [...__GRAMMAR_ID_CONST___LANGUAGE.fileExtensions]
25
+ };
26
+
27
+ // Theia's host bindings, logging to the language client's Output channel. Passed
28
+ // after GLSP's Theia modules, whose bindings it replaces; a rebind made earlier
29
+ // is overwritten without an error.
30
+ const theiaModule = new ContainerModule((bind, unbind, isBound, rebind) => {
31
+ createGlspClientTheiaModule({ bind, unbind, isBound, rebind }, { channelLogger: { channelName: '__NAME__' } });
32
+ });
33
+
34
+ @injectable()
35
+ export class __GRAMMAR__DiagramConfiguration extends AbstractHydraniumGlspDiagramConfiguration {
36
+ diagramType = __GRAMMAR_ID_CONST___DIAGRAM_TYPE;
37
+ // The LSP head already reports these problems.
38
+ protected override propagateMarkersToProblemsView = false;
39
+
40
+ configureContainer(container: Container, ...containerConfiguration: ContainerConfiguration): Container {
41
+ return initialize__GRAMMAR__DiagramContainer(container, ...containerConfiguration, theiaModule);
42
+ }
43
+ }
44
+
45
+ @injectable()
46
+ export class __GRAMMAR__DiagramManager extends AbstractHydraniumGlspDiagramManager {
47
+ protected readonly diagramLanguage = __GRAMMAR__DiagramLanguage;
48
+ protected readonly managerLabel = '__GRAMMAR__ Diagram Editor';
49
+
50
+ override get id(): string {
51
+ return '__GRAMMAR_ID__-diagram-manager';
52
+ }
53
+ }
54
+
55
+ export class __GRAMMAR__DiagramModule extends AbstractHydraniumGlspTheiaFrontendModule {
56
+ readonly diagramLanguage = __GRAMMAR__DiagramLanguage;
57
+ protected readonly diagramConfiguration = __GRAMMAR__DiagramConfiguration;
58
+ protected readonly diagramManager = __GRAMMAR__DiagramManager;
59
+
60
+ protected override bindClientContribution(): SkipClientContribution {
61
+ return SkipClientContribution;
62
+ }
63
+ }
64
+
65
+ export default new __GRAMMAR__DiagramModule();
@@ -0,0 +1,15 @@
1
+ // The reconnect hardening, browser side: messages sent while the socket is
2
+ // down are buffered and replayed, rather than the page reloading.
3
+ //
4
+ // A `frontendPreload` entry, not a `frontend` one: Theia builds its connection
5
+ // before any frontend module runs, so a rebind there would do nothing. The
6
+ // buffer size is the application's `theia.frontend.config.connectionBufferBytes`.
7
+
8
+ import { bindConnectionResilience } from '@hydranium/client-theia/browser';
9
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
10
+ import { ContainerModule } from '@theia/core/shared/inversify';
11
+
12
+ export default new ContainerModule((_bind, _unbind, isBound, rebind) => {
13
+ const bufferBytes = FrontendApplicationConfigProvider.get()['connectionBufferBytes'];
14
+ bindConnectionResilience(isBound, rebind, { bufferBytes: typeof bufferBytes === 'number' ? bufferBytes : undefined });
15
+ });
@@ -0,0 +1,15 @@
1
+ // The page's one connection to the data head. Everything that reads the data
2
+ // head takes a session off it: Theia opens one channel per service path.
3
+
4
+ import { DataConnectionWithEvents } from '@hydranium/protocol';
5
+ import { inject, injectable } from '@theia/core/shared/inversify';
6
+ import type { __NAME__Element } from '__PROTOCOL_PACKAGE__';
7
+ import { __NAME__DataPort } from './__PROJECT_ID__-data-port.js';
8
+
9
+ @injectable()
10
+ export class __NAME__DataConnection extends DataConnectionWithEvents<__NAME__Element> {
11
+ // The constructor, not a field: the port is needed before `super` returns.
12
+ constructor(@inject(__NAME__DataPort) port: __NAME__DataPort) {
13
+ super(port);
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ // The page's end of the data head: a channel to the backend, which forwards it
2
+ // to the port the servers extension answers.
3
+
4
+ import { ChannelDataPort } from '@hydranium/data-client-theia/browser';
5
+ import { DATA_SERVER_PATH } from '@hydranium/protocol';
6
+ import { injectable } from '@theia/core/shared/inversify';
7
+
8
+ @injectable()
9
+ export class __NAME__DataPort extends ChannelDataPort {
10
+ protected readonly servicePath = DATA_SERVER_PATH;
11
+ }
@@ -0,0 +1,13 @@
1
+ // The GLSP client every diagram of this extension shares, as they share one
2
+ // server: the frontend module binds it once, and each diagram module skips it.
3
+
4
+ import { HydraniumGlspClientContribution } from '@hydranium/glsp-client-theia/browser';
5
+ import { injectable } from '@theia/core/shared/inversify';
6
+ import { __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID } from '../common/__PROJECT_ID__-glsp-contribution.js';
7
+
8
+ @injectable()
9
+ export class __NAME__GlspClientContribution extends HydraniumGlspClientContribution {
10
+ constructor() {
11
+ super({ languageContributionId: __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID });
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ // `__NAME__: Show Model`: the current editor's file, text or diagram, read
2
+ // through the data head, and its root type shown in a notification.
3
+
4
+ import { Command, type CommandContribution, type CommandRegistry, MessageService, nls } from '@theia/core';
5
+ import { ApplicationShell, NavigatableWidget } from '@theia/core/lib/browser';
6
+ import { inject, injectable } from '@theia/core/shared/inversify';
7
+ import { ALL_LANGUAGES } from '__PROTOCOL_PACKAGE__';
8
+ import { __NAME__DataConnection } from './__PROJECT_ID__-data-connection.js';
9
+
10
+ export const SHOW_MODEL_COMMAND = Command.toLocalizedCommand(
11
+ { id: '__PROJECT_ID__.showModel', label: 'Show Model', category: '__NAME__' },
12
+ '__PROJECT_ID__/showModel',
13
+ '__PROJECT_ID__/category'
14
+ );
15
+
16
+ @injectable()
17
+ export class __NAME__ShowModelCommand implements CommandContribution {
18
+ @inject(__NAME__DataConnection) protected readonly connection: __NAME__DataConnection;
19
+ @inject(ApplicationShell) protected readonly shell: ApplicationShell;
20
+ @inject(MessageService) protected readonly messages: MessageService;
21
+
22
+ // Opened on the first run and kept: the connection ends it with the page.
23
+ protected session?: ReturnType<__NAME__DataConnection['createSession']>;
24
+
25
+ registerCommands(registry: CommandRegistry): void {
26
+ registry.registerCommand(SHOW_MODEL_COMMAND, { execute: () => this.showModel() });
27
+ }
28
+
29
+ protected async showModel(): Promise<void> {
30
+ // The main area's, so focus in a side panel such as the Explorer still finds the editor.
31
+ const widget = this.shell.getCurrentWidget('main');
32
+ const uri = NavigatableWidget.is(widget) ? widget.getResourceUri() : undefined;
33
+ if (
34
+ uri === undefined ||
35
+ !ALL_LANGUAGES.some(language => language.fileExtensions.some(extension => uri.path.base.endsWith(extension)))
36
+ ) {
37
+ this.messages.info(nls.localize('__PROJECT_ID__/showModel/noFile', 'Open a __NAME__ file first.'));
38
+ return;
39
+ }
40
+ this.session ??= this.connection.createSession('__PROJECT_ID__-show-model');
41
+ const root = await this.session.withOpenDocument({ uri: uri.toString() }, document => document.model?.root.$type);
42
+ this.messages.info(
43
+ nls.localize(
44
+ '__PROJECT_ID__/showModel/holds',
45
+ '{0} holds a {1}',
46
+ uri.path.base,
47
+ root ?? nls.localize('__PROJECT_ID__/showModel/noModel', 'document with no model')
48
+ )
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,3 @@
1
+ // The id the page's GLSP client and the backend's forwarder meet under.
2
+
3
+ export const __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID = '__PROJECT_ID__-glsp';
@@ -0,0 +1,12 @@
1
+ // The reconnect hardening, backend side: the session outlives a dropped socket
2
+ // for the application's `frontendConnectionTimeout`, and messages for the page
3
+ // wait in a buffer of `theia.backend.config.connectionBufferBytes`.
4
+
5
+ import { bindConnectionResilience } from '@hydranium/client-theia/node';
6
+ import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
7
+ import { ContainerModule } from '@theia/core/shared/inversify';
8
+
9
+ export default new ContainerModule((bind, _unbind, isBound, rebind) => {
10
+ const bufferBytes = BackendApplicationConfigProvider.get()['connectionBufferBytes'];
11
+ bindConnectionResilience(bind, isBound, rebind, { bufferBytes: typeof bufferBytes === 'number' ? bufferBytes : undefined });
12
+ });
@@ -0,0 +1,15 @@
1
+ // Forwards the page's data channel to the port the servers extension answers
2
+ // under its host command.
3
+
4
+ import { createDataServerConnectionContainerModule, DataServerConnectionHandler } from '@hydranium/data-client-theia/node';
5
+ import { injectable } from '@theia/core/shared/inversify';
6
+ import { __CONFIG_ROOT_CONST___DATA_SERVER_HOST_COMMAND } from '__PROTOCOL_PACKAGE__';
7
+
8
+ @injectable()
9
+ export class __NAME__DataServerConnectionHandler extends DataServerConnectionHandler {
10
+ constructor() {
11
+ super({ portCommand: __CONFIG_ROOT_CONST___DATA_SERVER_HOST_COMMAND });
12
+ }
13
+ }
14
+
15
+ export default createDataServerConnectionContainerModule(__NAME__DataServerConnectionHandler);
@@ -0,0 +1,16 @@
1
+ // Forwards the page's GLSP channel to the port the servers extension answers
2
+ // under its host command.
3
+
4
+ import { createGlspConnectionContainerModule, GlspServerConnectionHandler } from '@hydranium/glsp-client-theia/node';
5
+ import { injectable } from '@theia/core/shared/inversify';
6
+ import { __CONFIG_ROOT_CONST___GLSP_SERVER_HOST_COMMAND } from '__PROTOCOL_PACKAGE__';
7
+ import { __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID } from '../common/__PROJECT_ID__-glsp-contribution.js';
8
+
9
+ @injectable()
10
+ export class __NAME__GlspConnectionHandler extends GlspServerConnectionHandler {
11
+ constructor() {
12
+ super({ languageContributionId: __CONFIG_ROOT_CONST___GLSP_CONTRIBUTION_ID, portCommand: __CONFIG_ROOT_CONST___GLSP_SERVER_HOST_COMMAND });
13
+ }
14
+ }
15
+
16
+ export default createGlspConnectionContainerModule(__NAME__GlspConnectionHandler);