@hydranium/core 1.0.0-next.205 → 1.0.0-next.206

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 (99) hide show
  1. package/lib/documents/ast-document-manager.js +4 -4
  2. package/lib/documents/ast-document-manager.js.map +1 -1
  3. package/lib/documents/client-ids.d.ts +1 -1
  4. package/lib/documents/client-ids.d.ts.map +1 -1
  5. package/lib/documents/client-ids.js +1 -1
  6. package/lib/documents/client-ids.js.map +1 -1
  7. package/lib/documents/client-session-registry.d.ts +24 -17
  8. package/lib/documents/client-session-registry.d.ts.map +1 -1
  9. package/lib/documents/client-session-registry.js +49 -29
  10. package/lib/documents/client-session-registry.js.map +1 -1
  11. package/lib/documents/dirty-state-tracker.d.ts +79 -0
  12. package/lib/documents/dirty-state-tracker.d.ts.map +1 -0
  13. package/lib/documents/dirty-state-tracker.js +69 -0
  14. package/lib/documents/dirty-state-tracker.js.map +1 -0
  15. package/lib/documents/document-release-handler.d.ts +174 -0
  16. package/lib/documents/document-release-handler.d.ts.map +1 -0
  17. package/lib/documents/document-release-handler.js +270 -0
  18. package/lib/documents/document-release-handler.js.map +1 -0
  19. package/lib/documents/document-release-scheduler.d.ts +67 -0
  20. package/lib/documents/document-release-scheduler.d.ts.map +1 -0
  21. package/lib/documents/document-release-scheduler.js +80 -0
  22. package/lib/documents/document-release-scheduler.js.map +1 -0
  23. package/lib/documents/hydranium-text-documents.d.ts +130 -455
  24. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  25. package/lib/documents/hydranium-text-documents.js +362 -835
  26. package/lib/documents/hydranium-text-documents.js.map +1 -1
  27. package/lib/documents/index.d.ts +5 -1
  28. package/lib/documents/index.d.ts.map +1 -1
  29. package/lib/documents/index.js +5 -1
  30. package/lib/documents/index.js.map +1 -1
  31. package/lib/documents/language-client-shadow.d.ts +221 -0
  32. package/lib/documents/language-client-shadow.d.ts.map +1 -0
  33. package/lib/documents/language-client-shadow.js +288 -0
  34. package/lib/documents/language-client-shadow.js.map +1 -0
  35. package/lib/documents/text-ledger.d.ts +64 -0
  36. package/lib/documents/text-ledger.d.ts.map +1 -0
  37. package/lib/documents/text-ledger.js +63 -0
  38. package/lib/documents/text-ledger.js.map +1 -0
  39. package/lib/langium/document-builder/document-builder.d.ts +8 -7
  40. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  41. package/lib/langium/document-builder/document-builder.js +8 -7
  42. package/lib/langium/document-builder/document-builder.js.map +1 -1
  43. package/lib/langium/integrity/integrity-service.d.ts +1 -1
  44. package/lib/langium/integrity/integrity-service.js +4 -4
  45. package/lib/langium/integrity/integrity-service.js.map +1 -1
  46. package/lib/langium/model-service/client-session.d.ts +4 -4
  47. package/lib/langium/model-service/client-session.js +2 -2
  48. package/lib/langium/model-service/client-session.js.map +1 -1
  49. package/lib/langium/model-service/model-events.d.ts +8 -11
  50. package/lib/langium/model-service/model-events.d.ts.map +1 -1
  51. package/lib/langium/model-service/model-service.d.ts +12 -4
  52. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  53. package/lib/langium/model-service/model-service.js +1 -1
  54. package/lib/langium/model-service/model-service.js.map +1 -1
  55. package/lib/langium/module.d.ts +7 -0
  56. package/lib/langium/module.d.ts.map +1 -1
  57. package/lib/langium/module.js +3 -1
  58. package/lib/langium/module.js.map +1 -1
  59. package/lib/langium/workspace/file-not-found.d.ts +7 -1
  60. package/lib/langium/workspace/file-not-found.d.ts.map +1 -1
  61. package/lib/langium/workspace/file-not-found.js +14 -3
  62. package/lib/langium/workspace/file-not-found.js.map +1 -1
  63. package/lib/lsp/hydranium-document-update-handler.d.ts +6 -6
  64. package/lib/lsp/hydranium-document-update-handler.js +10 -10
  65. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  66. package/lib/testing/make-test-services.d.ts +3 -0
  67. package/lib/testing/make-test-services.d.ts.map +1 -1
  68. package/lib/testing/make-test-services.js +5 -2
  69. package/lib/testing/make-test-services.js.map +1 -1
  70. package/lib/testing/stub-hydranium-text-documents.d.ts +9 -6
  71. package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
  72. package/lib/testing/stub-hydranium-text-documents.js +19 -11
  73. package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
  74. package/package.json +5 -5
  75. package/src/documents/ast-document-manager.ts +4 -4
  76. package/src/documents/client-ids.ts +1 -1
  77. package/src/documents/client-session-registry.ts +55 -34
  78. package/src/documents/dirty-state-tracker.ts +130 -0
  79. package/src/documents/document-release-handler.ts +351 -0
  80. package/src/documents/document-release-scheduler.ts +121 -0
  81. package/src/documents/hydranium-text-documents.ts +395 -1034
  82. package/src/documents/index.ts +5 -1
  83. package/src/documents/language-client-shadow.ts +463 -0
  84. package/src/documents/text-ledger.ts +112 -0
  85. package/src/langium/document-builder/document-builder.ts +8 -7
  86. package/src/langium/integrity/integrity-service.ts +4 -4
  87. package/src/langium/model-service/client-session.ts +6 -6
  88. package/src/langium/model-service/model-events.ts +12 -11
  89. package/src/langium/model-service/model-service.ts +13 -4
  90. package/src/langium/module.ts +9 -1
  91. package/src/langium/workspace/file-not-found.ts +15 -3
  92. package/src/lsp/hydranium-document-update-handler.ts +10 -10
  93. package/src/testing/make-test-services.ts +7 -2
  94. package/src/testing/stub-hydranium-text-documents.ts +34 -21
  95. package/lib/documents/language-client-text-shadow.d.ts +0 -133
  96. package/lib/documents/language-client-text-shadow.d.ts.map +0 -1
  97. package/lib/documents/language-client-text-shadow.js +0 -201
  98. package/lib/documents/language-client-text-shadow.js.map +0 -1
  99. package/src/documents/language-client-text-shadow.ts +0 -223
