@hydranium/core 1.0.0-next.19 → 1.0.0-next.193

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 (588) hide show
  1. package/README.md +6 -2
  2. package/lib/documents/ast-document-manager.d.ts +222 -63
  3. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  4. package/lib/documents/ast-document-manager.js +250 -83
  5. package/lib/documents/ast-document-manager.js.map +1 -1
  6. package/lib/documents/client-ids.d.ts +5 -19
  7. package/lib/documents/client-ids.d.ts.map +1 -1
  8. package/lib/documents/client-ids.js +5 -19
  9. package/lib/documents/client-ids.js.map +1 -1
  10. package/lib/documents/client-session-errors.d.ts +15 -0
  11. package/lib/documents/client-session-errors.d.ts.map +1 -0
  12. package/lib/documents/client-session-errors.js +15 -0
  13. package/lib/documents/client-session-errors.js.map +1 -0
  14. package/lib/documents/client-session-registry.d.ts +138 -0
  15. package/lib/documents/client-session-registry.d.ts.map +1 -0
  16. package/lib/documents/client-session-registry.js +187 -0
  17. package/lib/documents/client-session-registry.js.map +1 -0
  18. package/lib/documents/file-system-task-queue.d.ts +49 -0
  19. package/lib/documents/file-system-task-queue.d.ts.map +1 -0
  20. package/lib/documents/file-system-task-queue.js +34 -0
  21. package/lib/documents/file-system-task-queue.js.map +1 -0
  22. package/lib/documents/hydranium-text-documents.d.ts +523 -101
  23. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  24. package/lib/documents/hydranium-text-documents.js +961 -177
  25. package/lib/documents/hydranium-text-documents.js.map +1 -1
  26. package/lib/documents/index.d.ts +5 -0
  27. package/lib/documents/index.d.ts.map +1 -1
  28. package/lib/documents/index.js +5 -0
  29. package/lib/documents/index.js.map +1 -1
  30. package/lib/documents/language-client-text-shadow.d.ts +41 -9
  31. package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
  32. package/lib/documents/language-client-text-shadow.js +56 -11
  33. package/lib/documents/language-client-text-shadow.js.map +1 -1
  34. package/lib/documents/model-ledger.d.ts +47 -0
  35. package/lib/documents/model-ledger.d.ts.map +1 -0
  36. package/lib/documents/model-ledger.js +30 -0
  37. package/lib/documents/model-ledger.js.map +1 -0
  38. package/lib/documents/self-save-registry.d.ts +37 -6
  39. package/lib/documents/self-save-registry.d.ts.map +1 -1
  40. package/lib/documents/self-save-registry.js +28 -15
  41. package/lib/documents/self-save-registry.js.map +1 -1
  42. package/lib/documents/version-sync-service.d.ts +134 -0
  43. package/lib/documents/version-sync-service.d.ts.map +1 -0
  44. package/lib/documents/version-sync-service.js +171 -0
  45. package/lib/documents/version-sync-service.js.map +1 -0
  46. package/lib/index.d.ts +3 -0
  47. package/lib/index.d.ts.map +1 -1
  48. package/lib/index.js +6 -0
  49. package/lib/index.js.map +1 -1
  50. package/lib/langium/ast-extension/ast-extension-service.d.ts.map +1 -1
  51. package/lib/langium/ast-extension/ast-extension-service.js.map +1 -1
  52. package/lib/langium/ast-extension/ast-node-builder.d.ts +32 -9
  53. package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
  54. package/lib/langium/ast-extension/ast-node-builder.js +60 -22
  55. package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
  56. package/lib/langium/bootstrap.d.ts.map +1 -1
  57. package/lib/langium/bootstrap.js +32 -9
  58. package/lib/langium/bootstrap.js.map +1 -1
  59. package/lib/langium/build-phase-pass/build-phase-pass-service.d.ts.map +1 -1
  60. package/lib/langium/build-phase-pass/build-phase-pass-service.js.map +1 -1
  61. package/lib/langium/config/configuration-provider.d.ts +81 -0
  62. package/lib/langium/config/configuration-provider.d.ts.map +1 -0
  63. package/lib/langium/config/configuration-provider.js +112 -0
  64. package/lib/langium/config/configuration-provider.js.map +1 -0
  65. package/lib/langium/config/index.d.ts +1 -0
  66. package/lib/langium/config/index.d.ts.map +1 -1
  67. package/lib/langium/config/index.js +1 -0
  68. package/lib/langium/config/index.js.map +1 -1
  69. package/lib/langium/config/settings.js.map +1 -1
  70. package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts.map +1 -1
  71. package/lib/langium/diagnostics/hydranium-langium-profiler.js.map +1 -1
  72. package/lib/langium/diagnostics/logger.d.ts +10 -3
  73. package/lib/langium/diagnostics/logger.d.ts.map +1 -1
  74. package/lib/langium/diagnostics/logger.js +12 -5
  75. package/lib/langium/diagnostics/logger.js.map +1 -1
  76. package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
  77. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  78. package/lib/langium/diagnostics/lsp-logger.js +56 -13
  79. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  80. package/lib/langium/diagnostics/server-tracer.d.ts.map +1 -1
  81. package/lib/langium/diagnostics/server-tracer.js.map +1 -1
  82. package/lib/langium/document-builder/build-pipeline-integration.d.ts +21 -1
  83. package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
  84. package/lib/langium/document-builder/build-pipeline-integration.js +32 -5
  85. package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
  86. package/lib/langium/document-builder/build-session.d.ts +93 -0
  87. package/lib/langium/document-builder/build-session.d.ts.map +1 -0
  88. package/lib/langium/document-builder/build-session.js +72 -0
  89. package/lib/langium/document-builder/build-session.js.map +1 -0
  90. package/lib/langium/document-builder/document-builder.d.ts +382 -29
  91. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  92. package/lib/langium/document-builder/document-builder.js +659 -61
  93. package/lib/langium/document-builder/document-builder.js.map +1 -1
  94. package/lib/langium/document-builder/index.d.ts +1 -0
  95. package/lib/langium/document-builder/index.d.ts.map +1 -1
  96. package/lib/langium/document-builder/index.js +1 -0
  97. package/lib/langium/document-builder/index.js.map +1 -1
  98. package/lib/langium/documentation/comment-provider.d.ts.map +1 -1
  99. package/lib/langium/documentation/comment-provider.js.map +1 -1
  100. package/lib/langium/integration-services.d.ts +45 -1
  101. package/lib/langium/integration-services.d.ts.map +1 -1
  102. package/lib/langium/integration-services.js +10 -5
  103. package/lib/langium/integration-services.js.map +1 -1
  104. package/lib/langium/integrity/integrity-rule.d.ts +13 -5
  105. package/lib/langium/integrity/integrity-rule.d.ts.map +1 -1
  106. package/lib/langium/integrity/integrity-rule.js.map +1 -1
  107. package/lib/langium/integrity/integrity-service.d.ts +58 -13
  108. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  109. package/lib/langium/integrity/integrity-service.js +204 -29
  110. package/lib/langium/integrity/integrity-service.js.map +1 -1
  111. package/lib/langium/keys/containment.d.ts +64 -0
  112. package/lib/langium/keys/containment.d.ts.map +1 -0
  113. package/lib/langium/keys/containment.js +53 -0
  114. package/lib/langium/keys/containment.js.map +1 -0
  115. package/lib/langium/keys/index.d.ts +1 -0
  116. package/lib/langium/keys/index.d.ts.map +1 -1
  117. package/lib/langium/keys/index.js +1 -0
  118. package/lib/langium/keys/index.js.map +1 -1
  119. package/lib/langium/keys/name-based-key-provider.d.ts +4 -0
  120. package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
  121. package/lib/langium/keys/name-based-key-provider.js +4 -0
  122. package/lib/langium/keys/name-based-key-provider.js.map +1 -1
  123. package/lib/langium/keys/positional-key-provider.d.ts.map +1 -1
  124. package/lib/langium/keys/positional-key-provider.js.map +1 -1
  125. package/lib/langium/labeling/label-provider.d.ts.map +1 -1
  126. package/lib/langium/labeling/label-provider.js.map +1 -1
  127. package/lib/langium/language-module.d.ts +27 -2
  128. package/lib/langium/language-module.d.ts.map +1 -1
  129. package/lib/langium/language-module.js +16 -2
  130. package/lib/langium/language-module.js.map +1 -1
  131. package/lib/langium/model-service/client-session.d.ts +330 -0
  132. package/lib/langium/model-service/client-session.d.ts.map +1 -0
  133. package/lib/langium/model-service/client-session.js +418 -0
  134. package/lib/langium/model-service/client-session.js.map +1 -0
  135. package/lib/langium/model-service/index.d.ts +1 -0
  136. package/lib/langium/model-service/index.d.ts.map +1 -1
  137. package/lib/langium/model-service/index.js +1 -0
  138. package/lib/langium/model-service/index.js.map +1 -1
  139. package/lib/langium/model-service/model-service.d.ts +353 -253
  140. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  141. package/lib/langium/model-service/model-service.js +361 -408
  142. package/lib/langium/model-service/model-service.js.map +1 -1
  143. package/lib/langium/module.d.ts +150 -35
  144. package/lib/langium/module.d.ts.map +1 -1
  145. package/lib/langium/module.js +50 -35
  146. package/lib/langium/module.js.map +1 -1
  147. package/lib/langium/naming/name-provider.d.ts +25 -4
  148. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  149. package/lib/langium/naming/name-provider.js +2 -1
  150. package/lib/langium/naming/name-provider.js.map +1 -1
  151. package/lib/langium/naming/name-separator-validation.d.ts +23 -0
  152. package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
  153. package/lib/langium/naming/name-separator-validation.js +30 -1
  154. package/lib/langium/naming/name-separator-validation.js.map +1 -1
  155. package/lib/langium/project/abstract-project-manager.d.ts.map +1 -1
  156. package/lib/langium/project/abstract-project-manager.js +9 -2
  157. package/lib/langium/project/abstract-project-manager.js.map +1 -1
  158. package/lib/langium/project/project-change-event.d.ts +2 -2
  159. package/lib/langium/project/single-project-manager.d.ts.map +1 -1
  160. package/lib/langium/project/single-project-manager.js.map +1 -1
  161. package/lib/langium/residency/cst-residency-service.d.ts +31 -11
  162. package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
  163. package/lib/langium/residency/cst-residency-service.js +23 -57
  164. package/lib/langium/residency/cst-residency-service.js.map +1 -1
  165. package/lib/langium/scope/ast-node-description-provider.d.ts.map +1 -1
  166. package/lib/langium/scope/ast-node-description-provider.js.map +1 -1
  167. package/lib/langium/scope/hydranium-scope-computation.d.ts.map +1 -1
  168. package/lib/langium/scope/hydranium-scope-computation.js.map +1 -1
  169. package/lib/langium/scope/hydranium-scope-provider.d.ts +48 -13
  170. package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
  171. package/lib/langium/scope/hydranium-scope-provider.js +56 -20
  172. package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
  173. package/lib/langium/scope/reference-builder.d.ts.map +1 -1
  174. package/lib/langium/scope/reference-builder.js.map +1 -1
  175. package/lib/langium/scope/reference-candidate-provider.d.ts.map +1 -1
  176. package/lib/langium/scope/reference-candidate-provider.js.map +1 -1
  177. package/lib/langium/scope/scope-extension-service.d.ts.map +1 -1
  178. package/lib/langium/scope/scope-extension-service.js.map +1 -1
  179. package/lib/langium/serialization/abstract-serializer.d.ts +11 -20
  180. package/lib/langium/serialization/abstract-serializer.d.ts.map +1 -1
  181. package/lib/langium/serialization/abstract-serializer.js +12 -21
  182. package/lib/langium/serialization/abstract-serializer.js.map +1 -1
  183. package/lib/langium/serialization/json-serializer.d.ts +1 -1
  184. package/lib/langium/serialization/json-serializer.d.ts.map +1 -1
  185. package/lib/langium/serialization/json-serializer.js.map +1 -1
  186. package/lib/langium/serialization/yaml-serializer.d.ts.map +1 -1
  187. package/lib/langium/serialization/yaml-serializer.js.map +1 -1
  188. package/lib/langium/service-registry.d.ts +15 -1
  189. package/lib/langium/service-registry.d.ts.map +1 -1
  190. package/lib/langium/service-registry.js +16 -0
  191. package/lib/langium/service-registry.js.map +1 -1
  192. package/lib/langium/shared-services.d.ts +21 -4
  193. package/lib/langium/shared-services.d.ts.map +1 -1
  194. package/lib/langium/shared-services.js.map +1 -1
  195. package/lib/langium/transfer/transfer-encoder.d.ts +75 -23
  196. package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
  197. package/lib/langium/transfer/transfer-encoder.js +44 -21
  198. package/lib/langium/transfer/transfer-encoder.js.map +1 -1
  199. package/lib/langium/trivia/comment-preserver.d.ts +423 -0
  200. package/lib/langium/trivia/comment-preserver.d.ts.map +1 -0
  201. package/lib/langium/trivia/comment-preserver.js +906 -0
  202. package/lib/langium/trivia/comment-preserver.js.map +1 -0
  203. package/lib/langium/trivia/document-ending-preserver.d.ts +43 -0
  204. package/lib/langium/trivia/document-ending-preserver.d.ts.map +1 -0
  205. package/lib/langium/trivia/document-ending-preserver.js +48 -0
  206. package/lib/langium/trivia/document-ending-preserver.js.map +1 -0
  207. package/lib/langium/trivia/index.d.ts +14 -0
  208. package/lib/langium/trivia/index.d.ts.map +1 -0
  209. package/lib/langium/trivia/index.js +14 -0
  210. package/lib/langium/trivia/index.js.map +1 -0
  211. package/lib/langium/trivia/trivia-contribution.d.ts +37 -0
  212. package/lib/langium/trivia/trivia-contribution.d.ts.map +1 -0
  213. package/lib/langium/trivia/trivia-contribution.js +10 -0
  214. package/lib/langium/trivia/trivia-contribution.js.map +1 -0
  215. package/lib/langium/trivia/trivia-preserver.d.ts +50 -0
  216. package/lib/langium/trivia/trivia-preserver.d.ts.map +1 -0
  217. package/lib/langium/trivia/trivia-preserver.js +10 -0
  218. package/lib/langium/trivia/trivia-preserver.js.map +1 -0
  219. package/lib/langium/trivia/trivia-service.d.ts +70 -0
  220. package/lib/langium/trivia/trivia-service.d.ts.map +1 -0
  221. package/lib/langium/trivia/trivia-service.js +56 -0
  222. package/lib/langium/trivia/trivia-service.js.map +1 -0
  223. package/lib/langium/update-rewrite/normalize-empty-strings.d.ts.map +1 -1
  224. package/lib/langium/update-rewrite/update-rewrite-service.d.ts.map +1 -1
  225. package/lib/langium/update-rewrite/update-rewrite-service.js.map +1 -1
  226. package/lib/langium/update-rewrite/update-rewrite.d.ts +1 -1
  227. package/lib/langium/validation/document-validator.d.ts +294 -10
  228. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  229. package/lib/langium/validation/document-validator.js +448 -5
  230. package/lib/langium/validation/document-validator.js.map +1 -1
  231. package/lib/langium/validation/validation-contribution-collector.d.ts.map +1 -1
  232. package/lib/langium/validation/validation-contribution-collector.js.map +1 -1
  233. package/lib/langium/workspace/document-uri-policy.d.ts +17 -12
  234. package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
  235. package/lib/langium/workspace/document-uri-policy.js +10 -9
  236. package/lib/langium/workspace/document-uri-policy.js.map +1 -1
  237. package/lib/langium/workspace/file-not-found.d.ts +17 -0
  238. package/lib/langium/workspace/file-not-found.d.ts.map +1 -0
  239. package/lib/langium/workspace/file-not-found.js +20 -0
  240. package/lib/langium/workspace/file-not-found.js.map +1 -0
  241. package/lib/langium/workspace/file-system-provider.d.ts +84 -8
  242. package/lib/langium/workspace/file-system-provider.d.ts.map +1 -1
  243. package/lib/langium/workspace/file-system-provider.js +93 -14
  244. package/lib/langium/workspace/file-system-provider.js.map +1 -1
  245. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +56 -1
  246. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
  247. package/lib/langium/workspace/hydranium-langium-document-factory.js +79 -0
  248. package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
  249. package/lib/langium/workspace/hydranium-workspace-lock.d.ts +43 -1
  250. package/lib/langium/workspace/hydranium-workspace-lock.d.ts.map +1 -1
  251. package/lib/langium/workspace/hydranium-workspace-lock.js +58 -1
  252. package/lib/langium/workspace/hydranium-workspace-lock.js.map +1 -1
  253. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +81 -8
  254. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  255. package/lib/langium/workspace/hydranium-workspace-manager.js +128 -10
  256. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  257. package/lib/langium/workspace/in-memory-file-system-provider.d.ts +25 -2
  258. package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
  259. package/lib/langium/workspace/in-memory-file-system-provider.js +41 -15
  260. package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
  261. package/lib/langium/workspace/index-manager.d.ts.map +1 -1
  262. package/lib/langium/workspace/index-manager.js.map +1 -1
  263. package/lib/langium/workspace/initialize-workspace.d.ts +31 -2
  264. package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
  265. package/lib/langium/workspace/initialize-workspace.js +13 -5
  266. package/lib/langium/workspace/initialize-workspace.js.map +1 -1
  267. package/lib/langium/workspace/langium-documents.d.ts +86 -17
  268. package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
  269. package/lib/langium/workspace/langium-documents.js +101 -29
  270. package/lib/langium/workspace/langium-documents.js.map +1 -1
  271. package/lib/langium/workspace/persistent-file-system-provider.d.ts +0 -7
  272. package/lib/langium/workspace/persistent-file-system-provider.d.ts.map +1 -1
  273. package/lib/langium/workspace/persistent-file-system-provider.js +0 -7
  274. package/lib/langium/workspace/persistent-file-system-provider.js.map +1 -1
  275. package/lib/langium/workspace/virtual-document.d.ts +48 -15
  276. package/lib/langium/workspace/virtual-document.d.ts.map +1 -1
  277. package/lib/langium/workspace/virtual-document.js +78 -18
  278. package/lib/langium/workspace/virtual-document.js.map +1 -1
  279. package/lib/langium/workspace/write-lock-scope.d.ts +20 -10
  280. package/lib/langium/workspace/write-lock-scope.d.ts.map +1 -1
  281. package/lib/langium/workspace/write-lock-scope.js +12 -4
  282. package/lib/langium/workspace/write-lock-scope.js.map +1 -1
  283. package/lib/locale/index.d.ts +10 -0
  284. package/lib/locale/index.d.ts.map +1 -0
  285. package/lib/locale/index.js +10 -0
  286. package/lib/locale/index.js.map +1 -0
  287. package/lib/locale/server-locale.d.ts +73 -0
  288. package/lib/locale/server-locale.d.ts.map +1 -0
  289. package/lib/locale/server-locale.js +61 -0
  290. package/lib/locale/server-locale.js.map +1 -0
  291. package/lib/lsp/completion/hydranium-completion-provider.d.ts +44 -2
  292. package/lib/lsp/completion/hydranium-completion-provider.d.ts.map +1 -1
  293. package/lib/lsp/completion/hydranium-completion-provider.js +68 -0
  294. package/lib/lsp/completion/hydranium-completion-provider.js.map +1 -1
  295. package/lib/lsp/connection-features.d.ts +23 -0
  296. package/lib/lsp/connection-features.d.ts.map +1 -0
  297. package/lib/lsp/connection-features.js +60 -0
  298. package/lib/lsp/connection-features.js.map +1 -0
  299. package/lib/lsp/diagnostics-connection.d.ts +27 -0
  300. package/lib/lsp/diagnostics-connection.d.ts.map +1 -0
  301. package/lib/lsp/diagnostics-connection.js +53 -0
  302. package/lib/lsp/diagnostics-connection.js.map +1 -0
  303. package/lib/lsp/hydranium-document-update-handler.d.ts +90 -79
  304. package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
  305. package/lib/lsp/hydranium-document-update-handler.js +153 -99
  306. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  307. package/lib/lsp/index.d.ts +3 -1
  308. package/lib/lsp/index.d.ts.map +1 -1
  309. package/lib/lsp/index.js +3 -1
  310. package/lib/lsp/index.js.map +1 -1
  311. package/lib/lsp/lsp-latency.d.ts +39 -0
  312. package/lib/lsp/lsp-latency.d.ts.map +1 -0
  313. package/lib/lsp/lsp-latency.js +58 -0
  314. package/lib/lsp/lsp-latency.js.map +1 -0
  315. package/lib/lsp/semantic-token-provider.d.ts +34 -1
  316. package/lib/lsp/semantic-token-provider.d.ts.map +1 -1
  317. package/lib/lsp/semantic-token-provider.js +55 -8
  318. package/lib/lsp/semantic-token-provider.js.map +1 -1
  319. package/lib/lsp/shared-module.d.ts +4 -8
  320. package/lib/lsp/shared-module.d.ts.map +1 -1
  321. package/lib/lsp/shared-module.js.map +1 -1
  322. package/lib/lsp/start-language-server.d.ts +8 -7
  323. package/lib/lsp/start-language-server.d.ts.map +1 -1
  324. package/lib/lsp/start-language-server.js +13 -7
  325. package/lib/lsp/start-language-server.js.map +1 -1
  326. package/lib/messages/carriers.d.ts +54 -0
  327. package/lib/messages/carriers.d.ts.map +1 -0
  328. package/lib/messages/carriers.js +62 -0
  329. package/lib/messages/carriers.js.map +1 -0
  330. package/lib/messages/index.d.ts +25 -0
  331. package/lib/messages/index.d.ts.map +1 -0
  332. package/lib/messages/index.js +25 -0
  333. package/lib/messages/index.js.map +1 -0
  334. package/lib/messages/renderer.d.ts +142 -0
  335. package/lib/messages/renderer.d.ts.map +1 -0
  336. package/lib/messages/renderer.js +165 -0
  337. package/lib/messages/renderer.js.map +1 -0
  338. package/lib/node/event-loop-monitor.js.map +1 -1
  339. package/lib/node/heap-ceiling.d.ts +61 -0
  340. package/lib/node/heap-ceiling.d.ts.map +1 -0
  341. package/lib/node/heap-ceiling.js +72 -0
  342. package/lib/node/heap-ceiling.js.map +1 -0
  343. package/lib/node/index.d.ts +1 -0
  344. package/lib/node/index.d.ts.map +1 -1
  345. package/lib/node/index.js +1 -0
  346. package/lib/node/index.js.map +1 -1
  347. package/lib/node/latency-from-env.d.ts +1 -1
  348. package/lib/node/latency-from-env.js +1 -1
  349. package/lib/node/lint-grammar.js.map +1 -1
  350. package/lib/node/measure-memory.d.ts.map +1 -1
  351. package/lib/node/measure-memory.js +1 -0
  352. package/lib/node/measure-memory.js.map +1 -1
  353. package/lib/node/memory-monitor.js.map +1 -1
  354. package/lib/node/node-file-system-provider.d.ts +39 -4
  355. package/lib/node/node-file-system-provider.d.ts.map +1 -1
  356. package/lib/node/node-file-system-provider.js +109 -49
  357. package/lib/node/node-file-system-provider.js.map +1 -1
  358. package/lib/node/process-memory.js.map +1 -1
  359. package/lib/node/profile-capture.d.ts +8 -2
  360. package/lib/node/profile-capture.d.ts.map +1 -1
  361. package/lib/node/profile-capture.js.map +1 -1
  362. package/lib/node/profile-digest.js.map +1 -1
  363. package/lib/node/profiling-run.d.ts +13 -3
  364. package/lib/node/profiling-run.d.ts.map +1 -1
  365. package/lib/node/profiling-run.js +17 -8
  366. package/lib/node/profiling-run.js.map +1 -1
  367. package/lib/node/rename-over-open-readers.d.ts +52 -0
  368. package/lib/node/rename-over-open-readers.d.ts.map +1 -0
  369. package/lib/node/rename-over-open-readers.js +66 -0
  370. package/lib/node/rename-over-open-readers.js.map +1 -0
  371. package/lib/node/socket-launcher.d.ts +16 -1
  372. package/lib/node/socket-launcher.d.ts.map +1 -1
  373. package/lib/node/socket-launcher.js +4 -3
  374. package/lib/node/socket-launcher.js.map +1 -1
  375. package/lib/node/stdio-launcher.d.ts +1 -1
  376. package/lib/node/stdio-launcher.d.ts.map +1 -1
  377. package/lib/node/stdio-launcher.js +1 -1
  378. package/lib/node/stdio-launcher.js.map +1 -1
  379. package/lib/testing/document-uri-policy-conformance.js.map +1 -1
  380. package/lib/testing/fake-description.js.map +1 -1
  381. package/lib/testing/fake-document.d.ts +16 -4
  382. package/lib/testing/fake-document.d.ts.map +1 -1
  383. package/lib/testing/fake-document.js +18 -3
  384. package/lib/testing/fake-document.js.map +1 -1
  385. package/lib/testing/index.d.ts +1 -1
  386. package/lib/testing/index.d.ts.map +1 -1
  387. package/lib/testing/index.js +2 -1
  388. package/lib/testing/index.js.map +1 -1
  389. package/lib/testing/langium-test-helpers.d.ts.map +1 -1
  390. package/lib/testing/langium-test-helpers.js +1 -0
  391. package/lib/testing/langium-test-helpers.js.map +1 -1
  392. package/lib/testing/make-noop-language-services.js.map +1 -1
  393. package/lib/testing/make-noop-shared-services.d.ts +14 -0
  394. package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
  395. package/lib/testing/make-noop-shared-services.js +28 -2
  396. package/lib/testing/make-noop-shared-services.js.map +1 -1
  397. package/lib/testing/make-test-services.d.ts +69 -14
  398. package/lib/testing/make-test-services.d.ts.map +1 -1
  399. package/lib/testing/make-test-services.js +39 -10
  400. package/lib/testing/make-test-services.js.map +1 -1
  401. package/lib/testing/make-test-tracer.js.map +1 -1
  402. package/lib/testing/node/golden-corpus.js.map +1 -1
  403. package/lib/testing/node/index.d.ts +1 -0
  404. package/lib/testing/node/index.d.ts.map +1 -1
  405. package/lib/testing/node/index.js +1 -0
  406. package/lib/testing/node/index.js.map +1 -1
  407. package/lib/testing/node/lsp-harness.d.ts.map +1 -1
  408. package/lib/testing/node/lsp-harness.js +2 -0
  409. package/lib/testing/node/lsp-harness.js.map +1 -1
  410. package/lib/testing/node/lsp-server-connection.d.ts +11 -3
  411. package/lib/testing/node/lsp-server-connection.d.ts.map +1 -1
  412. package/lib/testing/node/lsp-server-connection.js +42 -9
  413. package/lib/testing/node/lsp-server-connection.js.map +1 -1
  414. package/lib/testing/node/scratch-workspace.d.ts.map +1 -1
  415. package/lib/testing/node/scratch-workspace.js +10 -5
  416. package/lib/testing/node/scratch-workspace.js.map +1 -1
  417. package/lib/testing/node/spawned-server.d.ts +14 -0
  418. package/lib/testing/node/spawned-server.d.ts.map +1 -1
  419. package/lib/testing/node/spawned-server.js +33 -1
  420. package/lib/testing/node/spawned-server.js.map +1 -1
  421. package/lib/testing/node/unhandled-rejections.d.ts +20 -0
  422. package/lib/testing/node/unhandled-rejections.d.ts.map +1 -0
  423. package/lib/testing/node/unhandled-rejections.js +32 -0
  424. package/lib/testing/node/unhandled-rejections.js.map +1 -0
  425. package/lib/testing/playwright/browser-capture-bridge.js.map +1 -1
  426. package/lib/testing/playwright/e2e-profiling.js.map +1 -1
  427. package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
  428. package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
  429. package/lib/testing/playwright/flaky-network-proxy.js +164 -0
  430. package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
  431. package/lib/testing/playwright/index.d.ts +1 -0
  432. package/lib/testing/playwright/index.d.ts.map +1 -1
  433. package/lib/testing/playwright/index.js +1 -0
  434. package/lib/testing/playwright/index.js.map +1 -1
  435. package/lib/testing/playwright/server-log-capture.d.ts +24 -8
  436. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  437. package/lib/testing/playwright/server-log-capture.js +27 -6
  438. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  439. package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
  440. package/lib/testing/playwright/server-log-rename-reporter.js +10 -1
  441. package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
  442. package/lib/testing/run-update-pipeline.d.ts +4 -4
  443. package/lib/testing/run-update-pipeline.d.ts.map +1 -1
  444. package/lib/testing/stub-ast-document-manager.d.ts +11 -9
  445. package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
  446. package/lib/testing/stub-ast-document-manager.js +25 -9
  447. package/lib/testing/stub-ast-document-manager.js.map +1 -1
  448. package/lib/testing/stub-document-builder.d.ts +26 -11
  449. package/lib/testing/stub-document-builder.d.ts.map +1 -1
  450. package/lib/testing/stub-document-builder.js +57 -4
  451. package/lib/testing/stub-document-builder.js.map +1 -1
  452. package/lib/testing/stub-hydranium-text-documents.d.ts +22 -7
  453. package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
  454. package/lib/testing/stub-hydranium-text-documents.js +97 -15
  455. package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
  456. package/lib/testing/stub-index-manager.js.map +1 -1
  457. package/lib/testing/stub-langium-documents.d.ts +3 -2
  458. package/lib/testing/stub-langium-documents.d.ts.map +1 -1
  459. package/lib/testing/stub-langium-documents.js.map +1 -1
  460. package/lib/testing/stub-model-service.d.ts +7 -6
  461. package/lib/testing/stub-model-service.d.ts.map +1 -1
  462. package/lib/testing/stub-model-service.js +4 -4
  463. package/lib/testing/stub-model-service.js.map +1 -1
  464. package/lib/testing/stub-project-manager.js.map +1 -1
  465. package/lib/testing/stub-service-registry.js.map +1 -1
  466. package/lib/util/connection-liveness.d.ts +7 -6
  467. package/lib/util/connection-liveness.d.ts.map +1 -1
  468. package/lib/util/connection-liveness.js +35 -14
  469. package/lib/util/connection-liveness.js.map +1 -1
  470. package/lib/util/environment.d.ts.map +1 -1
  471. package/lib/util/environment.js.map +1 -1
  472. package/lib/util/registry.d.ts.map +1 -1
  473. package/package.json +35 -26
  474. package/src/documents/ast-document-manager.ts +395 -126
  475. package/src/documents/client-ids.ts +5 -22
  476. package/src/documents/client-session-errors.ts +24 -0
  477. package/src/documents/client-session-registry.ts +240 -0
  478. package/src/documents/file-system-task-queue.ts +68 -0
  479. package/src/documents/hydranium-text-documents.ts +1175 -225
  480. package/src/documents/index.ts +5 -0
  481. package/src/documents/language-client-text-shadow.ts +59 -11
  482. package/src/documents/model-ledger.ts +66 -0
  483. package/src/documents/self-save-registry.ts +52 -13
  484. package/src/documents/version-sync-service.ts +267 -0
  485. package/src/index.ts +6 -0
  486. package/src/langium/ast-extension/ast-node-builder.ts +66 -22
  487. package/src/langium/bootstrap.ts +32 -9
  488. package/src/langium/config/configuration-provider.ts +119 -0
  489. package/src/langium/config/index.ts +1 -0
  490. package/src/langium/diagnostics/logger.ts +12 -5
  491. package/src/langium/diagnostics/lsp-logger.ts +58 -12
  492. package/src/langium/document-builder/build-pipeline-integration.ts +50 -5
  493. package/src/langium/document-builder/build-session.ts +87 -0
  494. package/src/langium/document-builder/document-builder.ts +776 -59
  495. package/src/langium/document-builder/index.ts +1 -0
  496. package/src/langium/integration-services.ts +67 -6
  497. package/src/langium/integrity/integrity-rule.ts +13 -5
  498. package/src/langium/integrity/integrity-service.ts +220 -30
  499. package/src/langium/keys/containment.ts +87 -0
  500. package/src/langium/keys/index.ts +1 -0
  501. package/src/langium/keys/name-based-key-provider.ts +4 -0
  502. package/src/langium/language-module.ts +42 -4
  503. package/src/langium/model-service/client-session.ts +649 -0
  504. package/src/langium/model-service/index.ts +1 -0
  505. package/src/langium/model-service/model-service.ts +570 -433
  506. package/src/langium/module.ts +201 -64
  507. package/src/langium/naming/name-provider.ts +27 -5
  508. package/src/langium/naming/name-separator-validation.ts +34 -5
  509. package/src/langium/project/abstract-project-manager.ts +9 -1
  510. package/src/langium/project/project-change-event.ts +2 -2
  511. package/src/langium/residency/cst-residency-service.ts +46 -15
  512. package/src/langium/scope/hydranium-scope-provider.ts +60 -22
  513. package/src/langium/serialization/abstract-serializer.ts +13 -32
  514. package/src/langium/service-registry.ts +19 -2
  515. package/src/langium/shared-services.ts +25 -4
  516. package/src/langium/transfer/transfer-encoder.ts +102 -34
  517. package/src/langium/trivia/comment-preserver.ts +1100 -0
  518. package/src/langium/trivia/document-ending-preserver.ts +55 -0
  519. package/src/langium/trivia/index.ts +14 -0
  520. package/src/langium/trivia/trivia-contribution.ts +39 -0
  521. package/src/langium/trivia/trivia-preserver.ts +54 -0
  522. package/src/langium/trivia/trivia-service.ts +99 -0
  523. package/src/langium/update-rewrite/update-rewrite.ts +1 -1
  524. package/src/langium/validation/document-validator.ts +537 -11
  525. package/src/langium/workspace/document-uri-policy.ts +17 -12
  526. package/src/langium/workspace/file-not-found.ts +23 -0
  527. package/src/langium/workspace/file-system-provider.ts +153 -16
  528. package/src/langium/workspace/hydranium-langium-document-factory.ts +117 -0
  529. package/src/langium/workspace/hydranium-workspace-lock.ts +86 -2
  530. package/src/langium/workspace/hydranium-workspace-manager.ts +153 -13
  531. package/src/langium/workspace/in-memory-file-system-provider.ts +47 -16
  532. package/src/langium/workspace/initialize-workspace.ts +43 -5
  533. package/src/langium/workspace/langium-documents.ts +124 -33
  534. package/src/langium/workspace/persistent-file-system-provider.ts +0 -7
  535. package/src/langium/workspace/virtual-document.ts +96 -19
  536. package/src/langium/workspace/write-lock-scope.ts +24 -11
  537. package/src/locale/index.ts +10 -0
  538. package/src/locale/server-locale.ts +86 -0
  539. package/src/lsp/completion/hydranium-completion-provider.ts +82 -2
  540. package/src/lsp/connection-features.ts +67 -0
  541. package/src/lsp/diagnostics-connection.ts +56 -0
  542. package/src/lsp/hydranium-document-update-handler.ts +185 -106
  543. package/src/lsp/index.ts +3 -1
  544. package/src/lsp/lsp-latency.ts +62 -0
  545. package/src/lsp/semantic-token-provider.ts +74 -7
  546. package/src/lsp/shared-module.ts +4 -8
  547. package/src/lsp/start-language-server.ts +13 -7
  548. package/src/messages/carriers.ts +78 -0
  549. package/src/messages/index.ts +36 -0
  550. package/src/messages/renderer.ts +212 -0
  551. package/src/node/heap-ceiling.ts +108 -0
  552. package/src/node/index.ts +1 -0
  553. package/src/node/latency-from-env.ts +1 -1
  554. package/src/node/measure-memory.ts +1 -0
  555. package/src/node/node-file-system-provider.ts +122 -51
  556. package/src/node/profile-capture.ts +8 -2
  557. package/src/node/profiling-run.ts +30 -8
  558. package/src/node/rename-over-open-readers.ts +78 -0
  559. package/src/node/socket-launcher.ts +19 -4
  560. package/src/node/stdio-launcher.ts +1 -2
  561. package/src/testing/fake-document.ts +24 -5
  562. package/src/testing/index.ts +2 -1
  563. package/src/testing/langium-test-helpers.ts +1 -0
  564. package/src/testing/make-noop-shared-services.ts +50 -2
  565. package/src/testing/make-test-services.ts +120 -31
  566. package/src/testing/node/index.ts +1 -0
  567. package/src/testing/node/lsp-harness.ts +2 -0
  568. package/src/testing/node/lsp-server-connection.ts +61 -11
  569. package/src/testing/node/scratch-workspace.ts +10 -5
  570. package/src/testing/node/spawned-server.ts +35 -1
  571. package/src/testing/node/unhandled-rejections.ts +31 -0
  572. package/src/testing/playwright/flaky-network-proxy.ts +240 -0
  573. package/src/testing/playwright/index.ts +1 -0
  574. package/src/testing/playwright/server-log-capture.ts +45 -11
  575. package/src/testing/playwright/server-log-rename-reporter.ts +12 -1
  576. package/src/testing/run-update-pipeline.ts +4 -4
  577. package/src/testing/stub-ast-document-manager.ts +40 -23
  578. package/src/testing/stub-document-builder.ts +83 -17
  579. package/src/testing/stub-hydranium-text-documents.ts +133 -23
  580. package/src/testing/stub-langium-documents.ts +3 -2
  581. package/src/testing/stub-model-service.ts +9 -8
  582. package/src/util/connection-liveness.ts +37 -14
  583. package/src/util/environment.ts +9 -1
  584. package/lib/lsp/instrument-connection.d.ts +0 -36
  585. package/lib/lsp/instrument-connection.d.ts.map +0 -1
  586. package/lib/lsp/instrument-connection.js +0 -62
  587. package/lib/lsp/instrument-connection.js.map +0 -1
  588. package/src/lsp/instrument-connection.ts +0 -66
