@hydranium/core 1.0.0-next.7 → 1.0.0-next.71

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 (286) hide show
  1. package/lib/documents/ast-document-manager.d.ts +58 -5
  2. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  3. package/lib/documents/ast-document-manager.js +50 -18
  4. package/lib/documents/ast-document-manager.js.map +1 -1
  5. package/lib/documents/client-ids.d.ts +5 -19
  6. package/lib/documents/client-ids.d.ts.map +1 -1
  7. package/lib/documents/client-ids.js +5 -19
  8. package/lib/documents/client-ids.js.map +1 -1
  9. package/lib/documents/hydranium-text-documents.d.ts +51 -17
  10. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  11. package/lib/documents/hydranium-text-documents.js +136 -40
  12. package/lib/documents/hydranium-text-documents.js.map +1 -1
  13. package/lib/documents/language-client-text-shadow.d.ts +41 -9
  14. package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
  15. package/lib/documents/language-client-text-shadow.js +56 -11
  16. package/lib/documents/language-client-text-shadow.js.map +1 -1
  17. package/lib/documents/self-save-registry.d.ts +37 -6
  18. package/lib/documents/self-save-registry.d.ts.map +1 -1
  19. package/lib/documents/self-save-registry.js +28 -15
  20. package/lib/documents/self-save-registry.js.map +1 -1
  21. package/lib/index.d.ts +2 -0
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/index.js +5 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/langium/ast-extension/ast-node-builder.d.ts +32 -9
  26. package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
  27. package/lib/langium/ast-extension/ast-node-builder.js +60 -22
  28. package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
  29. package/lib/langium/bootstrap.d.ts.map +1 -1
  30. package/lib/langium/bootstrap.js +16 -9
  31. package/lib/langium/bootstrap.js.map +1 -1
  32. package/lib/langium/config/configuration-provider.d.ts +81 -0
  33. package/lib/langium/config/configuration-provider.d.ts.map +1 -0
  34. package/lib/langium/config/configuration-provider.js +112 -0
  35. package/lib/langium/config/configuration-provider.js.map +1 -0
  36. package/lib/langium/config/index.d.ts +1 -0
  37. package/lib/langium/config/index.d.ts.map +1 -1
  38. package/lib/langium/config/index.js +1 -0
  39. package/lib/langium/config/index.js.map +1 -1
  40. package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
  41. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  42. package/lib/langium/diagnostics/lsp-logger.js +40 -6
  43. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  44. package/lib/langium/document-builder/build-pipeline-integration.d.ts +13 -1
  45. package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
  46. package/lib/langium/document-builder/build-pipeline-integration.js +1 -1
  47. package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
  48. package/lib/langium/document-builder/build-session.d.ts +93 -0
  49. package/lib/langium/document-builder/build-session.d.ts.map +1 -0
  50. package/lib/langium/document-builder/build-session.js +72 -0
  51. package/lib/langium/document-builder/build-session.js.map +1 -0
  52. package/lib/langium/document-builder/document-builder.d.ts +150 -7
  53. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  54. package/lib/langium/document-builder/document-builder.js +298 -11
  55. package/lib/langium/document-builder/document-builder.js.map +1 -1
  56. package/lib/langium/document-builder/index.d.ts +1 -0
  57. package/lib/langium/document-builder/index.d.ts.map +1 -1
  58. package/lib/langium/document-builder/index.js +1 -0
  59. package/lib/langium/document-builder/index.js.map +1 -1
  60. package/lib/langium/integration-services.d.ts +45 -1
  61. package/lib/langium/integration-services.d.ts.map +1 -1
  62. package/lib/langium/integration-services.js +10 -5
  63. package/lib/langium/integration-services.js.map +1 -1
  64. package/lib/langium/integrity/integrity-service.d.ts +5 -0
  65. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  66. package/lib/langium/integrity/integrity-service.js +34 -1
  67. package/lib/langium/integrity/integrity-service.js.map +1 -1
  68. package/lib/langium/keys/containment.d.ts +64 -0
  69. package/lib/langium/keys/containment.d.ts.map +1 -0
  70. package/lib/langium/keys/containment.js +53 -0
  71. package/lib/langium/keys/containment.js.map +1 -0
  72. package/lib/langium/keys/index.d.ts +1 -0
  73. package/lib/langium/keys/index.d.ts.map +1 -1
  74. package/lib/langium/keys/index.js +1 -0
  75. package/lib/langium/keys/index.js.map +1 -1
  76. package/lib/langium/keys/name-based-key-provider.d.ts +4 -0
  77. package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
  78. package/lib/langium/keys/name-based-key-provider.js +4 -0
  79. package/lib/langium/keys/name-based-key-provider.js.map +1 -1
  80. package/lib/langium/model-service/model-service.d.ts +103 -7
  81. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  82. package/lib/langium/model-service/model-service.js +70 -89
  83. package/lib/langium/model-service/model-service.js.map +1 -1
  84. package/lib/langium/module.d.ts +82 -13
  85. package/lib/langium/module.d.ts.map +1 -1
  86. package/lib/langium/module.js +26 -12
  87. package/lib/langium/module.js.map +1 -1
  88. package/lib/langium/naming/name-provider.d.ts +10 -0
  89. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  90. package/lib/langium/naming/name-provider.js.map +1 -1
  91. package/lib/langium/naming/name-separator-validation.d.ts +23 -0
  92. package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
  93. package/lib/langium/naming/name-separator-validation.js +30 -1
  94. package/lib/langium/naming/name-separator-validation.js.map +1 -1
  95. package/lib/langium/residency/cst-residency-service.d.ts +31 -9
  96. package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
  97. package/lib/langium/residency/cst-residency-service.js +13 -56
  98. package/lib/langium/residency/cst-residency-service.js.map +1 -1
  99. package/lib/langium/scope/hydranium-scope-provider.d.ts +32 -13
  100. package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
  101. package/lib/langium/scope/hydranium-scope-provider.js +37 -20
  102. package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
  103. package/lib/langium/shared-services.d.ts +15 -2
  104. package/lib/langium/shared-services.d.ts.map +1 -1
  105. package/lib/langium/shared-services.js.map +1 -1
  106. package/lib/langium/transfer/transfer-encoder.d.ts +33 -2
  107. package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
  108. package/lib/langium/transfer/transfer-encoder.js +14 -3
  109. package/lib/langium/transfer/transfer-encoder.js.map +1 -1
  110. package/lib/langium/validation/document-validator.d.ts +145 -6
  111. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  112. package/lib/langium/validation/document-validator.js +211 -4
  113. package/lib/langium/validation/document-validator.js.map +1 -1
  114. package/lib/langium/workspace/document-uri-policy.d.ts +3 -4
  115. package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
  116. package/lib/langium/workspace/document-uri-policy.js +3 -4
  117. package/lib/langium/workspace/document-uri-policy.js.map +1 -1
  118. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +25 -0
  119. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
  120. package/lib/langium/workspace/hydranium-langium-document-factory.js +25 -0
  121. package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
  122. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +21 -1
  123. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  124. package/lib/langium/workspace/hydranium-workspace-manager.js +28 -0
  125. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  126. package/lib/langium/workspace/in-memory-file-system-provider.d.ts +8 -0
  127. package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
  128. package/lib/langium/workspace/in-memory-file-system-provider.js +11 -2
  129. package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
  130. package/lib/langium/workspace/initialize-workspace.d.ts +22 -2
  131. package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
  132. package/lib/langium/workspace/initialize-workspace.js +13 -5
  133. package/lib/langium/workspace/initialize-workspace.js.map +1 -1
  134. package/lib/langium/workspace/langium-documents.d.ts +79 -17
  135. package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
  136. package/lib/langium/workspace/langium-documents.js +83 -28
  137. package/lib/langium/workspace/langium-documents.js.map +1 -1
  138. package/lib/locale/index.d.ts +10 -0
  139. package/lib/locale/index.d.ts.map +1 -0
  140. package/lib/locale/index.js +10 -0
  141. package/lib/locale/index.js.map +1 -0
  142. package/lib/locale/server-locale.d.ts +73 -0
  143. package/lib/locale/server-locale.d.ts.map +1 -0
  144. package/lib/locale/server-locale.js +61 -0
  145. package/lib/locale/server-locale.js.map +1 -0
  146. package/lib/lsp/hydranium-document-update-handler.js +1 -1
  147. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  148. package/lib/lsp/index.d.ts +1 -1
  149. package/lib/lsp/index.d.ts.map +1 -1
  150. package/lib/lsp/index.js +1 -1
  151. package/lib/lsp/index.js.map +1 -1
  152. package/lib/lsp/lsp-latency.d.ts +39 -0
  153. package/lib/lsp/lsp-latency.d.ts.map +1 -0
  154. package/lib/lsp/lsp-latency.js +58 -0
  155. package/lib/lsp/lsp-latency.js.map +1 -0
  156. package/lib/messages/carriers.d.ts +54 -0
  157. package/lib/messages/carriers.d.ts.map +1 -0
  158. package/lib/messages/carriers.js +62 -0
  159. package/lib/messages/carriers.js.map +1 -0
  160. package/lib/messages/index.d.ts +25 -0
  161. package/lib/messages/index.d.ts.map +1 -0
  162. package/lib/messages/index.js +25 -0
  163. package/lib/messages/index.js.map +1 -0
  164. package/lib/messages/renderer.d.ts +136 -0
  165. package/lib/messages/renderer.d.ts.map +1 -0
  166. package/lib/messages/renderer.js +159 -0
  167. package/lib/messages/renderer.js.map +1 -0
  168. package/lib/node/latency-from-env.d.ts +1 -1
  169. package/lib/node/latency-from-env.js +1 -1
  170. package/lib/node/node-file-system-provider.d.ts.map +1 -1
  171. package/lib/node/node-file-system-provider.js +1 -40
  172. package/lib/node/node-file-system-provider.js.map +1 -1
  173. package/lib/node/profiling-run.d.ts +2 -2
  174. package/lib/node/profiling-run.js +1 -1
  175. package/lib/node/profiling-run.js.map +1 -1
  176. package/lib/node/rename-over-open-readers.d.ts +52 -0
  177. package/lib/node/rename-over-open-readers.d.ts.map +1 -0
  178. package/lib/node/rename-over-open-readers.js +66 -0
  179. package/lib/node/rename-over-open-readers.js.map +1 -0
  180. package/lib/testing/fake-document.d.ts +14 -3
  181. package/lib/testing/fake-document.d.ts.map +1 -1
  182. package/lib/testing/fake-document.js +18 -3
  183. package/lib/testing/fake-document.js.map +1 -1
  184. package/lib/testing/make-noop-shared-services.d.ts +14 -0
  185. package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
  186. package/lib/testing/make-noop-shared-services.js +28 -2
  187. package/lib/testing/make-noop-shared-services.js.map +1 -1
  188. package/lib/testing/make-test-services.d.ts +31 -4
  189. package/lib/testing/make-test-services.d.ts.map +1 -1
  190. package/lib/testing/make-test-services.js +19 -3
  191. package/lib/testing/make-test-services.js.map +1 -1
  192. package/lib/testing/node/spawned-server.d.ts +14 -0
  193. package/lib/testing/node/spawned-server.d.ts.map +1 -1
  194. package/lib/testing/node/spawned-server.js +33 -1
  195. package/lib/testing/node/spawned-server.js.map +1 -1
  196. package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
  197. package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
  198. package/lib/testing/playwright/flaky-network-proxy.js +164 -0
  199. package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
  200. package/lib/testing/playwright/index.d.ts +1 -0
  201. package/lib/testing/playwright/index.d.ts.map +1 -1
  202. package/lib/testing/playwright/index.js +1 -0
  203. package/lib/testing/playwright/index.js.map +1 -1
  204. package/lib/testing/playwright/server-log-capture.d.ts +14 -4
  205. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  206. package/lib/testing/playwright/server-log-capture.js +21 -4
  207. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  208. package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
  209. package/lib/testing/playwright/server-log-rename-reporter.js +10 -1
  210. package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
  211. package/lib/testing/stub-ast-document-manager.d.ts +3 -4
  212. package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
  213. package/lib/testing/stub-ast-document-manager.js +2 -5
  214. package/lib/testing/stub-ast-document-manager.js.map +1 -1
  215. package/lib/testing/stub-document-builder.d.ts +8 -0
  216. package/lib/testing/stub-document-builder.d.ts.map +1 -1
  217. package/lib/testing/stub-document-builder.js +22 -3
  218. package/lib/testing/stub-document-builder.js.map +1 -1
  219. package/lib/testing/stub-model-service.d.ts +1 -1
  220. package/lib/testing/stub-model-service.d.ts.map +1 -1
  221. package/lib/testing/stub-model-service.js +2 -2
  222. package/lib/testing/stub-model-service.js.map +1 -1
  223. package/package.json +17 -8
  224. package/src/documents/ast-document-manager.ts +99 -23
  225. package/src/documents/client-ids.ts +5 -22
  226. package/src/documents/hydranium-text-documents.ts +161 -55
  227. package/src/documents/language-client-text-shadow.ts +59 -11
  228. package/src/documents/self-save-registry.ts +52 -13
  229. package/src/index.ts +5 -0
  230. package/src/langium/ast-extension/ast-node-builder.ts +66 -22
  231. package/src/langium/bootstrap.ts +16 -9
  232. package/src/langium/config/configuration-provider.ts +119 -0
  233. package/src/langium/config/index.ts +1 -0
  234. package/src/langium/diagnostics/lsp-logger.ts +41 -6
  235. package/src/langium/document-builder/build-pipeline-integration.ts +14 -1
  236. package/src/langium/document-builder/build-session.ts +87 -0
  237. package/src/langium/document-builder/document-builder.ts +339 -11
  238. package/src/langium/document-builder/index.ts +1 -0
  239. package/src/langium/integration-services.ts +67 -6
  240. package/src/langium/integrity/integrity-service.ts +35 -1
  241. package/src/langium/keys/containment.ts +87 -0
  242. package/src/langium/keys/index.ts +1 -0
  243. package/src/langium/keys/name-based-key-provider.ts +4 -0
  244. package/src/langium/model-service/model-service.ts +145 -22
  245. package/src/langium/module.ts +104 -19
  246. package/src/langium/naming/name-provider.ts +10 -0
  247. package/src/langium/naming/name-separator-validation.ts +34 -5
  248. package/src/langium/residency/cst-residency-service.ts +36 -12
  249. package/src/langium/scope/hydranium-scope-provider.ts +40 -22
  250. package/src/langium/shared-services.ts +19 -2
  251. package/src/langium/transfer/transfer-encoder.ts +45 -3
  252. package/src/langium/validation/document-validator.ts +255 -8
  253. package/src/langium/workspace/document-uri-policy.ts +3 -4
  254. package/src/langium/workspace/hydranium-langium-document-factory.ts +34 -0
  255. package/src/langium/workspace/hydranium-workspace-manager.ts +30 -1
  256. package/src/langium/workspace/in-memory-file-system-provider.ts +13 -3
  257. package/src/langium/workspace/initialize-workspace.ts +34 -5
  258. package/src/langium/workspace/langium-documents.ts +106 -33
  259. package/src/locale/index.ts +10 -0
  260. package/src/locale/server-locale.ts +86 -0
  261. package/src/lsp/hydranium-document-update-handler.ts +1 -1
  262. package/src/lsp/index.ts +1 -1
  263. package/src/lsp/lsp-latency.ts +62 -0
  264. package/src/messages/carriers.ts +78 -0
  265. package/src/messages/index.ts +27 -0
  266. package/src/messages/renderer.ts +204 -0
  267. package/src/node/latency-from-env.ts +1 -1
  268. package/src/node/node-file-system-provider.ts +1 -41
  269. package/src/node/profiling-run.ts +3 -3
  270. package/src/node/rename-over-open-readers.ts +78 -0
  271. package/src/testing/fake-document.ts +22 -4
  272. package/src/testing/make-noop-shared-services.ts +50 -2
  273. package/src/testing/make-test-services.ts +49 -7
  274. package/src/testing/node/spawned-server.ts +35 -1
  275. package/src/testing/playwright/flaky-network-proxy.ts +240 -0
  276. package/src/testing/playwright/index.ts +1 -0
  277. package/src/testing/playwright/server-log-capture.ts +24 -5
  278. package/src/testing/playwright/server-log-rename-reporter.ts +12 -1
  279. package/src/testing/stub-ast-document-manager.ts +5 -10
  280. package/src/testing/stub-document-builder.ts +32 -3
  281. package/src/testing/stub-model-service.ts +2 -2
  282. package/lib/lsp/instrument-connection.d.ts +0 -36
  283. package/lib/lsp/instrument-connection.d.ts.map +0 -1
  284. package/lib/lsp/instrument-connection.js +0 -62
  285. package/lib/lsp/instrument-connection.js.map +0 -1
  286. package/src/lsp/instrument-connection.ts +0 -66