@@ -12,10 +12,14 @@ import { type URI } from '@hydranium/langium';
12
12
  import { type ServerSharedServices } from '../langium/module.js';
13
13
  import { type ApplyWorkspaceEditResult, type CancellationToken, type Connection, type DidChangeTextDocumentParams, type DidCloseTextDocumentParams, type DidOpenTextDocumentParams, type DidSaveTextDocumentParams, type Disposable, Emitter, type Event, type HandlerResult, type RequestHandler, type TextDocumentChangeEvent, type TextDocumentWillSaveEvent, type TextDocumentsConfiguration, type TextEdit, type WillSaveTextDocumentParams } from 'vscode-languageserver';
14
14
  import { type DocumentUri, TextDocument, type TextDocumentContentChangeEvent } from 'vscode-languageserver-textdocument';
15
- import { type CanonicalUri, type LanguageClientUri, type Stopwatch, type TextState, type TextVersion, type Tracer } from '@hydranium/protocol';
15
+ import { type CanonicalUri, type LanguageClientUri, type TextState, type TextVersion, type Tracer } from '@hydranium/protocol';
16
16
  import { type LogNameOptions } from '../langium/diagnostics/logger.js';
17
17
  import { type ClientSessionClosedEvent, ClientSessionRegistry, type SessionEndCause } from './client-session-registry.js';
18
- import { LanguageClientTextShadow } from './language-client-text-shadow.js';
18
+ import { type LanguageClientShadow } from './language-client-shadow.js';
19
+ import { type TextLedger } from './text-ledger.js';
20
+ import { type ReleasedDocument } from './document-release-handler.js';
21
+ import { type DocumentReleaseScheduler } from './document-release-scheduler.js';
22
+ import { type CleanAnnouncement, type DirtyStateTracker, type DocumentDirtyChangedEvent } from './dirty-state-tracker.js';
19
23
  export interface ClientTextDocumentChangeEvent<T> extends TextDocumentChangeEvent<T> {
20
24
  clientId: string;
21
25
  }
@@ -32,123 +36,29 @@ export interface HydraniumTextDocumentsOptions<T extends TextDocument = TextDocu
32
36
  readonly configuration?: TextDocumentsConfiguration<T>;
33
37
  /**
34
38
  * How long a document whose last open closed because its client's
35
- * connection was lost keeps its text before it reverts to disk. An open by
36
- * a client lost from the document, within this time of its own loss,
37
- * cancels the revert, so a client that registers again under its id after
39
+ * connection was lost keeps its text before the store releases it. An open
40
+ * by a client lost from the document, within this time of its own loss,
41
+ * cancels the release, so a client that registers again under its id after
38
42
  * a dropped connection finds its unsaved edits. Any other open releases the
39
43
  * document first and then opens it as a first open does, from the text the
40
44
  * opener supplies (an editor's own) or else from the file. Meanwhile the
41
45
  * document counts as open for the integrity service, which therefore writes
42
46
  * none of its unsaved text to disk. A close the client makes itself, or
43
- * ending its session, reverts at once whatever this is.
47
+ * ending its session, releases at once whatever this is.
44
48
  *
45
- * Defaults to 10 s. `0` reverts such a document at once as well, released
46
- * in the close itself rather than on a timer.
49
+ * Defaults to 10 s. `0` releases such a document at once as well, in the
50
+ * close itself rather than on a timer.
47
51
  */
48
- readonly revertGraceMs?: number;
52
+ readonly releaseGraceMs?: number;
49
53
  }
50
54
  /** Delivered by {@link HydraniumTextDocuments.onDidSaveInLanguageClient}. */
51
55
  export interface LanguageClientSavedEvent {
52
56
  readonly uri: string;
53
57
  }