@@ -8,22 +8,28 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  import {
11
+ type CanonicalUri,
11
12
  type CloseModelArgs,
12
13
  type Tracer,
13
14
  type TransferSavedEvent,
14
15
  type TransferUpdatedEvent,
15
- type OpenModelArgs
16
+ type OpenModelArgs,
17
+ asModelVersion,
18
+ type ModelVersion,
19
+ type TextVersion,
20
+ UNRECORDED_VERSION
16
21
  } from '@hydranium/protocol';
17
22
  import {
18
23
  type AstNode,
19
- type DocumentBuilder,
20
24
  DocumentState,
21
25
  type FileSystemProvider,
22
26
  type LangiumDocument,
23
27
  type LangiumDocuments,
28
+ URI,
24
29
  UriUtils
25
30
  } from '@hydranium/langium';
26
31
  import { type LogNameOptions } from '../langium/diagnostics/logger.js';
32
+ import { type AstDiagnostic } from '../langium/validation/document-validator.js';
27
33
 
28
34
  /**
29
35
  * File-system provider extension that supports writes. Langium's standard
@@ -62,8 +68,9 @@ export interface WritableFileSystemProvider extends FileSystemProvider {
62
68
  * - A non-`file:` URI (or any URI the provider cannot stat) passes through
63
69
  * **unchanged** — it is treated as present, never reported absent.
64
70
  * - A provider with no disk to check (in-memory / browser / empty) simply
65
- * omits this method; `RealpathDocumentUriPolicy` then degrades to the
66
- * syntactic `DefaultDocumentUriPolicy` behaviour automatically.
71
+ * omits this method; the framework's `FileSystemProviderRegistry` then
72
+ * returns the URI unchanged, and `RealpathDocumentUriPolicy` answers as
73
+ * the syntactic `DefaultDocumentUriPolicy` does.
67
74
  *
68
75
  * Synchronous (a `realpath` is a kernel-cached syscall) and optional — the
69
76
  * only consumers are the document-identity policy's `canonicalUri`/`loadUri`.
@@ -77,9 +84,8 @@ export interface WritableFileSystemProvider extends FileSystemProvider {
77
84
  import { Disposable } from 'vscode-languageserver';
78
85
  import { TextDocumentIdentifier, type TextDocumentItem } from 'vscode-languageserver-protocol';
79
86
  import { type TextDocument } from 'vscode-languageserver-textdocument';
80
- import { URI } from '@hydranium/langium';
81
87
  import { type ServerSharedServices } from '../langium/module.js';
82
- import { labelPhaseListener } from '../langium/document-builder/index.js';
88
+ import { type HydraniumDocumentBuilder, labelPhaseListener } from '../langium/document-builder/index.js';
83
89
  import { UNKNOWN_CLIENT_ID } from './client-ids.js';
84
90
  import { type HydraniumTextDocuments } from './hydranium-text-documents.js';
85
91
  import { type SelfSaveRegistry } from './self-save-registry.js';
@@ -95,76 +101,147 @@ export interface UpdateInfo {
95
101
  * {@link AstDocumentManager.onUpdate} / {@link AstDocumentManager.onSave}
96
102
  * for in-process subscribers (`ModelService`, integrity service, etc.).
97
103
  *
98
- * Carries an AST-typed root (`TAst extends AstNode`) — distinct from
99
- * `TransferDocument<TTransfer extends TransferElement, …>` in
100
- * `@hydranium/protocol`, which carries the wire-transfer shape (no
101
- * `$container` cycles, references as strings). The two are structural
102
- * twins; the type-system distinction is the generic constraint, and the
103
- * conversion seam between them is the framework `TransferEncoder`.
104
+ * `root` is the live AST the build holds, not an encoded copy like
105
+ * `TransferDocument.model`, so mutating it changes the model every reader sees.
104
106
  */