@@ -7,7 +7,7 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { inject, type Module } from '@hydranium/langium';
10
+ import { type DeepPartial, inject, type Module } from '@hydranium/langium';
11
11
  import {
12
12
  createDefaultModule,
13
13
  createDefaultSharedModule,
@@ -98,6 +98,31 @@ export interface IntegrationServicesOptions<
98
98
  * it is silently inert.
99
99
  */
100
100
  extra?: ReadonlyArray<Module<TShared, PartialLangiumSharedServices>>;
101
+ /**
102
+ * Shared modules composed LAST — after `adopter` — and partial over
103
+ * `TShared` rather than over Langium's shared services.
104
+ *
105
+ * Both differences are the point, and neither is served by
106
+ * {@link extra}. Most framework shared services are constructed with
107
+ * no options (`ModelService: services => new DefaultModelService(services)`),
108
+ * so rebinding the slot is the only way to boot one configured
109
+ * differently; a caller that needs that is usually a test, and the slot
110
+ * it needs is as often a framework ADDITION (`Clock`, `Tracer`,
111
+ * `ProjectManager`) as a Langium one. `extra` can express neither: it
112
+ * is typed `DeepPartial<LangiumSharedServices>`, and it loses to
113
+ * `adopter` on every slot the adopter binds.
114
+ *
115
+ * Kept separate rather than widening `extra` because `extra` is the
116
+ * protocol-head tier, and a head must NOT outrank the adopter. Widening
117
+ * it also does not typecheck — `Module<I, T>` puts `T` in factory
118
+ * return position, so a head module declaring Langium slot types stops
119
+ * being assignable once `T` becomes `DeepPartial<TShared>`.
120
+ *
121
+ * Composing last means an override wins over everything, including the
122
+ * adopter. That is what makes it usable for the slots an adopter binds
123
+ * itself, and why production code should not reach for it.
124
+ */
125
+ overrides?: ReadonlyArray<Module<TShared, DeepPartial<TShared>>>;
101
126
  };