54
- /** Delivered by {@link HydraniumTextDocuments.onDidCloseLastOpen}. */
55
- export interface LastOpenClosedEvent {
58
+ /** Delivered by {@link HydraniumTextDocuments.onDidReleaseDocument}. */
59
+ export interface DocumentReleasedEvent {
56
60
  readonly uri: CanonicalUri;
57
61
  }
58
- /** Delivered by {@link HydraniumTextDocuments.onDidChangeDirty}. */
59
- export interface DocumentDirtyChangedEvent {
60
- readonly uri: CanonicalUri;
61
- /**
62
- * The text the new answer of {@link HydraniumTextDocuments.isDirty} was
63
- * decided on. The answer changes with the text, before any build, so it can
64
- * name text whose model has not been sent yet. Absent when the document no
65
- * longer exists, or when the build that follows its release failed.
66
- */
67
- readonly text?: TextState;
68
- }
69
- /**
70
- * The language client's open of a document under one URI, from its didOpen to
71
- * its didClose. Each URI is its own editor buffer with its own version counter,
72
- * so a file reached through a symlink and its real path has one of these each.
73
- */
74
- export interface LanguageClientDocumentState {
75
- /** The version the client last declared for this URI. */
76
- declaredVersion: number;
77
- /**
78
- * The version an applied versioned push moved this URI to, ahead of its
79
- * echo. Apart from `declaredVersion`, whose staleness guard would drop that
80
- * echo and strand its pending push.
81
- */
82
- pushedVersion?: number;
83
- }
84
- /**
85
- * All per-URI client-facing tracking the manager keys by normalized URI,
86
- * collapsed into one record so a URI's full state lives in one place and the
87
- * last-client close clears every axis in a single delete. (Parallel per-axis
88
- * maps are the substrate of a close-on-stale-state desync — one map can be
89
- * cleared while another lingers.)
90
- *
91
- * Two neighbours deliberately stay separate:
92
- * - The inherited `__syncedDocuments` (Langium's parsed `TextDocument` store).
93
- * - {@link LanguageClientTextShadow} (`__shadow`), a self-contained,
94
- * separately-tested diff/apply-verify abstraction that owns its own baseline
95
- * text; folding its storage here would couple a clean utility to this record
96
- * for no real gain.
97
- *
98
- * Returned by the `protected` {@link HydraniumTextDocuments.trackingFor}, so an
99
- * override has to name it. Restating the shape structurally instead compiles
100
- * until a field is added here, and then fails at the adopter rather than at the
101
- * change that caused it.
102
- */
103
- export interface DocumentTrackingRecord {
104
- /** Author of each version, sparse-indexed by the SHARED (server-assigned) version number. */
105
- readonly versionAuthors: string[];
106
- /**
107
- * Last version id each client declared for this document (didOpen baseline,
108
- * advanced by every accepted didChange), for the per-client staleness guard.
109
- * Client version ids are CLIENT-owned per LSP (Monaco numbers its own
110
- * buffer) — they never leak into the shared version sequence, which the
111
- * server assigns (see {@link HydraniumTextDocuments.__versionSequences}).
112
- * The language client's entry is the latest any of its URIs declared; the
113
- * store checks and addresses it per URI, through
114
- * {@link DocumentTrackingRecord.languageClientDocuments}.
115
- */
116
- readonly clientVersions: Map<string, number>;
117
- /** Content staged by integrity rules for a closed document. Consumed on next open. */
118
- pendingContent?: string;
119
- /**
120
- * Each URI the LSP textual language client opened this (canonically-keyed)
121
- * document under, with that open's state. Usually one; more when the same
122
- * file is opened under a symlink path and its real path. These are the
123
- * egress addresses: the document is *keyed* by its canonical identity, but
124
- * Monaco holds it under the URI it opened. The language client holds the
125
- * document while any entry remains.
126
- */
127
- languageClientDocuments?: Map<LanguageClientUri, LanguageClientDocumentState>;
128
- /**
129
- * The clients whose close of this document was caused by a lost connection
130
- * and that have not opened it again, each with a stopwatch started at its
131
- * loss on the store's `Clock`. While the document waits out the revert grace, only an
132
- * open by one of them within its own
133
- * {@link HydraniumTextDocumentsOptions.revertGraceMs} of that loss cancels
134
- * the revert. Every such client counts, not only the last to close: one
135
- * connection's sessions all end lost together, and any of them may reopen
136
- * first. Without the time limit, a client that returns late inherits the
137
- * unsaved text of a holder lost after it. A stopwatch rather than a `now()`
138
- * reading, because a wall-clock step would otherwise expire a claim early or
139
- * revive one past its grace, against a grace timer that the step leaves
140
- * alone. An entry past its grace counts as any other client's and is pruned
141
- * at the next open of the document.
142
- */
143
- lostClients?: Map<string, Stopwatch>;
144
- /**
145
- * The text the server last knew the file to hold, `undefined` for no file.
146
- * Set by the first open and moved by {@link HydraniumTextDocuments.updateDiskBaseline}.
147
- */
148
- diskBaseline?: string;
149
- /** The last answer {@link HydraniumTextDocuments.onDidChangeDirty} announced. */
150
- dirty?: boolean;
151
- }
152
62
  /**
153
63
  * A document held open by at least one client, with the client ids holding it —
154
64
  * one entry of {@link HydraniumTextDocuments.openDocuments}.
@@ -179,199 +89,72 @@ export type RepairCommit<T extends TextDocument> = {
179
89
  readonly status: 'not-open';
180
90
  };
181
91
  /**
182
- * Where a URI's shared version sequence left off while no client holds it —
183
- * started by its first build, stepped by builds of changed text, written at
184
- * last-client close, and consulted at the next open so the sequence CONTINUES
185
- * instead of restarting at whatever version id the opening client declares. One entry
186
- * of {@link HydraniumTextDocuments.__versionSequences} — a `protected` field, so
187
- * a subclass reading the map has to name what it holds.
188
- */
189
- export interface VersionSequence {
190
- /** The shared version of the text last closed or built. */
191
- readonly version: number;
192
- /** {@link textHash} of that text. */
193
- readonly contentHash: string;
194
- }
195
- /**
196
- * One text pushed to the LSP textual language client by
197
- * {@link HydraniumTextDocuments.applyEditToLanguageClient} whose echo has not
198
- * come back yet. One entry of {@link HydraniumTextDocuments.__pendingPushes} —
199
- * a `protected` field, so a subclass reading the queue has to name what it holds.
200
- */
201
- export interface PendingLanguageClientPush {
202
- /**
203
- * The text the client held BEFORE this push, and therefore the text its
204
- * echo addresses with its ranges. Wider than the baseline the push's edits
205
- * were diffed against: a full replace is sent with no diff baseline and
206
- * still lands on a buffer the echo is keyed to.
207
- *
208
- * `undefined` only when that buffer is unknown — the client never declared
209
- * one, or a rejection invalidated what was tracked. The echo is then
210
- * reconstructed against the synced text, which is sound only for a
211
- * position-independent (full-text) change.
212
- */
213
- readonly before: string | undefined;
214
- /** {@link textHash} of the text this push moves the client to. */
215
- readonly afterHash: string;
216
- }
217
- /**
218
- * What an incoming language-client change turns out to be once reconstructed
219
- * against the buffer its ranges address — the return of
220
- * {@link HydraniumTextDocuments.classifyLanguageClientChange}. That method is
221
- * `protected`, so an override has to name every arm it can return.
222
- */
223
- export type LanguageClientChangeOrigin =
224
- /** The client is reporting a text we pushed it. The synced document is already there. */
225
- {
226
- readonly kind: 'echo';
227
- }
228
- /**
229
- * The client's buffer holds a text we did not push it — a keystroke that
230
- * raced a push, or an edit to a buffer the store has already been written
231
- * past. The reconstructed text is what it now holds, and is authoritative.
232
- */
233
- | {
234
- readonly kind: 'divergent';
235
- readonly text: string;
236
- }
237
- /**
238
- * The change carries ranges and no known text addresses them, so no
239
- * reconstruction is offered. Adopting one anyway splices the document and
240
- * stores an edit nobody made; dropping costs at most the one keystroke the
241
- * client still holds and the next push contradicts.
242
- */
243
- | {
244
- readonly kind: 'unreconstructable';
245
- };
246
- /**
247
- * Multi-client text-document tracking on top of Langium's `NormalizedTextDocuments`.
92
+ * The one text store every head writes to, on top of Langium's
93
+ * `NormalizedTextDocuments`, and the LSP text-sync endpoint: every
94
+ * `textDocument/*` notification and every `workspace/applyEdit` push goes
95
+ * through here.
96
+ *
97
+ * Each open, change, close and save is one synchronous transition over the
98
+ * held document and the collaborators its `create…` methods build. A
99
+ * collaborator that defers its part lets a listener of the transition's event
100
+ * read state from before it. A document no client holds any more is
101
+ * released to the `DocumentReleaseHandler` slot, after
102
+ * {@link HydraniumTextDocumentsOptions.releaseGraceMs} when its last client's
103
+ * connection was lost.
248
104
  *
249
- * Adds the framework features used by the integrity, model-server, and GLSP layers:
250
- * - Per-document client membership (multiple clients can attach to the same
251
- * URI) and the client-session table, both kept by {@link __sessions}.
252
- * - A SERVER-OWNED shared version sequence: per-URI, monotonic across
253
- * close/reopen cycles, advancing exactly when the synced content changes.
254
- * Client-declared version ids (Monaco's buffer numbering) feed only a
255
- * per-client staleness guard and never leak into a running sequence —
256
- * the two are different things (an editor's edit-operation counter vs the
257
- * document's content-revision number), and splicing them lets versions drift
258
- * silently past base-version gate holders. A URI with no sequence, and no
259
- * root that records a version, starts at its opener's declared id: no version
260
- * was handed out for it.
261
- * - Version-author history so each edit is attributable to its originating client.
262
- * - The revert to disk once no client has a document open, for every head
263
- * ({@link revertToDisk}), deferred by
264
- * {@link HydraniumTextDocumentsOptions.revertGraceMs} after a lost connection.
265
- * - A disk baseline per open document, and whether its text differs from it
266
- * ({@link isDirty}).
267
- * - Pending-content staging used by the integrity service to thread corrections
268
- * through `workspace/applyEdit` cycles for currently-closed documents.
269
- * - `didOpen` notifications arriving over the LSP connection wait on the
270
- * workspace-ready promise, so a client's first open cannot race workspace
271
- * discovery. Direct {@link notifyDidOpenTextDocument} calls (the non-LSP
272
- * heads) do not pass that gate — their caller owns the ordering.
105
+ * Client-declared version ids feed only the per-client staleness guard and
106
+ * never a running shared sequence: the two count different things, and
107
+ * splicing them lets versions drift past base-version gate holders. A URI
108
+ * with no ledger record and no built root starts at its opener's declared id:
109
+ * no version was handed out for it.
110
+ *
111
+ * `didOpen` notifications arriving over the LSP connection wait on the
112
+ * workspace-ready promise, so a client's first open cannot race workspace
113
+ * discovery. Direct {@link notifyDidOpenTextDocument} calls (the non-LSP
114
+ * heads) do not pass that gate — their caller owns the ordering.
273
115
  */
274
116
  export declare class HydraniumTextDocuments<T extends TextDocument = TextDocument> extends NormalizedTextDocuments<T> {
275
117
  protected services: ServerSharedServices;
118
+ protected readonly options: HydraniumTextDocumentsOptions<T>;
119
+ /** Content staged by integrity rules for a document no client holds, consumed by its first open. */
120
+ protected readonly __pendingContent: Map<CanonicalUri, string>;
276
121
  /**
277
- * Per-URI client-facing tracking ({@link DocumentTrackingRecord}), keyed by
278
- * canonical URI. One record per URI, so dropping it clears every axis at
279
- * once. Which client has the document open is kept apart, in
280
- * {@link __sessions}.
281
- */
282
- protected __documents: Map<CanonicalUri, DocumentTrackingRecord>;
283
- /**
284
- * Which client has which document open, and which client ids are registered
285
- * sessions. Every open-state predicate on this class reads it, so an open
122
+ * Which client has which document open, which client ids are registered
123
+ * sessions, and each client's declared version, the staleness guard's
124
+ * baseline. Every open-state predicate on this class reads it, so an open
286
125
  * recorded anywhere else is invisible to the last-close transition.
287
126
  */
288
127
  protected readonly __sessions: ClientSessionRegistry;
289
- /**
290
- * Per-URI shared-version continuity across close/reopen cycles
291
- * ({@link VersionSequence}), kept for every document a build or a client
292
- * gave the store, and consulted by the next first-client open. A URI
293
- * with none, and no root that records a version, starts at its opener's
294
- * declared version.
295
- * DELIBERATELY outside {@link DocumentTrackingRecord}: that record is deleted on last close, while the version sequence must
296
- * survive it — the shared version is a server-owned, monotonic,
297
- * advances-iff-content-changes counter that never resets while the server
298
- * lives. That invariant is what makes an optimistic base-version gate
299
- * sound: "version unchanged ⇔ content unchanged", with no false conflicts
300
- * from close/reopen version resets and no false passes from a reopened
301
- * sequence coincidentally landing on a stale writer's number.
302
- *
303
- * Never pruned, not even when the file is deleted: a recreated file
304
- * restarting at `0` would let a write based on the deleted text pass. Two
305
- * small values per URI ever built, however often it changes.
306
- */
307
- protected readonly __versionSequences: Map<CanonicalUri, VersionSequence>;
308
- /**
309
- * Released documents last announced dirty. Their clean flip waits for the
310
- * revert, so it carries the reverted text's version; a first open before the
311
- * revert takes the entry over, and its own dirty answer decides the flip.
312
- * Each release enters a token of its own, so the revert of an earlier
313
- * release cannot announce a later one clean before that one's revert.
314
- */
315
- protected readonly __releasedDirty: Map<CanonicalUri, object>;
316
- /** Per held document, the {@link textHash} of its text at the version it was taken. */
317
- protected readonly __textHashes: WeakMap<TextDocument, {
318
- readonly version: number;
319
- readonly hash: string;
320
- }>;
321
- /**
322
- * Texts pushed to the LSP textual language client via
323
- * {@link applyEditToLanguageClient} whose echoes have not come back yet
324
- * ({@link PendingLanguageClientPush}), keyed like the shadow by language-client URI.
325
- * Outbound pushes and inbound echoes are uncorrelated on the wire; this
326
- * FIFO is the explicit correlation, and it is what
327
- * {@link classifyLanguageClientChange} reconstructs against.
328
- *
329
- * **Each entry keeps the client's PRE-push text, not only a hash of the
330
- * post-push one.** A hash alone can classify a full-text echo, whose
331
- * application is a no-op either way — and that is all it ever classified,
332
- * because a conforming client echoes INCREMENTAL ranges keyed to its
333
- * previous buffer. Those ranges cannot be applied to the synced text (which
334
- * the authored write already advanced) and cannot be reconstructed without
335
- * the baseline: the line they insert lands twice, validates cleanly, and
336
- * compounds on every later edit.
337
- *
338
- * Lifecycle: entries are consumed by the matching echo (together with any
339
- * older entries it supersedes), and the whole queue drops when the client
340
- * stops being a pure mirror — a divergent change, an `applyEdit`
341
- * failure/rejection (shadow invalidation), a close, or an explicit
342
- * shadow (re)baseline. {@link PENDING_ECHO_CAP} bounds the queue against
343
- * a pathological echo that never arrives; the memory cost until then is
344
- * one pre-push text per in-flight push, for milliseconds.
345
- */
346
- protected readonly __pendingPushes: Map<LanguageClientUri, PendingLanguageClientPush[]>;
347
- /**
348
- * Tracked text content per URI for the LSP textual language client (Monaco / VS Code).
349
- * Owned here so {@link applyEditToLanguageClient} can compute minimal `workspace/applyEdit`
350
- * diffs instead of full-document replaces (5–20 s → <500 ms on 20-30 KB YAML diagrams).
351
- *
352
- * Auto-tracked from the multi-client text-document events: open / change / close of
353
- * the language client (re)baseline or invalidate the shadow. Other client ids
354
- * (form editor, GLSP, integrity) do NOT touch the shadow — only what Monaco believes
355
- * it has matters for the diff.
356
- *
357
- * Apply-verify safety net is built in: if the diff doesn't reconstruct `newText`
358
- * exactly, {@link LanguageClientTextShadow.computeEdits} falls back to a full-range
359
- * replace and invokes the `onFallback` callback — a diff regression becomes log
360
- * noise, not a 0-byte save.
361
- *
362
- * Assigned in the constructor body so the `onFallback` callback can capture
363
- * `this.logger` after the parameter-property assignment has run (field
364
- * initializers fire BEFORE parameter-property assignment in TS).
365
- */
366
- protected readonly __shadow: LanguageClientTextShadow;
367
128
  protected readonly tracer: Tracer;
368
129
  protected readonly configuration: TextDocumentsConfiguration<T>;
369
- /** See {@link HydraniumTextDocumentsOptions.revertGraceMs}. */
370
- protected readonly revertGraceMs: number;
371
- protected readonly lastOpenClosedEmitter: Emitter<LastOpenClosedEvent>;
130
+ protected __textLedger: TextLedger | undefined;
131
+ protected __languageClientShadow: LanguageClientShadow | undefined;
132
+ protected __dirtyStateTracker: DirtyStateTracker | undefined;
133
+ protected __documentReleaseScheduler: DocumentReleaseScheduler | undefined;
134
+ protected readonly documentReleasedEmitter: Emitter<DocumentReleasedEvent>;
372
135
  protected readonly languageClientSavedEmitter: Emitter<LanguageClientSavedEvent>;
373
- protected readonly dirtyChangedEmitter: Emitter<DocumentDirtyChangedEvent>;
374
136
  constructor(services: ServerSharedServices, options?: HydraniumTextDocumentsOptions<T>);
137
+ protected get textLedger(): TextLedger;
138
+ protected get languageClientShadow(): LanguageClientShadow;
139
+ protected get dirtyStateTracker(): DirtyStateTracker;
140
+ protected get documentReleaseScheduler(): DocumentReleaseScheduler;
141
+ protected createTextLedger(): TextLedger;
142
+ protected createLanguageClientShadow(): LanguageClientShadow;
143
+ protected createDirtyStateTracker(): DirtyStateTracker;
144
+ protected createDocumentReleaseScheduler(): DocumentReleaseScheduler;
145
+ /** Hold `document` as the text of `key`, a new version authored by `author`. */
146
+ protected commitText(key: CanonicalUri, document: T, author: string): void;
147
+ /**
148
+ * Apply `changes` to `document`, the held text of `key`, and hold the
149
+ * result. The version steps only when the text changes, which is what a
150
+ * base-version gate relies on: unchanged text keeps its version and its
151
+ * author. The new text is known only once the changes are applied, so they
152
+ * go in at a tentative step that an identical result rolls back.
153
+ */
154
+ protected commitChange(key: CanonicalUri, document: T, changes: TextDocumentContentChangeEvent[], author: string): {
155
+ document: T;
156
+ changed: boolean;
157
+ };
375
158
  create(uri: string, languageId: string, version: number, content: string): T;
376
159
  update(document: T, changes: TextDocumentContentChangeEvent[], version: number): T;
377
160
  protected get __syncedDocuments(): Map<string, T>;
@@ -407,64 +190,38 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
407
190
  applyContentChange(uri: DocumentUri, text: string, clientId: string): TextVersion;
408
191
  /**
409
192
  * Close `clientId`'s open of the document. When it was the last open, the
410
- * document is released and reverts to disk, at once or, for a `'lost'`
411
- * close, after {@link HydraniumTextDocumentsOptions.revertGraceMs}.
193
+ * document is released, at once or, for a `'lost'`
194
+ * close, after {@link HydraniumTextDocumentsOptions.releaseGraceMs}.
412
195
  */
413
196
  notifyDidCloseTextDocument(event: DidCloseTextDocumentParams, clientId?: string, cause?: SessionEndCause): void;
414
197
  /**
415
- * Keep the document, text and all, for the revert grace, then release it.
198
+ * Keep the document, text and all, for the release grace, then release it.
416
199
  * The document stays in the store meanwhile, so a lost client that opens it
417
200
  * again within its own grace attaches to it and finds its unsaved text
418
- * rather than reading disk; see {@link resolvePendingRevert} for any other
201
+ * rather than reading disk; see {@link resolveDeferredRelease} for any other
419
202
  * open.
420
203
  */
421
204
  protected deferRelease(uri: CanonicalUri): void;
205
+ /** Resolve a deferred release of `uri` for an open by `clientId`: a release the scheduler decides on runs now. */
206
+ protected resolveDeferredRelease(uri: CanonicalUri, clientId: string): void;
422
207
  /**
423
- * Resolve the pending revert of `uri` for an open by `clientId`. An open by a
424
- * client lost from the document within its grace cancels the revert, and
425
- * the client finds its unsaved text. Any other open releases the document
426
- * first, so it opens as a first open does: cancelling for every open hands
427
- * a lost client's unsaved text to whoever opens next, a reloaded page or an
428
- * editor, with nothing marking it unsaved.
429
- */
430
- protected resolvePendingRevert(uri: CanonicalUri, clientId: string): void;
431
- /** Drop the entries of {@link DocumentTrackingRecord.lostClients} whose grace has run out. */
432
- protected pruneLostClients(record: DocumentTrackingRecord): void;
433
- /**
434
- * Drop the document no client has open any more, then announce it on
435
- * {@link onDidCloseLastOpen}, which is what reverts it to disk.
208
+ * Drop the document no client has open any more, announce it on
209
+ * {@link onDidReleaseDocument}, then hand it to the
210
+ * `DocumentReleaseHandler` slot: its listeners, such as the update
211
+ * handler dropping a change it still holds back, act before any build the
212
+ * handler runs.
436
213
  */
437
214
  protected releaseDocument(uri: CanonicalUri): void;
438
215
  /**
439
- * Rebuild a released document from the file system provider, so the build
440
- * stops carrying the unsaved text of its last client.
441
- *
442
- * The provider decides, for every scheme, by `exists`: a document it can
443
- * serve is rebuilt from its text, and any other — an editor's `untitled:`
444
- * buffer, a file never saved, or one deleted meanwhile — is removed
445
- * from the workspace. A `virtual:` document survives, since the framework's
446
- * provider for that scheme serves it from the index, whatever provider the
447
- * host passes as `context.fileSystemProvider`; an edited one therefore keeps
448
- * its last client's text, and keeping it read-only is the client's job.
449
- *
450
- * The answer is read in the document's disk queue, so the rebuild follows
451
- * any save still queued rather than reverting past it; a file that goes
452
- * after that read is removed when its rebuild finds none.
453
- *
454
- * Whether to revert at all is decided inside the write lock, as its holder:
455
- * decided before waiting for the lock, a client that opens or re-creates the
456
- * document meanwhile would have its text rebuilt over, or the document
457
- * removed. A document some client has open again, or that waits out a new
458
- * grace, is left to that client.
459
- *
460
- * A document released dirty is announced clean, so a watcher is never left
461
- * holding it dirty: once the revert has parsed the file, even if it is then
462
- * cancelled, or else once the build it requests in its place has parsed it,
463
- * as the announcement names the store's text. One the revert or that build
464
- * removed, or that build failed, is announced without text. A reopen before
465
- * that takes the announcement over.
466
- */
467
- protected revertToDisk(uri: CanonicalUri): Promise<void>;
216
+ * Call the `DocumentReleaseHandler` slot, and announce a document released
217
+ * dirty clean once the promise it returns settles, which is after the
218
+ * release event. A failure is logged rather than thrown: the store has let
219
+ * go of the document by now, and a throw would abort the transition that
220
+ * released it, a session's close of its other documents included.
221
+ */
222
+ protected handOverRelease(released: ReleasedDocument, cleanAnnouncement: CleanAnnouncement | undefined): void;
223
+ /** `uri` as the `DocumentReleaseHandler` slot receives it. */
224
+ protected toReleasedDocument(uri: CanonicalUri): ReleasedDocument;
468
225
  notifyWillSaveTextDocument(event: WillSaveTextDocumentParams): void;
469
226
  notifyWillSaveTextDocumentWaitUntil(event: WillSaveTextDocumentParams, token: CancellationToken): HandlerResult<TextEdit[], void>;
470
227
  /**
@@ -493,24 +250,12 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
493
250
  * and refreshing per attach turns each into a Langium rebuild and dependent
494
251
  * relink cascade.
495
252
  *
496
- * The staleness guard baselines at the SYNCED version, not a declared buffer
497
- * version, because a client arriving this way holds no buffer of its own.
498
- * Unifying the two routes therefore rebaselines a textual client's guard to a
499
- * version it never declared.
500
- *
501
253
  * Returns whether a hold was added — `false` when `uri` is not open,
502
- * `clientId` already holds it, or the document was waiting out the revert
254
+ * `clientId` already holds it, or the document was waiting out the release
503
255
  * grace for other clients and was released instead (see
504
- * {@link resolvePendingRevert}); the caller then opens it anew.
256
+ * {@link resolveDeferredRelease}); the caller then opens it anew.
505
257
  */
506
258
  attachClient(uri: DocumentUri, clientId: string): boolean;
507
- /**
508
- * The built root's recorded version, one on when `text` differs from the root's;
509
- * `declared` when the root records no store version. Seeded from `declared`, a write based
510
- * on the root passes the gate over other text, and the same text looks newer than its model.
511
- * A built root has no sequence only under a `LangiumDocuments` that does not reconcile at registration.
512
- */
513
- protected firstOpenVersion(uri: CanonicalUri, text: string, declared: number): number;
514
259
  protected logClientJoined(uri: DocumentUri, clientId: string, version: number, existingClients: readonly string[]): void;
515
260
  refreshContent(uri: DocumentUri, clientId: string): void;
516
261
  /**
@@ -520,7 +265,7 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
520
265
  * two URIs for one physical file (a symlink path and its real path) into a
521
266
  * single registration — dedup at the editor layer, not just in
522
267
  * `LangiumDocuments`. The URI the client opened under is preserved separately
523
- * for egress addressing (see {@link DocumentTrackingRecord.languageClientDocuments}). The
268
+ * for egress addressing, by the {@link LanguageClientShadow}. The
524
269
  * policy is always bound (the framework defaults it to
525
270
  * `DefaultDocumentUriPolicy`, where canonical ≡ syntactic normalize).
526
271
  */
@@ -534,8 +279,6 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
534
279
  * from its real path (symlink / `..` / case).
535
280
  */
536
281
  protected toLanguageClientUri(uri: string): LanguageClientUri;
537
- /** Get-or-create the per-URI tracking record. `uri` must already be a {@link documentKey}. */
538
- protected trackingFor(uri: CanonicalUri): DocumentTrackingRecord;
539
282
  setAuthor(uri: DocumentUri, version: number, author: string): void;
540
283
  /**
541
284
  * Resolve the synced `TextDocument` for `uri`. The store keys documents by
@@ -572,14 +315,18 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
572
315
  */
573
316
  textState(uri: DocumentUri): TextState | undefined;
574
317
  /**
575
- * {@link textHash} of `document`'s text, taken once per version: the store
576
- * moves a document's version with every change of its text.
318
+ * The built root's recorded version for a first open of `key` with `text`,
319
+ * one on when the text differs from the root's; `undefined` when the root
320
+ * records no store version. Seeded from the opener's declared version
321
+ * instead, a write based on the built root passes the gate over other text,
322
+ * and the same text looks newer than its model. A built root records none
323
+ * only under a `LangiumDocuments` that does not reconcile at registration.
577
324
  */
578
- protected heldTextHash(document: T): string;
325
+ protected builtRootOpeningVersion(key: CanonicalUri, text: string): TextVersion | undefined;
579
326
  /**
580
327
  * Reconcile the persisted version sequence with content that reached the
581
328
  * build OUTSIDE the store's write paths — a closed document rebuilt from
582
- * disk (last-close revert) or replaced by a watched-file change. Steps the
329
+ * disk after its release, or replaced by a watched-file change. Steps the
583
330
  * sequence iff `text` differs from the sequence's last-known content and
584
331
  * returns the resulting sequence version so the caller can re-stamp the
585
332
  * rebuilt document (`VersionSyncService.modelProduced`) —
@@ -633,8 +380,10 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
633
380
  * `onDidClose` event fires for the last client, so `isOpen` returns `true`
634
381
  * during the close event itself. `isOpenInAnyClient` reads the open table
635
382
  * ({@link __sessions}), which is updated BEFORE the fire, so an `onDidClose`
636
- * subscriber that finds this `false` knows the last client just closed and a
637
- * disk re-read / rebuild can proceed.
383
+ * subscriber that finds this `false` knows the last client just closed.
384
+ * What the build keeps is the release's: a subscriber that re-read or
385
+ * rebuilt the document would race the `DocumentReleaseHandler`, and after a
386
+ * lost connection the release waits out its grace.
638
387
  */
639
388
  isOpenInAnyClient(uri: DocumentUri): boolean;
640
389
  isOpenInClient(uri: DocumentUri, client: string): boolean;
@@ -657,11 +406,11 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
657
406
  get onDidCloseSession(): Event<ClientSessionClosedEvent>;
658
407
  /**
659
408
  * Fires once a document no client has open is released: at its last close,
660
- * or, for a lost client's last close, when its revert grace runs out,
661
- * another client opens it, or it is deleted. The document then reverts to
662
- * disk.
409
+ * or, for a lost client's last close, when its release grace runs out,
410
+ * another client opens it, or it is deleted, before the
411
+ * `DocumentReleaseHandler` slot is handed the document.
663
412
  */
664
- get onDidCloseLastOpen(): Event<LastOpenClosedEvent>;
413
+ get onDidReleaseDocument(): Event<DocumentReleasedEvent>;
665
414
  /**
666
415
  * Fires for every save the language client reports of a document it has
667
416
  * open: the editor has written the file. {@link onDidSave} follows only
@@ -669,16 +418,16 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
669
418
  */
670
419
  get onDidSaveInLanguageClient(): Event<LanguageClientSavedEvent>;
671
420
  /**
672
- * Whether `uri` is waiting out the revert grace: its last open closed with a
421
+ * Whether `uri` is waiting out the release grace: its last open closed with a
673
422
  * lost connection, and it still holds its unsaved text. Such a document is
674
423
  * open for no client, yet not closed either, so a caller that would persist
675
424
  * a closed document's text to disk treats it as open.
676
425
  */
677
- isRevertPending(uri: DocumentUri): boolean;
426
+ isReleaseDeferred(uri: DocumentUri): boolean;
678
427
  /**
679
428
  * Whether the store holds `uri` with text that differs from its disk
680
429
  * baseline: what the server last knew the file to hold. `false` for a URI
681
- * the store does not hold; a document waiting out the revert grace is still
430
+ * the store does not hold; a document waiting out the release grace is still
682
431
  * held.
683
432
  *
684
433
  * The baseline is the text a first open brought, or what the server wrote,
@@ -688,10 +437,11 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
688
437
  */
689
438
  isDirty(uri: DocumentUri): boolean;
690
439
  /**
691
- * Fires each time the answer of {@link isDirty} changes. A dirty document's
692
- * release fires once a parse of the file reaches the store, at that text's
693
- * version, or without text once its revert removed the document or could
694
- * not rebuild it, though {@link isDirty} answers clean from the release on.
440
+ * Fires each time the answer of {@link isDirty} changes. For a document
441
+ * released dirty it fires once the `DocumentReleaseHandler` slot reports the
442
+ * release settled: at the version of the text the build then holds, or
443
+ * without text when the document is gone or the handler failed, though
444
+ * {@link isDirty} answers clean from the release on.
695
445
  */
696
446
  get onDidChangeDirty(): Event<DocumentDirtyChangedEvent>;
697
447
  /**
@@ -699,15 +449,13 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
699
449
  * `undefined`. A no-op for a URI the store does not hold: the next first
700
450
  * open sets the baseline from its own text.
701
451
  */
702
- updateDiskBaseline(uri: DocumentUri, text: string | undefined): void;
452
+ setDiskBaseline(uri: DocumentUri, text: string | undefined): void;
703
453
  /**
704
454
  * Read the file behind `uri` through its disk queue and take it as the
705
455
  * baseline. A file that cannot be read counts as none, the side that
706
456
  * leaves the document dirty.
707
457
  */
708
458
  reloadDiskBaseline(uri: DocumentUri): Promise<void>;
709
- /** Compare the held text of `uri` with its baseline, and announce a changed answer. */
710
- protected refreshDirty(uri: CanonicalUri): void;
711
459
  /**
712
460
  * Start a client session under `clientId`. Throws where
713
461
  * {@link ClientSessionRegistry.register} refuses the id.
@@ -722,7 +470,7 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
722
470
  closeSession(clientId: string, cause?: SessionEndCause): void;
723
471
  /**
724
472
  * Close every document the language client has open, as a `didClose` for
725
- * each would, so each last close reverts. For a host whose editor connection
473
+ * each would, so each last close releases its document. For a host whose editor connection
726
474
  * can end while the process lives on, such as a worker whose port's peer
727
475
  * closed: the language client is no session, so nothing else closes them.
728
476
  *
@@ -740,13 +488,6 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
740
488
  * The workspace manager logs a failed build.
741
489
  */
742
490
  protected initialBuildFinished(): Promise<void>;
743
- /**
744
- * Stop tracking every URI the language client holds the document `key`
745
- * under, with its shadow and pending pushes. Call before a close by
746
- * canonical key that means all of them: a close for one URI while others
747
- * remain drops only that URI's and keeps the client's hold.
748
- */
749
- protected untrackLanguageClientDocuments(key: CanonicalUri): void;
750
491
  /**
751
492
  * The file behind `uri` was deleted: close every open of it except the
752
493
  * language client's.
@@ -786,10 +527,9 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
786
527
  * Only a FIRST open consumes it, so stage only for a URI no client holds
787
528
  * ({@link isOpenInAnyClient} is `false`). A URI held only through another
788
529
  * head is not closed: an editor attaching to it joins the existing entry and
789
- * never reads the stage, and the last close discards the stage with the
790
- * tracking record. An entry lingers only if `workspace/applyEdit` fails and
791
- * the file is never opened — the memory cost is one serialised string per
792
- * URI.
530
+ * never reads the stage, and the release discards it. An entry lingers
531
+ * only if `workspace/applyEdit` fails and the file is never opened — the
532
+ * memory cost is one serialised string per URI.
793
533
  */
794
534
  stagePendingContent(uri: DocumentUri, text: string): void;
795
535
  /**
@@ -809,20 +549,19 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
809
549
  *
810
550
  * The diff path is apply-verify-safe: the shadow internally checks that
811
551
  * `TextDocument.applyEdits(old, edits) === newText` and falls back to a
812
- * full replace on mismatch (logged via the warn-callback wired in the
813
- * constructor), so a diff regression becomes log noise, not data loss.
552
+ * full replace on mismatch, logged, so a diff regression becomes log noise,
553
+ * not data loss.
814
554
  *
815
555
  * That safety net verifies the diff against the SHADOW, which is what the
816
556
  * client is *believed* to hold — so it cannot see the client's buffer moving
817
557
  * underneath a push. A line-keyed edit is position-dependent: if a genuine
818
- * client keystroke lands between {@link LanguageClientTextShadow.computeEdits}
819
- * and the client applying, the ranges address the wrong lines and splice the
820
- * buffer (observed as a duplicated declaration, which the integrity tier then
558
+ * client keystroke lands between computing the edits and the client
559
+ * applying them, the ranges address the wrong lines and splice the buffer
560
+ * (observed as a duplicated declaration, which the integrity tier then
821
561
  * "repairs" into a suffixed name and persists). The edit is therefore
822
- * addressed at the language client's last known version for that URI (see
823
- * {@link languageClientVersion}) rather than at `null` ("version
824
- * intentionally unknown"), which is what lets the client
825
- * reject a push its buffer has outrun. On rejection the shadow is invalidated,
562
+ * addressed at the language client's last known version for that URI rather
563
+ * than at `null` ("version intentionally unknown"), which is what lets the
564
+ * client reject a push its buffer has outrun. On rejection the shadow is invalidated,
826
565
  * so the caller's retry is a full-range replace — position-independent, and
827
566
  * safe to apply to whatever the client now holds.
828
567
  */
@@ -830,71 +569,7 @@ export declare class HydraniumTextDocuments<T extends TextDocument = TextDocumen
830
569
  label?: string;
831
570
  }): Promise<ApplyWorkspaceEditResult | undefined>;