105
- export interface AstDocument<TAst extends AstNode, TDiagnostic> {
107
+ export interface AstDocument<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> {
106
108
  root: TAst;
107
- diagnostics: TDiagnostic[];
109
+ /** Absent until the document is validated, and on any `ModelService` read below `Validated`; `[]` means validated and clean. */
110
+ diagnostics?: TDiagnostic[];
108
111
  uri: string;
109
112
  /**
110
- * Text-document version this snapshot was taken at — read from
111
- * `LangiumDocument.textDocument.version`. Symmetric with
112
- * `TransferDocument.version` on the wire side: in-process callers
113
- * that hold an `AstDocument` and mutate it pass this value back as
114
- * `TransferUpdateArgs.baseVersion` / `TransferSaveArgs.baseVersion` to opt
115
- * into the conflict gate.
113
+ * The version of the text `root` was parsed from (`ModelLedger.versionOf`).
114
+ * A caller that mutates the document sends it back as the `baseVersion` of
115
+ * its session's write, and the conflict gate arms on it.
116
116
  */
117
- version: number;
117
+ version: ModelVersion;
118
118
  }
119
119
 
120
+ /**
121
+ * What a session's save or persist resolves to: the document, and in
122
+ * `persisted` the version of the text it wrote to the file. That can differ
123
+ * from `version` either way, since writes land between the build and the
124
+ * take.
125
+ */
126
+ export type SavedAstDocument<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> = AstDocument<TAst, TDiagnostic> & {
127
+ persisted: { version: TextVersion };
128
+ };
129
+
120
130
  export namespace AstDocument {
121
131
  /**
122
- * Construct an {@link AstDocument} envelope. `diagnostics` defaults
123
- * to `[]` so test fixtures and the in-process pre-validated paths
124
- * don't need to repeat the empty array at every call site. Mirrors
125
- * `TransferDocument.create` on the wire side.
132
+ * Construct an {@link AstDocument} envelope, its version marked as coming
133
+ * from a read. `diagnostics` absent means not validated.
126
134
  */
127
- export function create<TAst extends AstNode, TDiagnostic = unknown>(
135
+ export function create<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic>(
128
136
  uri: string,
129
137
  version: number,
130
138
  root: TAst,
131
- diagnostics: TDiagnostic[] = []
139
+ diagnostics?: TDiagnostic[]
132
140
  ): AstDocument<TAst, TDiagnostic> {
133
- return { uri, version, root, diagnostics };
134
- }
135
-
136
- /**
137
- * Project a {@link LangiumDocument} into its {@link AstDocument}
138
- * envelope — the single place that reads `root` / `diagnostics` /
139
- * `version` / `uri` off a built document. Both the event path
140
- * ({@link AstDocumentManager.onUpdate} / `onSave`) and the read path
141
- * (`ModelService`'s snapshot accessors) go through here so the
142
- * field mapping lives once.
143
- *
144
- * The emitted `uri` is always the document's own `textDocument.uri` — the
145
- * canonical document-identity form (see {@link DocumentUriPolicy}). Events
146
- * carry that identity, not the subscriber's (possibly non-canonical) URI:
147
- * a subscriber already knows the URI it subscribed with, so the useful
148
- * thing to surface is the canonical one every other layer keys by.
149
- */
150
- export function from<TAst extends AstNode, TDiagnostic = unknown>(document: LangiumDocument): AstDocument<TAst, TDiagnostic> {
151
- return create<TAst, TDiagnostic>(
152
- document.textDocument.uri,
153
- document.textDocument.version,
154
- document.parseResult.value as TAst,
155
- (document.diagnostics ?? []) as unknown as TDiagnostic[]
156
- );
141
+ return { uri, version: asModelVersion(version), root, ...(diagnostics === undefined ? {} : { diagnostics }) };
157
142
  }
158
143
  }
159
144
 
160
145
  /** Update event delivered by {@link AstDocumentManager.onUpdate} — typed alias over the generic event wrapper. */
161
- export type AstDocumentUpdatedEvent<TAst extends AstNode, TDiagnostic> = TransferUpdatedEvent<AstDocument<TAst, TDiagnostic>>;
146
+ export type AstDocumentUpdatedEvent<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> = TransferUpdatedEvent<
147
+ AstDocument<TAst, TDiagnostic>
148
+ >;
162
149
 
163
150
  /** Save event delivered by {@link AstDocumentManager.onSave} — typed alias over the generic event wrapper. */
164
- export type AstDocumentSavedEvent<TAst extends AstNode, TDiagnostic> = TransferSavedEvent<AstDocument<TAst, TDiagnostic>>;
151
+ export type AstDocumentSavedEvent<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> = TransferSavedEvent<
152
+ AstDocument<TAst, TDiagnostic>
153
+ >;
154
+
155
+ /**
156
+ * Why an update event fired and whose write it echoes, as
157
+ * {@link AstDocumentManager.attributeUpdate} answers for a document. The
158
+ * fields mean what they mean on `TransferUpdatedEvent`.
159
+ */
160
+ export interface UpdateAttribution {
161
+ reason: 'changed' | 'rebuilt';
162
+ sourceClientId: string;
163
+ causedBy: string;
164
+ }
165
165
 
166
166
  /** Construction options for {@link AstDocumentManager}. */
167
- export type AstDocumentManagerOptions = LogNameOptions;
167
+ export interface AstDocumentManagerOptions extends LogNameOptions {
168
+ /**
169
+ * Skip a queued save of a URI when a newer save of it is queued behind. The
170
+ * newer save took its text later, so disk ends the same with fewer writes.
171
+ * A skipped save announces nothing and takes the newer save's outcome: it
172
+ * resolves when that one lands and rejects with its error when it fails,
173
+ * since disk then holds neither text. Defaults to `false`: every save
174
+ * writes. The framework binds the manager without options, so turning this
175
+ * on means rebinding `AstDocumentManager` with them.
176
+ */
177
+ readonly coalesceSaves?: boolean;
178
+ }
179
+
180
+ /**
181
+ * The document slot the model server and the integrity service talk to:
182
+ * open/close/update/save plus the AST-typed event streams, with the LSP
183
+ * plumbing hidden.
184
+ *
185
+ * Generic over `<TAst extends AstNode, TDiagnostic>` so each consumer projects
186
+ * its own AST root type and diagnostic shape into the emitted
187
+ * {@link AstDocument} envelopes.
188
+ *
189
+ * **URI contract.** Every URI-accepting member takes a URI in *any* spelling
190
+ * (canonical, symlinked, `..`/case-divergent) and canonicalizes internally
191
+ * through the {@link DocumentUriPolicy}, so a caller never has to canonicalize
192
+ * first and an implementation may not require it.
193
+ */
194
+ export interface AstDocumentManager<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> {
195
+ open(args: OpenModelArgs): Promise<Disposable>;
196
+ close(args: CloseModelArgs): Promise<void>;
197
+ isOpen(uri: string): boolean;
198
+
199
+ /** Apply `text` as `clientId`'s edit. Resolves to the resulting text-document version. */
200
+ update(uri: string, text: string, clientId: string): Promise<TextVersion>;
201
+ /**
202
+ * Write the document's current text to disk through `FileSystemTaskQueue`.
203
+ * The text is taken when this is called, so saves of one file land in the
204
+ * order they were called.
205
+ *
206
+ * Resolves to the version of the text on disk once it lands: the version
207
+ * taken, or under `coalesceSaves` the version of the newer save that wrote
208
+ * in its place. A caller reporting what it persisted takes it from here,
209
+ * since a version read beside the call names text a coalesced save never
210
+ * wrote.
211
+ */
212
+ save(uri: string, clientId: string): Promise<TextVersion>;
213
+
214
+ onUpdate(uri: string, listener: (event: AstDocumentUpdatedEvent<TAst, TDiagnostic>) => void): Disposable;
215
+ onSave(uri: string, listener: (event: AstDocumentSavedEvent<TAst, TDiagnostic>) => void | Promise<void>): Disposable;
216
+ onClientClosed(uri: string, clientId: string, listener: () => void): Disposable;
217
+
218
+ getDocument(uri: string): LangiumDocument | undefined;
219
+
220
+ /**
221
+ * `document` as the envelope every event and snapshot read hands out, its
222
+ * `version` the one `ModelLedger` recorded for its root.
223
+ *
224
+ * The `uri` is the document's own canonical one, not a subscriber's
225
+ * spelling, so it matches the key every other layer uses. The diagnostics
226
+ * are asserted to be `TDiagnostic` unchecked: a lexer error or another
227
+ * validator's diagnostic satisfies the declared type and not a narrower one.
228
+ */
229
+ toAstDocument(document: LangiumDocument): AstDocument<TAst, TDiagnostic>;
230
+
231
+ /** Client id that authored the document's current version, or `undefined` for a framework-internal build. */
232
+ getAuthor(document: LangiumDocument): string | undefined;
233
+
234
+ /**
235
+ * The reason, source and cause of an update event for `document` emitted
236
+ * now, from a phase listener of the build that carries it. Every head takes
237
+ * its update events' attribution from here, so they name the same client
238
+ * for one build. For a document a client opened, while rebuilds validate,
239
+ * the answer changes once its `Validated` listeners have all run: a later
240
+ * event at that version is `'rebuilt'`. `TransferDocumentUpdateReason`
241
+ * names the cases that fall back to the last update.
242
+ */
243
+ attributeUpdate(document: LangiumDocument): UpdateAttribution;
244
+ }
168
245
 
169
246
  /**
170
247
  * Higher-level facade over {@link HydraniumTextDocuments} that speaks the
@@ -186,13 +263,53 @@ export type AstDocumentManagerOptions = LogNameOptions;
186
263
  * resolution (the egress `applyEditToLanguageClient`) stay on
187
264
  * {@link HydraniumTextDocuments}, their owner.
188
265
  */
189
- export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = unknown> {
266
+ export class DefaultAstDocumentManager<
267
+ TAst extends AstNode,
268
+ TDiagnostic extends AstDiagnostic = AstDiagnostic
269
+ > implements AstDocumentManager<TAst, TDiagnostic> {
270
+ /** The last `DocumentBuilder.update`'s URIs; the reason for a document the store does not track. */
190
271
  protected lastUpdate?: UpdateInfo;
272
+ /**
273
+ * Canonical URIs a client has opened since the file was last deleted,
274
+ * whose versions the store keeps. One no client has opened keeps Langium's
275
+ * version `0` through every change of its content, so its version says
276
+ * nothing. Kept past the last close, since the store continues the version
277
+ * sequence on the next open; a delivery can land after that close.
278
+ */
279
+ protected readonly trackedUris = new Set<CanonicalUri>();
280
+ /**
281
+ * Per tracked URI, the version whose `Validated` listeners last all ran,
282
+ * which is the version an update event was delivered for. Dropped on
283
+ * deletion, so a file created again with the same text is changed again.
284
+ */
285
+ protected readonly deliveredVersions = new Map<CanonicalUri, number>();
286
+ /**
287
+ * Whose writes the build under way carries, set when it starts: the author
288
+ * of every open document's undelivered version, and the unknown-client id
289
+ * for a change no write explains, such as a deletion, a document no client
290
+ * has open, or a write that changed nothing. An undelivered version is one
291
+ * whose build has not validated it yet, so a write whose build was
292
+ * cancelled stays a cause of the build that takes over.
293
+ *
294
+ * Errs toward more than one cause, and so toward the unknown client, which
295
+ * a reader treats as foreign: an answer it repeats, never one it misses.
296
+ * A change no write explains counts for its own build only, so a build
297
+ * that takes over from a cancelled one loses it. One field for every build:
298
+ * a build started outside the workspace lock, such as a re-queue of an
299
+ * orphaned document, reads the causes of whichever build started last.
300
+ * An open document no build delivers, one a `shouldValidate` override
301
+ * skips, stays undelivered, so it is a cause of every build while open.
302
+ */
303
+ protected readonly buildCauses = new Set<string>();
304
+
305
+ /** Per canonical URI, the newest save queued for it, which {@link coalesceSaves} lets older queued saves defer to. */
306
+ protected readonly newestSaves = new Map<CanonicalUri, Promise<TextVersion>>();
307
+ protected readonly coalesceSaves: boolean;
191
308
 
192
309
  protected readonly textDocuments: HydraniumTextDocuments<TextDocument>;
193
310
  protected readonly fileSystemProvider: WritableFileSystemProvider;
194
311
  protected readonly langiumDocs: LangiumDocuments;
195
- protected readonly documentBuilder: DocumentBuilder;
312
+ protected readonly documentBuilder: HydraniumDocumentBuilder;
196
313
  protected readonly uriPolicy: DocumentUriPolicy;
197
314
  protected readonly tracer: Tracer;
198
315
 
@@ -206,32 +323,41 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
206
323
  this.documentBuilder = services.workspace.DocumentBuilder;
207
324
  this.uriPolicy = services.workspace.DocumentUriPolicy;
208
325
  this.tracer = services.Tracer.for(options.logName ?? 'AstDocumentManager').trace('instantiated');
209
- this.textDocuments.onDidOpen(event =>
210
- this.open({ clientId: event.clientId, uri: event.document.uri, languageId: event.document.languageId })
211
- );
326
+ this.coalesceSaves = options.coalesceSaves ?? false;
327
+ this.textDocuments.onDidOpen(event => {
328
+ this.trackedUris.add(this.uriPolicy.canonicalUri(event.document.uri));
329
+ return this.open({ clientId: event.clientId, uri: event.document.uri, languageId: event.document.languageId });
330
+ });
212
331
  this.textDocuments.onDidClose(event => this.close({ clientId: event.clientId, uri: event.document.uri }));
213
332
  this.documentBuilder.onUpdate((changed, deleted) => {
214
333
  this.lastUpdate = { changed, deleted };
215
- });
216
- // Content transitions for CLOSED documents bypass the text store: the
217
- // last-close revert and watched-file changes rebuild from disk into a
218
- // factory-fresh text document carrying the factory's own version. At the
219
- // Parsed transition (the earliest phase with text, and one only re-read
220
- // documents pass through — cascade relinks re-enter later), reconcile the
221
- // store's persisted version sequence with the rebuilt content and
222
- // re-stamp the document so every downstream envelope (revert broadcast,
223
- // snapshot reads) carries the continued sequence. An empty-changes
224
- // update only re-stamps the version. Previously-untracked URIs exit on a
225
- // map lookup, so workspace-wide builds don't hash untouched documents.
226
- this.documentBuilder.onDocumentPhase(
227
- DocumentState.Parsed,
228
- labelPhaseListener(document => {
229
- const sequenceVersion = this.textDocuments.reconcileExternalContent(document.textDocument.uri, document.textDocument.getText());
230
- if (sequenceVersion !== undefined && document.textDocument.version !== sequenceVersion) {
231
- this.textDocuments.update(document.textDocument, [], sequenceVersion);
334
+ this.buildCauses.clear();
335
+ for (const textDocument of this.textDocuments.all()) {
336
+ if (!this.isDelivered(textDocument.uri, textDocument.version)) {
337
+ this.buildCauses.add(this.textDocuments.getAuthor(textDocument.uri, textDocument.version) ?? UNKNOWN_CLIENT_ID);
232
338
  }
233
- }, 'AstDocumentManager.reconcileExternalContent')
234
- );
339
+ }
340
+ const unexplained = changed.some(uri => {
341
+ const textDocument = this.textDocuments.get(uri.toString());
342
+ return textDocument === undefined || this.isDelivered(textDocument.uri, textDocument.version);
343
+ });
344
+ if (unexplained || deleted.length > 0) {
345
+ this.buildCauses.add(UNKNOWN_CLIENT_ID);
346
+ }
347
+ // The only place a deletion is observable: `DocumentBuilder.update`
348
+ // removes a deleted document before it builds anything, so no phase
349
+ // listener ever sees it.
350
+ for (const uri of deleted) {
351
+ const key = this.uriPolicy.canonicalUri(uri.toString());
352
+ this.trackedUris.delete(key);
353
+ this.deliveredVersions.delete(key);
354
+ this.textDocuments.notifyDocumentDeleted(uri.toString());
355
+ }
356
+ });
357
+ // Once every listener ran, not in a listener of its own: listeners after
358
+ // it could still be skipped by a cancel, and the next build would then
359
+ // report as rebuilt a version they never delivered.
360
+ this.documentBuilder.onDocumentPhaseDelivered(DocumentState.Validated, (document, version) => this.markDelivered(document, version));
235
361
  }
236
362
 
237
363
  /**
@@ -304,20 +430,16 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
304
430
  if (this.uriPolicy.canonicalUri(document.uri) !== target) {
305
431
  return;
306
432
  }
307
- // Presentation boundary: the event field is a concrete `string`, so an
308
- // unauthored (framework-rebuilt) document surfaces the readable default
309
- // rather than `undefined` to subscribers and logs.
310
- const sourceClientId = this.getAuthor(document) ?? UNKNOWN_CLIENT_ID;
433
+ // No `'deleted'` reason: `DocumentBuilder.update` drops a deleted
434
+ // document from `LangiumDocuments` before deriving the rebuild set
435
+ // from it, so a deleted URI is never built and never reaches this
436
+ // phase listener. A subscriber needing deletions has to be told on
437
+ // a channel that does not require a built document.
311
438
  const event: TransferUpdatedEvent<AstDocument<TAst, TDiagnostic>> = {
312
439
  document: this.toAstDocument(document),
313
- sourceClientId,
314
- reason: this.lastUpdate?.changed.find(changed => UriUtils.equals(changed, document.uri))
315
- ? 'changed'
316
- : this.lastUpdate?.deleted.find(deleted => UriUtils.equals(deleted, document.uri))
317
- ? 'deleted'
318
- : 'rebuilt'
440
+ ...this.attributeUpdate(document)
319
441
  };
320
- this.tracer.with(uri).trace(`emitUpdate start: source=${sourceClientId}, reason=${event.reason}`);
442
+ this.tracer.with(uri).trace(`emitUpdate start: source=${event.sourceClientId}, reason=${event.reason}, cause=${event.causedBy}`);
321
443
  listener(event);
322
444
  this.tracer.with(uri).trace('emitUpdate listener returned');
323
445
  };
@@ -326,23 +448,86 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
326
448
 
327
449
  /**
328
450
  * The client that authored `document`'s current version, or `undefined` when
329
- * no client wrote it — a framework-internal rebuild (workspace startup, a
330
- * cascade relink, a `didClose`-reload) or a genuine author gap. Honest about
331
- * absence so routing consumers (`ModelService.isNonLanguageClientEdit`)
332
- * test `undefined` directly rather than against a sentinel; the presentation
333
- * default ({@link UNKNOWN_CLIENT_ID}) is applied at the boundary that needs a
334
- * concrete value (the `onUpdate` event's `sourceClientId`, the data-server's
335
- * `resolveSourceClientId`).
451
+ * no client wrote it: a document no client has open. A rebuild that keeps
452
+ * the version keeps its author, so this does not say who caused a build;
453
+ * {@link attributeUpdate} does.
454
+ * Honest about absence so a routing consumer tests `undefined` directly
455
+ * rather than against a sentinel; the presentation default
456
+ * ({@link UNKNOWN_CLIENT_ID}) is applied where an event needs a value.
336
457
  */
337
- getAuthor(document: LangiumDocument<AstNode>): string | undefined {
458
+ getAuthor(document: LangiumDocument): string | undefined {
338
459
  return this.textDocuments.getAuthor(document.textDocument.uri, document.textDocument.version);
339
460
  }
340
461
 
462
+ /**
463
+ * `'changed'` for the first delivery of the document's version, crediting
464
+ * its author; `'rebuilt'` for a later one, crediting nobody, since it echoes
465
+ * no write, and naming the build's single cause if it has one.
466
+ *
467
+ * A document the rule does not apply to (see {@link isTracked}) falls back
468
+ * to the last update: it is `'changed'` when its URI was passed to
469
+ * `DocumentBuilder.update`. An open document that a validating build skips,
470
+ * through a `shouldValidate` override, is never delivered, so each of its
471
+ * events is its author's change.
472
+ */
473
+ attributeUpdate(document: LangiumDocument): UpdateAttribution {
474
+ const key = this.uriPolicy.canonicalUri(document.uri.toString());
475
+ const changed = this.isTracked(key)
476
+ ? this.deliveredVersions.get(key) !== document.textDocument.version
477
+ : this.lastUpdate?.changed.some(uri => UriUtils.equals(uri, document.uri)) === true;
478
+ if (changed) {
479
+ // Presentation boundary: the event field is a concrete `string`, so an
480
+ // unauthored document surfaces the readable default rather than
481
+ // `undefined` to subscribers and logs.
482
+ const author = this.getAuthor(document) ?? UNKNOWN_CLIENT_ID;
483
+ return { reason: 'changed', sourceClientId: author, causedBy: author };
484
+ }
485
+ const [cause] = this.buildCauses;
486
+ return { reason: 'rebuilt', sourceClientId: UNKNOWN_CLIENT_ID, causedBy: this.buildCauses.size === 1 ? cause : UNKNOWN_CLIENT_ID };
487
+ }
488
+
489
+ /**
490
+ * Whether the version rule applies to `uri`: the store keeps its version,
491
+ * and rebuilds validate. With `updateBuildOptions.validation` off no version
492
+ * is ever delivered, and the rule would report every event of an open
493
+ * document as its author's change.
494
+ */
495
+ protected isTracked(uri: CanonicalUri): boolean {
496
+ return this.keepsVersion(uri) && Boolean(this.documentBuilder.updateBuildOptions.validation);
497
+ }
498
+
499
+ /**
500
+ * Whether the store keeps `uri`'s version: a client opened it since its
501
+ * file was last deleted, or has it open still, as an editor keeps a
502
+ * deleted file's buffer.
503
+ */
504
+ protected keepsVersion(uri: CanonicalUri): boolean {
505
+ return this.trackedUris.has(uri) || this.textDocuments.isOpen(uri);
506
+ }
507
+
508
+ /**
509
+ * Record that `version` of `document` was delivered, when the store tracks
510
+ * it. The version the listeners saw, not the document's current one: a
511
+ * write during them has moved that on, and marking it would report the
512
+ * write's own build as rebuilt.
513
+ */
514
+ protected markDelivered(document: LangiumDocument, version: number): void {
515
+ const key = this.uriPolicy.canonicalUri(document.uri.toString());
516
+ if (this.keepsVersion(key)) {
517
+ this.deliveredVersions.set(key, version);
518
+ }
519
+ }
520
+
521
+ /** Whether `version` of `uri` was delivered. */
522
+ protected isDelivered(uri: string, version: number): boolean {
523
+ return this.deliveredVersions.get(this.uriPolicy.canonicalUri(uri)) === version;
524
+ }
525
+
341
526
  async open(args: OpenModelArgs): Promise<Disposable> {
342
527
  // `open()` is the RPC-level "ensure this document is loaded" call. It is issued
343
- // by a view attaching, but also internally by `update()` / `save()` and by every
344
- // additional client attaching to the same URI, including the several sub-editors
345
- // a composite view opens. When the document is already loaded there is nothing to
528
+ // by a session opening the document, including by every additional client
529
+ // attaching to the same URI and the several sub-editors a composite view
530
+ // opens. When the document is already loaded there is nothing to
346
531
  // do here but hand back the close-disposable — callers that want the current
347
532
  // state read it from this method's surrounding RPC response, not from a re-build.
348
533
  //
@@ -355,10 +540,33 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
355
540
  // transiently breaks cross-document linking mid-rebuild (a dependent briefly
356
541
  // seeing an `extends`/`type` reference unresolved). And because the constructor
357
542
  // wires `onDidOpen -> this.open`, it would fire a second, identical rebuild on
358
- // every first open. So an already-open `open()` is a no-op.
543
+ // every first open.
544
+ //
545
+ // So an already-open `open()` records the attaching client's open and nothing
546
+ // else. The open is per `(uri, clientId)` and the last-close revert counts down
547
+ // to it, so skipping it lets the first client's close tear down a document
548
+ // another client is still reading. A document waiting out the revert grace
549
+ // is still loaded: a client lost from it within its own grace attaches and
550
+ // keeps its text, and for any other client the attach releases it, so the
551
+ // open reads the file.
552
+ if (this.isOpen(args.uri)) {
553
+ this.textDocuments.attachClient(args.uri, args.clientId);
554
+ }
359
555
  if (!this.isOpen(args.uri)) {
360
556
  const textDocument = await this.createDocumentFromTextOrFileSystem(args.uri, args.languageId, args.version, args.text);
557
+ const creates = !this.isOpen(args.uri);
361
558
  this.textDocuments.notifyDidOpenTextDocument({ textDocument }, args.clientId);
559
+ if (creates && args.text !== undefined) {
560
+ // Text the caller supplies was not read from the file, so no file
561
+ // is assumed: the document is dirty until its first save, whatever
562
+ // the file holds.
563
+ this.textDocuments.updateDiskBaseline(args.uri, undefined);
564
+ }
565
+ const built = this.getDocument(args.uri);
566
+ if (creates && built) {
567
+ // Without an LSP head nothing else builds the opened text.
568
+ void this.services.workspace.VersionSyncService.syncTo(built.uri, this.textDocuments.version(args.uri));
569
+ }
362
570
  }
363
571
  return Disposable.create(() => this.close(args));
364
572
  }
@@ -378,7 +586,7 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
378
586
  * correlate the change against subsequent build events can. Throws if the
379
587
  * document isn't open.
380
588
  */
381
- async update(uri: string, text: string, clientId: string): Promise<number> {
589
+ async update(uri: string, text: string, clientId: string): Promise<TextVersion> {
382
590
  if (!this.isOpen(uri)) {
383
591
  throw new Error(`Document ${uri} hasn't been opened for updating yet`);
384
592
  }
@@ -393,31 +601,81 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
393
601
  * callers don't need to pass it — the manager owns content and version
394
602
  * sequencing.
395
603
  *
604
+ * The text is taken synchronously, before the save waits in the file's task
605
+ * queue; the compare and the write run in the queue. Taken later, a save
606
+ * that waits behind another writes whatever the store holds when its turn
607
+ * comes, edits made after the call included, and announces that text under
608
+ * its own client id.
609
+ *
610
+ * **The write is skipped when the file already holds that text; the
611
+ * notification is not.** A save announces that the content is on disk, which
612
+ * is true either way — so a subscriber clearing a dirty marker or reacting to
613
+ * a persist behaves the same, while an mtime is not moved for byte-identical
614
+ * content that a downstream mtime-keyed build would then read as new work.
615
+ *
396
616
  * Throws if no document is open for `uri`.
397
617
  */
398
- async save(uri: string, clientId: string): Promise<void> {
399
- const document = this.textDocuments.get(uri);
618
+ async save(uri: string, clientId: string): Promise<TextVersion> {
619
+ // Canonical throughout: the write and the read that gates it must address
620
+ // the same file the store was keyed by, or a divergent spelling compares
621
+ // one file and writes another.
622
+ const canonical = this.uriPolicy.canonicalUri(uri);
623
+ const document = this.textDocuments.get(canonical);
400
624
  if (!document) {
401
625
  throw new Error(`Document ${uri} hasn't been opened for saving yet`);
402
626
  }
403
627
  const text = document.getText();
404
- await this.tracer.with(uri).time(
405
- `Write file (${text.length} bytes, from ${clientId})`,
406
- // Async write so a slow disk does not block the event loop for the duration of the fs call.
407
- () => this.fileSystemProvider.writeFile(UriUtils.toUri(uri), text),
408
- 'debug'
409
- );
628
+ const version = document.version;
629
+ let supersededBy: Promise<TextVersion> | undefined;
630
+ const saved: Promise<TextVersion> = this.services.workspace.FileSystemTaskQueue.enqueue(canonical, async () => {
631
+ const newest = this.newestSaves.get(canonical);
632
+ if (this.coalesceSaves && newest !== saved) {
633
+ supersededBy = newest;
634
+ return;
635
+ }
636
+ await this.writeSave(canonical, text, clientId);
637
+ })
638
+ .then(() => supersededBy ?? version)
639
+ .finally(() => {
640
+ if (this.newestSaves.get(canonical) === saved) {
641
+ this.newestSaves.delete(canonical);
642
+ }
643
+ });
644
+ this.newestSaves.set(canonical, saved);
645
+ return saved;
646
+ }
647
+
648
+ /** Write `text` to `uri` unless the file already holds it, then announce the save. Runs inside the file's task queue. */
649
+ protected async writeSave(uri: CanonicalUri, text: string, clientId: string): Promise<void> {
650
+ if (!(await this.matchesDisk(uri, text))) {
651
+ await this.tracer.with(uri).time(
652
+ `Write file (${text.length} bytes, from ${clientId})`,
653
+ // Async write so a slow disk does not block the event loop for the duration of the fs call.
654
+ () => this.fileSystemProvider.writeFile(UriUtils.toUri(uri), text),
655
+ 'debug'
656
+ );
657
+ }
410
658
  this.textDocuments.notifyDidSaveTextDocument({ textDocument: TextDocumentIdentifier.create(uri), text }, clientId);
411
659
  }
412
660
 
413
- isOpen(uri: string): boolean {
414
- return !!this.textDocuments.get(uri);
661
+ /**
662
+ * Whether the file at `uri` already holds `text`.
663
+ *
664
+ * Compared by CONTENT rather than against a remembered version, so a write
665
+ * from outside this server is still corrected: a version this manager already
666
+ * saved says nothing about what is on disk now. `false` when the file cannot
667
+ * be read at all, which is the answer that writes — a document whose file does
668
+ * not exist yet is exactly the one a first save has to create.
669
+ */
670
+ protected async matchesDisk(uri: string, text: string): Promise<boolean> {
671
+ return this.fileSystemProvider
672
+ .readFile(UriUtils.toUri(uri))
673
+ .then(onDisk => onDisk === text)
674
+ .catch(() => false);
415
675
  }
416
676
 
417
- /** True if the URI was in the directly-changed set of the most recent build (not just affected). */
418
- isDirectChange(uri: string): boolean {
419
- const canonical = this.uriPolicy.canonicalUri(uri);
420
- return this.lastUpdate?.changed.some(changed => this.uriPolicy.canonicalUri(changed) === canonical) ?? false;
677
+ isOpen(uri: string): boolean {
678
+ return !!this.textDocuments.get(uri);
421
679
  }
422
680
 
423
681
  /**
@@ -437,19 +695,30 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
437
695
  version = 0,
438
696
  text?: string
439
697
  ): Promise<TextDocumentItem> {
440
- return { uri, languageId, version, text: text ?? (await this.readFile(uri)) };
441
- }
442
-
443
- async readFile(uri: string): Promise<string> {
444
- return this.fileSystemProvider.readFile(UriUtils.toUri(uri));
698
+ // Read in the file's task queue: a read beside a queued save of the file
699
+ // returns the text from before it, and the document then opens on that.
700
+ return {
701
+ uri,
702
+ languageId,
703
+ version,
704
+ text:
705
+ text ??
706
+ (await this.services.workspace.FileSystemTaskQueue.enqueue(uri, () => this.fileSystemProvider.readFile(UriUtils.toUri(uri))))
707
+ };
445
708
  }
446
709
 
447
710
  /**
448
- * Hook for subclasses that want to re-shape the emitted document (e.g. inject
449
- * a custom-typed `diagnostics` field). The default delegates to
450
- * {@link AstDocument.from}, the shared LangiumDocument→envelope projection.
711
+ * Events and the model service's snapshot reads both come through here, so
712
+ * an override that re-shapes the document re-shapes both.
451
713
  */
452
- protected toAstDocument(document: LangiumDocument): AstDocument<TAst, TDiagnostic> {
453
- return AstDocument.from<TAst, TDiagnostic>(document);
714
+ toAstDocument(document: LangiumDocument): AstDocument<TAst, TDiagnostic> {
715
+ const uri = document.textDocument.uri;
716
+ const root = document.parseResult.value as TAst;
717
+ const ledger = this.services.workspace.ModelLedger;
718
+ const version = ledger.versionOf(root);
719
+ if (version === UNRECORDED_VERSION && !ledger.isPlaceholder(root)) {
720
+ this.tracer.with(uri).warn('No model version recorded for the root: it was built outside the document factory');
721
+ }
722
+ return AstDocument.create<TAst, TDiagnostic>(uri, version, root, document.diagnostics as TDiagnostic[] | undefined);
454
723
  }
455
724
  }