102
127
 
103
128
  /**
@@ -134,6 +159,23 @@ export interface IntegrationServicesOptions<
134
159
  * still win over heads.
135
160
  */
136
161
  extra?: ReadonlyArray<Module<TLanguage, PartialLangiumServices>>;
162
+ /**
163
+ * Modules composed LAST, after `adopter`, and partial over `TLanguage`
164
+ * rather than over Langium's per-language services. The language-tier
165
+ * twin of {@link IntegrationServicesOptions.sharedModules}'s
166
+ * `overrides`, and both differences carry the same weight they do
167
+ * there.
168
+ *
169
+ * A per-language framework service is constructed by the framework's
170
+ * own module with no options
171
+ * (`IntegrityService: services => new DefaultIntegrityService(services)`),
172
+ * so booting one configured differently means rebinding the slot.
173
+ * {@link extra} serves neither half of that: it composes BEFORE
174
+ * `adopter` and so loses every slot the adopter binds, and it is typed
175
+ * `PartialLangiumServices`, which cannot express a framework ADDITION
176
+ * like `IntegrityService` or `AstExtensionService` at all.
177
+ */
178
+ overrides?: ReadonlyArray<Module<TLanguage, DeepPartial<TLanguage>>>;
137
179
  };
138
180
 
139
181
  /**
@@ -197,6 +239,8 @@ export interface AdditionalLanguageModules<
197
239
  adopter?: (shared: TShared) => Module<TLanguage, PartialLangiumServices>;
198
240
  /** Protocol-head modules for this language. Defaults to the primary language's. */