832
571
  /**
833
- * The version the LSP textual language client holds `uri` at under
834
- * `targetUri`, for addressing an outgoing `workspace/applyEdit`.
835
- *
836
- * Client version ids are CLIENT-owned per LSP, so this is the id the client
837
- * itself stamped on its last `didOpen` / `didChange` for `targetUri`, or the
838
- * one an applied push moved it to ahead of the push's echo
839
- * ({@link LanguageClientDocumentState}) — never the shared server version,
840
- * which advances on authored writes the client knows nothing about and would
841
- * therefore reject every push.
842
- *
843
- * Falls back to {@link UNKNOWN_CLIENT_VERSION} when the client has not opened
844
- * the document under `targetUri`, which is the honest answer. That is also
845
- * the case in which there is no shadow, so the push is already a
846
- * position-independent full replace and has nothing to gain from a gate.
847
- */
848
- protected languageClientVersion(uri: DocumentUri, targetUri?: LanguageClientUri): number;
849
- /**
850
- * Append a push to the in-flight queue for `targetUri`
851
- * (see {@link __pendingPushes}). Bounded: beyond
852
- * {@link PENDING_ECHO_CAP} the oldest entry drops with a debug log — an
853
- * echo that far outstanding means the client is not echoing at all, and
854
- * an unbounded queue must not become the leak.
855
- */
856
- protected recordPendingPush(targetUri: LanguageClientUri, before: string | undefined, newText: string): void;
857
- /**
858
- * Decide what an incoming language-client change actually is, by
859
- * reconstructing the client's resulting buffer against the text its ranges
860
- * address — the pre-push buffer of the OLDEST push still in flight for
861
- * `clientFacing`, else whatever that client is believed to hold.
862
- *
863
- * `undefined` when the client's ranges address the synced text itself —
864
- * nothing in flight, and no evidence the client holds anything else. That
865
- * is the ordinary path, and the caller then applies the ranges directly.
866
- * The two texts part company without a push in flight whenever a client
867
- * attaches to a document another client has already written: it opened
868
- * from disk, so applying its ranges to the synced text splices lines they
869
- * never addressed.
870
- *
871
- * **Why the oldest, and why reconstruct at all.** The client applies our
872
- * pushes in order and echoes each against the buffer it held before that
873
- * push, so the first echo to arrive belongs to the oldest entry — a FIFO
874
- * correspondence the queue preserves by consuming from the front. Comparing
875
- * the reconstruction against every pending hash, not only the oldest, is
876
- * what recognises an echo that a newer write already superseded: a rapid
877
- * write sequence can deliver the echo of push N after the store applied
878
- * push N+1, and everything older is then accounted for too.
879
- *
880
- * A reconstruction matching NO pending push means the client's buffer went
881
- * somewhere we did not send it — it coalesced a keystroke into the echo, or
882
- * typed before the push landed. That text is authoritative, and the queue
883
- * drops: the client has stopped being a pure mirror, so no outstanding echo
884
- * can match again. (A push still in flight at that point will be refused by
885
- * the client's own version gate, which invalidates the shadow and makes the
886
- * next sync a position-independent full replace.)
887
- *
888
- * Content equality is a sound echo proof because entries live only between a
889
- * push and its echo — a milliseconds window, never history — and the
890
- * client's `didChange` stream is ordered, so every buffer state arrives in
891
- * mutation order. An UNDO returning the buffer to previously-pushed text is
892
- * therefore never swallowed: the edit that preceded it already emptied the
893
- * queue, so undo revisits PAST states while the queue holds IN-FLIGHT ones.
894
- */
895
- protected classifyLanguageClientChange(clientFacing: LanguageClientUri, document: T, changes: TextDocumentContentChangeEvent[]): LanguageClientChangeOrigin | undefined;
896
- /**
897
- * Explicitly baseline the language-client text shadow for a URI. Useful in
572
+ * Explicitly baseline the language-client shadow for a URI. Useful in
898
573
  * tests and for adopters that need to seed the shadow without going through
899
574
  * a `didOpen` event (e.g. after a sideband save). Normal didOpen / didChange
900
575
  * paths from the LSP language client already auto-track the shadow.