199
241
  extra?: ReadonlyArray<Module<TLanguage, PartialLangiumServices>>;
242
+ /** Last-composed overrides for this language. Defaults to the primary language's. */
243
+ overrides?: ReadonlyArray<Module<TLanguage, DeepPartial<TLanguage>>>;
200
244
  }
201
245
 
202
246
  /**
@@ -247,7 +291,9 @@ export function createIntegrationServices<
247
291
  sharedModules.generated,
248
292
  sharedModule,
249
293
  ...(sharedModules.extra ?? []),
250
- sharedModules.adopter
294
+ sharedModules.adopter,
295
+ // Last, so an override outranks the adopter too — see `overrides`.
296
+ ...((sharedModules.overrides ?? []) as ReadonlyArray<Module<TShared, PartialLangiumSharedServices>>)
251
297
  ) as unknown as TShared;
252
298
 
253
299
  // Each language gets its OWN `createDefaultModule({ shared })` — the
@@ -256,19 +302,34 @@ export function createIntegrationServices<
256
302
  const composeLanguage = (
257
303
  generated: Module<TLanguage, PartialLangiumServices>,
258
304
  adopter: (shared: TShared) => Module<TLanguage, PartialLangiumServices>,
259
- extra: ReadonlyArray<Module<TLanguage, PartialLangiumServices>>
305
+ extra: ReadonlyArray<Module<TLanguage, PartialLangiumServices>>,
306
+ overrides: ReadonlyArray<Module<TLanguage, DeepPartial<TLanguage>>>
260
307
  ): TLanguage => {
261
308
  const defaultLanguageModule = createDefaultModule({ shared }) as unknown as Module<TLanguage, PartialLangiumServices>;
262
309
  const serverLanguageModule = createServerLanguageModule(context) as unknown as Module<TLanguage, PartialLangiumServices>;
263
- return inject(defaultLanguageModule, generated, serverLanguageModule, ...extra, adopter(shared)) as unknown as TLanguage;
310
+ return inject(
311
+ defaultLanguageModule,
312
+ generated,
313
+ serverLanguageModule,
314
+ ...extra,
315
+ adopter(shared),
316
+ // Last, so an override outranks the adopter too — see `overrides`.
317
+ ...(overrides as ReadonlyArray<Module<TLanguage, PartialLangiumServices>>)
318
+ ) as unknown as TLanguage;
264
319
  };
265
320
 
266
321
  const primaryExtra = (languageModules.extra ?? []) as ReadonlyArray<Module<TLanguage, PartialLangiumServices>>;
267
- const language = composeLanguage(languageModules.generated, languageModules.adopter, primaryExtra);
322
+ const primaryOverrides = languageModules.overrides ?? [];
323
+ const language = composeLanguage(languageModules.generated, languageModules.adopter, primaryExtra, primaryOverrides);
268
324
  const languages = [
269
325
  language,
270
326
  ...additionalLanguages.map(additional =>
271
- composeLanguage(additional.generated, additional.adopter ?? languageModules.adopter, additional.extra ?? primaryExtra)
327
+ composeLanguage(
328
+ additional.generated,
329
+ additional.adopter ?? languageModules.adopter,
330
+ additional.extra ?? primaryExtra,
331
+ additional.overrides ?? primaryOverrides
332
+ )
272
333
  )
273
334
  ];
274
335
 
@@ -75,6 +75,11 @@ export namespace IntegrityService {
75
75
  * at `DocumentState.Validated` and delivered separately via `publishDiagnostics`
76
76
  * or the `onModelUpdated` event.
77
77
  *
78
+ * The AST is what this guarantees, not the text. For a closed document whose
79
+ * repair was STAGED rather than written (`'editor'` sync mode),
80
+ * `textDocument` still mirrors disk — read the repair off the AST, or from
81
+ * the staged content the next open consumes.
82
+ *
78
83
  * Invariant: integrity rules only register at Parsed or Linked, so the build
79
84
  * is guaranteed post-integrity once it advances past `onBuildPhase(Linked)`
80
85
  * into `IndexedReferences`. {@link IntegrityService.register} enforces this at
@@ -317,10 +322,11 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
317
322
  return;
318
323
  }
319
324
 
325
+ const version = document.textDocument.version;
320
326
  // Keep the same version so HydraniumTextDocuments doesn't reject subsequent client edits.
321
327
  // Use manager.update so the `instanceof FullTextDocument` gate in the bare
322
328
  // `TextDocument.update` doesn't trip on adopter-custom text-document types.
323
- const textDocument = this.textDocuments.update(document.textDocument, [{ text: newText }], document.textDocument.version);
329
+ const textDocument = this.textDocuments.update(document.textDocument, [{ text: newText }], version);
324
330
 
325
331
  await this.syncCorrections(textDocument);
326
332
 
@@ -328,6 +334,14 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
328
334
  // Parsed-phase correction: re-parse only. The build pipeline still runs
329
335
  // IndexedContent → ComputedScopes → Linked → … on the fresh AST afterwards,
330
336
  // so the document reconciles naturally with no extra work here.
337
+ //
338
+ // Except for a closed document whose repair did not reach disk — `'editor'`
339
+ // sync mode stages it instead of writing it — where the re-parse is SKIPPED:
340
+ // Langium's factory gates the parse on the CST's `fullText`, which still
341
+ // equals the disk text the same call re-reads and redefines `textDocument`
342
+ // over. So `document.textDocument` is NOT post-integrity for such a
343
+ // document — it mirrors disk, and the AST is the only place the repair is
344
+ // legible, until an open consumes the staging.
331
345
  await this.documentBuilder.reparse(document, cancelToken);
332
346
  } else {
333
347
  // Linked-phase (or later) correction. The rule mutated the AST in place but
@@ -340,6 +354,26 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
340
354
  // stays in the builder, its proper home, rather than being duplicated here.
341
355
  await this.documentBuilder.reparseAndRelink(document, cancelToken);
342
356
  }
357
+
358
+ // Re-version against the REPAIRED text, after either branch. The repair is
359
+ // a content change the store has not seen: whatever reconciliation ran
360
+ // during the re-parse saw the text on disk, which in `'editor'` sync mode
361
+ // is the PRE-repair text. Leave the sequence describing that and the next
362
+ // open hashes the repair, finds a mismatch and steps the version again, so
363
+ // every `baseVersion` taken from this build is stale before it is used.
364
+ // Falls back to the pre-re-parse number for a URI the store never tracked,
365
+ // where there is no sequence to advance; an OPEN document answers
366
+ // `undefined` and keeps the store's own version, which is not this
367
+ // method's to move.
368
+ //
369
+ // Deliberately NOT gated on cancellation, unlike the entry to this method:
370
+ // `syncCorrections` has already written or staged the repair by now, so a
371
+ // preempted build that skipped this would leave the sequence describing
372
+ // text that is no longer there.
373
+ const reconciled = this.textDocuments.reconcileExternalContent(document.textDocument.uri, newText) ?? version;
374
+ if (document.textDocument.version !== reconciled) {
375
+ this.textDocuments.update(document.textDocument, [], reconciled);
376
+ }
343
377
  }
344
378
 
345
379
  /**
@@ -0,0 +1,87 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ /**
11
+ * Containment-array maintenance for callers that build or delete AST nodes
12
+ * programmatically — a GLSP operation handler being the usual one.
13
+ *
14
+ * **These live beside the key provider because the constraint is the key
15
+ * provider's.** `NameBasedKeyProvider` keys a node the `NameProvider` gives no
16
+ * name from its position, as `` `${$containerProperty}@${$containerIndex}` ``,
17
+ * so a node appended with a bare `array.push` carries neither and
18
+ * `getElementKey` answers `undefined` — the element has no key at all, and the
19
+ * GLSP index cannot address it. Deleting from the middle of such a list must
20
+ * renumber the survivors, or an id derived later in the same command names the
21
+ * wrong node. Shipping the constraint's statement without the code that
22
+ * discharges it leaves every adopter in that position re-deriving thirty lines
23
+ * from prose, and both failures are silent.
24
+ *
25
+ * The population is narrower than "anyone creating elements from a diagram": it
26
+ * takes a grammar with UNNAMED element types, programmatic creation or deletion
27
+ * of them, and this key provider. A grammar whose diagram elements are all
28
+ * named never reaches it, and the two ways out are named on the provider —
29
+ * give the type a name, or bind a provider whose fallback is content-derived.
30
+ *
31
+ * **Free functions, and deliberately not a step on the AST builder.** Building a
32
+ * node detached and appending it later is legitimate, so a containment step
33
+ * cannot be mandatory; and an optional fluent step could not check the index
34
+ * against the array unless it were handed the array, at which point it is these
35
+ * signatures with more ceremony. `AstNodeInit` already accepts the three fields
36
+ * for a caller who has them, which is the case these cover: it is the INDEX that
37
+ * cannot be known without reading the array.
38
+ */
39
+
40
+ import type { AstNode } from '@hydranium/langium';
41
+ import type { Mutable } from '@hydranium/protocol';
42
+
43
+ /**
44
+ * Append `child` to `children`, stamping the Langium containment plumbing
45
+ * (`$container`, `$containerProperty`, `$containerIndex`) so a positional key
46
+ * can address it.
47
+ *
48
+ * `property` and `children` are separate parameters rather than one property
49
+ * name the function dereferences: the array is what the index is derived from,
50
+ * so taking it directly is what makes the two agree by construction instead of
51
+ * by a lookup that a typo could point elsewhere.
52
+ *
53
+ * @returns `child`, so a caller can append and use it in one expression.
54
+ */
55
+ export function appendChild<TChild extends AstNode>(container: AstNode, property: string, children: TChild[], child: TChild): TChild {
56
+ const mutable = child as Mutable<TChild>;
57
+ mutable.$container = container;
58
+ mutable.$containerProperty = property;
59
+ mutable.$containerIndex = children.length;
60
+ children.push(child);
61
+ return child;
62
+ }
63
+
64
+ /**
65
+ * Remove every entry of `children` that is in `toRemove`, then renumber the
66
+ * survivors' `$containerIndex`.
67
+ *
68
+ * Mutates `children` in place rather than returning a new array, because the
69
+ * caller's array IS the AST's containment list — replacing the reference would
70
+ * leave the parent pointing at the old one.
71
+ *
72
+ * @returns how many entries were removed, so a caller can tell a no-op delete
73
+ * from one that changed the model without comparing lengths itself.
74
+ */
75
+ export function removeChildren<TChild extends AstNode>(children: TChild[], toRemove: ReadonlySet<TChild>): number {
76
+ if (toRemove.size === 0) {
77
+ return 0;
78
+ }
79
+ const survivors = children.filter(child => !toRemove.has(child));
80
+ const removed = children.length - survivors.length;
81
+ children.length = 0;
82
+ for (const [index, child] of survivors.entries()) {
83
+ (child as Mutable<TChild>).$containerIndex = index;
84
+ children.push(child);
85
+ }
86
+ return removed;
87
+ }
@@ -7,6 +7,7 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
+ export * from './containment.js';
10
11
  export * from './element-key-provider.js';
11
12
  export * from './name-based-key-provider.js';
12
13
  export * from './positional-key-provider.js';
@@ -55,6 +55,10 @@ import { type NameProvider } from '../naming/name-provider.js';
55
55
  * - After a removal, surviving siblings need their `$containerIndex`
56
56
  * renumbered, or a key derived afterwards addresses the wrong node.
57
57
  *
58
+ * {@link appendChild} and {@link removeChildren} discharge both, and ship
59
+ * beside this provider because the constraint is this provider's rather than
60
+ * any caller's.
61
+ *
58
62
  * Adopters that need insert / delete stability for unnamed types should
59
63
  * give those types a name in the grammar, or bind a key provider whose
60
64
  * fallback is content-derived rather than positional.
@@ -11,6 +11,7 @@ import {
11
11
  type CanonicalUri,
12
12
  type CloseModelArgs,
13
13
  ConflictError,
14
+ defineMessage,
14
15
  Logger,
15
16
  type MaybeObservableValue,
16
17
  type MaybePromise,
@@ -34,6 +35,23 @@ import { labelPhaseListener } from '../document-builder/labeled-phase-listener.j
34
35
  import { LANGUAGE_CLIENT_ID } from '../../documents/client-ids.js';
35
36
  import { type ServerSharedServices } from '../module.js';
36
37
 
38
+ /**
39
+ * The undo-stack entry for a server-authored write pushed to the editor.
40
+ *
41
+ * **A user-facing LABEL, not a log string**, which is easy to miss because it
42
+ * travels as an options field rather than as a message: LSP specifies
43
+ * `ApplyWorkspaceEditParams.label` as "presented in the user interface for
44
+ * example on an undo stack to undo the workspace edit". So a user who edits
45
+ * through a form or drags a diagram node reads this in their editor's undo menu
46
+ * — which is why it is rendered like any other message the server sends rather
47
+ * than left as the English literal it was.
48
+ *
49
+ * Parameterless deliberately. The obvious improvement is to name the document,
50
+ * and it is the wrong one: an undo menu is already grouped under the file, so
51
+ * the URI would be noise in the one place it is redundant.
52
+ */
53
+ export const MODEL_UPDATE_EDIT = defineMessage('hydranium/core/model-update-edit', 'Update Model');
54
+
37
55
  /** Max time {@link ModelService.settleSave} waits for the build to settle and the sync chain to drain. */
38
56
  const SAVE_SETTLE_TIMEOUT_MS = 10_000;
39
57
 
@@ -182,7 +200,74 @@ export interface ModelServiceOptions extends LogNameOptions {
182
200
  * args. Constrained to {@link TransferElement}. Defaults to the
183
201
  * structural base.
184
202
  */
185
- export class ModelService<
203
+ /**
204
+ * The seam every non-LSP head talks to: the data server, the GLSP head and an
205
+ * adopter's own services reach documents through this slot rather than through
206
+ * the workspace stores.
207
+ *
208
+ * **Two families, and the distinction matters more than the names suggest.**
209
+ * `waitFor*` is a pure wait — it never triggers a build, so a caller waiting on
210
+ * a document no build has touched waits until something else builds it.
211
+ * `ensureDocumentState` and the phase shorthands over it *dispatch*: warm
212
+ * documents are awaited, cold ones are built.
213
+ *
214
+ * **Diagnostics are typed `never` below `Validated`.** Validation is the last
215
+ * phase, so at any earlier landmark the array either is not yet computed or
216
+ * still holds the previous build's, and reading it would take stale results
217
+ * for fresh ones. A caller that needs diagnostics asks for {@link validated}.
218
+ *
219
+ * What the `never` buys, exactly: reading a field off an element is a compile
220
+ * error, and nothing can be appended. It does NOT stop a caller assigning an
221
+ * element to a typed variable, because `never` is assignable to everything — so
222
+ * this is a guard against reaching for diagnostics by accident, not a seal
223
+ * against doing it deliberately.
224
+ *
225
+ * The waits resolve at or ABOVE their target, so an already-validated document
226
+ * does carry usable diagnostics and the `never` over-forbids there. That
227
+ * direction is the safe one: the alternative permits stale reads silently. A
228
+ * member taking a phase as a PARAMETER cannot judge statically and so returns
229
+ * `TDiagnostic`, leaving the choice to the caller.
230
+ */
231
+ export interface ModelService<TAst extends AstNode, TDiagnostic = TransferDiagnostic, TTransfer extends TransferElement = TransferElement> {
232
+ /**
233
+ * Resolves once the workspace has been initialised and its first build has
234
+ * completed — the gate every read should wait behind, since a document
235
+ * queried before it may be unbuilt and reach no phase.
236
+ *
237
+ * A property rather than a method, matching `ProjectManager.ready` and
238
+ * Langium's `WorkspaceManager.ready`. An implementation needing a stricter
239
+ * gate supplies a Promise that awaits its own concern as well.
240
+ */
241
+ readonly ready: Promise<void>;
242
+
243
+ // Pure waits — never trigger a build.
244
+ waitForDocumentState(uri: string, state: DocumentState, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
245
+ waitForDocumentSettled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
246
+ waitForBuilderState(state: DocumentState, cancelToken?: CancellationToken): Promise<void>;
247
+
248
+ // Wait if warm, build if cold.
249
+ ensureDocumentState(uri: string, state?: DocumentState, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
250
+ rebuild(uri: string, state?: DocumentState, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
251
+ parsed(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
252
+ linked(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
253
+ settled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
254
+ indexed(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>>;
255
+ validated(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
256
+
257
+ update(args: TransferUpdateArgs<TTransfer>, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
258
+ save(args: TransferSaveArgs<TTransfer>, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
259
+
260
+ open(args: OpenModelArgs): Promise<Disposable>;
261
+ close(args: CloseModelArgs): Promise<void>;
262
+ isOpen(uri: string): boolean;
263
+ getDocument(uri: string): LangiumDocument | undefined;
264
+
265
+ onModelUpdated(uri: string, listener: (event: AstDocumentUpdatedEvent<TAst, TDiagnostic>) => void): Disposable;
266
+ onModelSaved(uri: string, listener: (event: AstDocumentSavedEvent<TAst, TDiagnostic>) => void): Disposable;
267
+ onClientClosed(uri: string, clientId: string, listener: () => void): Disposable;
268
+ }
269
+
270
+ export class DefaultModelService<
186
271
  TAst extends AstNode,
187
272
  TDiagnostic = TransferDiagnostic,
188
273
  /**
@@ -195,7 +280,7 @@ export class ModelService<
195
280
  * integrity service) that pass AST roots remain compatible.
196
281
  */
197
282
  TTransfer extends TransferElement = TransferElement
198
- > {
283
+ > implements ModelService<TAst, TDiagnostic, TTransfer> {
199
284
  protected readonly tracer: Tracer;
200
285
  /**
201
286
  * The single document-identity seam (`services.workspace.DocumentUriPolicy`),
@@ -329,8 +414,12 @@ export class ModelService<
329
414
  * {@link waitForDocumentState} for the common "wait until content is stable"
330
415
  * case (e.g. settling a save). Pure wait — does not trigger a build.
331
416
  */
332
- async waitForDocumentSettled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>> {
333
- return this.waitForDocumentStateCanonical(this.uriPolicy.canonicalUri(uri), IntegrityService.SettledState, cancelToken);
417
+ async waitForDocumentSettled(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, never>> {
418
+ return (await this.waitForDocumentStateCanonical(
419
+ this.uriPolicy.canonicalUri(uri),
420
+ IntegrityService.SettledState,
421
+ cancelToken
422
+ )) as AstDocument<TAst, never>;
334
423
  }
335
424
 
336
425
  /**
@@ -484,11 +573,22 @@ export class ModelService<
484
573
  *
485
574
  * Phase invariants encoded in the return type:
486
575
  * - `parsed` / `linked` / `settled` / `indexed` return
487
- * `AstDocument<TAst, never>` — diagnostics array is empty by phase
488
- * contract.
576
+ * `AstDocument<TAst, never>` — no diagnostics have been computed at
577
+ * those phases.
489
578
  * - `validated` returns `AstDocument<TAst, TDiagnostic>` — diagnostics
490
579
  * are populated.
491
580
  *
581
+ * **The `never` is a claim about the PHASE, not a guarantee about the
582
+ * instance, and the gap is reachable rather than theoretical.** The wait
583
+ * underneath resolves at or ABOVE the requested state, so a document
584
+ * something else already carried past `Validated` comes back from
585
+ * `settled()` with a populated diagnostics array typed `never`. Nothing
586
+ * strips it — the envelope copies the live document's array verbatim — and
587
+ * any host that validates its workspace before a consumer asks produces
588
+ * exactly that. So read an empty array as "none were computed, or there are
589
+ * none", never as "this document is clean", and call {@link validated} when
590
+ * the answer has to mean the second.
591
+ *
492
592
  * `settled` is the integrity-overlay name for "all integrity rules
493
593
  * have fired"; it maps to {@link IntegrityService.SettledState} (which
494
594
  * equals `DocumentState.IndexedReferences`), but the dedicated method
@@ -598,19 +698,29 @@ export class ModelService<
598
698
  // created from the payload rather than read from the filesystem — `update`
599
699
  // is an upsert. For an already-open document `open` refreshes content (the
600
700
  // text is ignored on that branch), so existing-document behaviour is
601
- // unchanged. `version` is intentionally NOT forwarded to `open`: a cold
602
- // create stays at its initial version, so a based-on-`version` update of a
603
- // not-yet-existing document still trips the conflict gate below.
701
+ // unchanged. `version` is intentionally NOT forwarded to `open`, so a cold
702
+ // create stays at its initial version rather than adopting a number the
703
+ // caller chose.
704
+ //
705
+ // The gate's version is read BEFORE that open, and must be: for a document
706
+ // no client holds open, the open assigns the shared version from the
707
+ // INCOMING text, so a version read afterwards has already absorbed the
708
+ // caller's own write. Gating on it rejected every modifying write to a
709
+ // closed document, having compared the caller's `baseVersion` against a
710
+ // number the caller itself produced — and a serialised round-trip that is
711
+ // not byte-identical to the stored text was enough to trigger it. Reading
712
+ // first keeps both cases the gate exists for: an unknown URI answers 0, so
713
+ // a based-on-version update of a not-yet-existing document still trips it,
714
+ // and a genuine conflict still trips it, another writer having advanced the
715
+ // sequence past the version the caller read.
716
+ const currentVersion = this.services.workspace.TextDocuments.version(uri);
604
717
  const text = await run('serialize', () => this.modelToText(uri, args.model, cancelToken));
605
718
  await run('open', () => this.open({ uri, clientId: args.clientId, text }));
606
- if (args.baseVersion !== undefined) {
607
- const current = this.services.workspace.TextDocuments.version(uri);
608
- if (current !== args.baseVersion) {
609
- // Distinct from the post-build "superseded" debug line below: this is a
610
- // based-on-stale rejection (the write never applies), not two writes racing.
611
- this.tracer.debug(`Conflict on ${uri}: based-on v${args.baseVersion} stale, server at v${current}`);
612
- throw new ConflictError(uri, args.baseVersion, current);
613
- }
719
+ if (args.baseVersion !== undefined && currentVersion !== args.baseVersion) {
720
+ // Distinct from the post-build "superseded" debug line below: this is a
721
+ // based-on-stale rejection (the write never applies), not two writes racing.
722
+ this.tracer.debug(`Conflict on ${uri}: based-on v${args.baseVersion} stale, server at v${currentVersion}`);
723
+ throw new ConflictError(uri, args.baseVersion, currentVersion);
614
724
  }
615
725
  const appliedVersion = await run('apply', () => this.services.workspace.AstDocumentManager.update(uri, text, args.clientId));
616
726
  // Dispatch through the public `rebuild` (which re-canonicalizes the already-
@@ -834,17 +944,17 @@ export class ModelService<
834
944
  *
835
945
  * The signal is "a known client other than the language client authored this
836
946
  * version **and** the URI was in the last build's changed set
837
- * (`isDirectChange`)". A framework-internal rebuild reports no author
947
+ * (`isTriggeringEdit`)". A framework-internal rebuild reports no author
838
948
  * (`getAuthor` → `undefined`), so it fails `hasKnownAuthor` without comparing
839
949
  * against a sentinel. This is NOT redundant with content/registration — it
840
950
  * distinguishes "client edited" from "framework rebuilt", which neither the
841
- * shadow nor `isDirectChange` alone can.
951
+ * shadow nor `isTriggeringEdit` alone can.
842
952
  */
843
953
  protected isNonLanguageClientEdit(document: LangiumDocument): boolean {
844
954
  const documents = this.services.workspace.AstDocumentManager;
845
955
  const author = documents.getAuthor(document);
846
956
  const hasKnownAuthor = !!author && author !== LANGUAGE_CLIENT_ID;
847
- return hasKnownAuthor && documents.isDirectChange(document.textDocument.uri);
957
+ return hasKnownAuthor && documents.isTriggeringEdit(document.textDocument.uri);
848
958
  }
849
959
 
850
960
  /**
@@ -878,13 +988,26 @@ export class ModelService<
878
988
  this.syncChains.set(uri, chain);
879
989
  }
880
990
 
991
+ /**
992
+ * The undo-stack label for a server-authored write, in the locale the server
993
+ * was handed at init.
994
+ *
995
+ * One method rather than the literal at each `applyEdit`, because the two
996
+ * call sites are the same edit — a push and its full-replace retry — and an
997
+ * undo menu showing two different words for one operation would read as two
998
+ * operations.
999
+ */
1000
+ protected editLabel(): string {
1001
+ return this.services.MessageRenderer.renderMessage(MODEL_UPDATE_EDIT);
1002
+ }
1003
+
881
1004
  protected async drainSyncQueue(uri: string): Promise<void> {
882
1005
  const uriLogger = this.tracer.withUri(uri);
883
1006
  while (this.pendingSync.has(uri)) {
884
1007
  const text = this.pendingSync.get(uri)!;
885
1008
  this.pendingSync.delete(uri);
886
1009
  try {
887
- let result = await this.services.workspace.TextDocuments.applyEditToLanguageClient(uri, text, { label: 'Update Model' });
1010
+ let result = await this.services.workspace.TextDocuments.applyEditToLanguageClient(uri, text, { label: this.editLabel() });
888
1011
  if (result?.applied === false && !this.pendingSync.has(uri)) {
889
1012
  // The push is addressed at the client's LAST DECLARED VERSION, so a
890
1013
  // rejection normally means the client's buffer moved while the
@@ -905,7 +1028,7 @@ export class ModelService<
905
1028
  // queued — best-effort, since a settle arriving later simply pushes
906
1029
  // after this and still wins.
907
1030
  uriLogger.warn(`Language client rejected applyEdit at its declared version — re-pushing a full replace`);
908
- result = await this.services.workspace.TextDocuments.applyEditToLanguageClient(uri, text, { label: 'Update Model' });
1031
+ result = await this.services.workspace.TextDocuments.applyEditToLanguageClient(uri, text, { label: this.editLabel() });
909
1032
  if (result?.applied === false) {
910
1033
  uriLogger.warn(`Language client rejected the full-replace retry too — client content is stale`);
911
1034
  }