@hydranium/core 1.0.0-next.206 → 1.0.0-next.209
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.
- package/lib/documents/document-release-handler.d.ts +6 -1
- package/lib/documents/document-release-handler.d.ts.map +1 -1
- package/lib/documents/document-release-handler.js +13 -0
- package/lib/documents/document-release-handler.js.map +1 -1
- package/lib/documents/hydranium-text-documents.d.ts +23 -11
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +28 -12
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/documents/language-client-shadow.d.ts +19 -13
- package/lib/documents/language-client-shadow.d.ts.map +1 -1
- package/lib/documents/language-client-shadow.js +16 -13
- package/lib/documents/language-client-shadow.js.map +1 -1
- package/lib/langium/integrity/integrity-rule.d.ts +6 -6
- package/lib/langium/integrity/integrity-service.d.ts +2 -1
- package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-service.js +3 -2
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/model-service/model-service.d.ts +6 -0
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +3 -0
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.d.ts +9 -4
- package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.js +9 -0
- package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
- package/package.json +5 -5
- package/src/documents/document-release-handler.ts +15 -1
- package/src/documents/hydranium-text-documents.ts +30 -12
- package/src/documents/language-client-shadow.ts +33 -25
- package/src/langium/integrity/integrity-rule.ts +6 -6
- package/src/langium/integrity/integrity-service.ts +3 -2
- package/src/langium/model-service/model-service.ts +9 -0
- package/src/testing/stub-hydranium-text-documents.ts +18 -3
|
@@ -67,7 +67,7 @@ export function isDocumentReleaseSkippedError(error: unknown): error is Document
|
|
|
67
67
|
*
|
|
68
68
|
* The data server announces the first build of a released document as the
|
|
69
69
|
* release, so a handler that builds nothing leaves that to whichever build
|
|
70
|
-
* comes next
|
|
70
|
+
* comes next; one that carries a client's write keeps that client's name.
|
|
71
71
|
*
|
|
72
72
|
* Extend {@link DefaultDocumentReleaseHandler} to change only the policy: it
|
|
73
73
|
* keeps the locking and the wait for the build. An implementation written
|
|
@@ -142,6 +142,7 @@ export class DefaultDocumentReleaseHandler implements DocumentReleaseHandler {
|
|
|
142
142
|
async didReleaseDocument(released: ReleasedDocument): Promise<void> {
|
|
143
143
|
const target = UriUtils.toUri(released.uri);
|
|
144
144
|
const outcome = await this.runRelease(released, target);
|
|
145
|
+
this.tracer.with(released.uri).debug(`Release outcome: ${this.formatReleaseOutcome(outcome)}`);
|
|
145
146
|
if (outcome.kind === 'skipped') {
|
|
146
147
|
if (!outcome.parsed) {
|
|
147
148
|
throw new DocumentReleaseSkippedError(outcome.error);
|
|
@@ -243,6 +244,19 @@ export class DefaultDocumentReleaseHandler implements DocumentReleaseHandler {
|
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
|
|
247
|
+
/**
|
|
248
|
+
* `outcome` for the line each release logs at debug: its kind, the
|
|
249
|
+
* decision and whether the document was parsed.
|
|
250
|
+
*/
|
|
251
|
+
protected formatReleaseOutcome(outcome: DocumentReleaseOutcome): string {
|
|
252
|
+
if (outcome.kind === 'reclaimed') {
|
|
253
|
+
return 'reclaimed by a client, built nothing';
|
|
254
|
+
}
|
|
255
|
+
const decision = 'decision' in outcome && outcome.decision !== undefined ? ` ${outcome.decision}` : '';
|
|
256
|
+
const parsed = 'parsed' in outcome ? (outcome.parsed ? ', parsed' : ', not parsed') : '';
|
|
257
|
+
return `${outcome.kind}${decision}${parsed}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
246
260
|
/**
|
|
247
261
|
* The policy: rebuild a document the file system provider can serve, and
|
|
248
262
|
* remove any other. Read in the document's disk queue, so it follows any
|
|
@@ -203,6 +203,9 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
203
203
|
*/
|
|
204
204
|
protected readonly __sessions = new ClientSessionRegistry();
|
|
205
205
|
|
|
206
|
+
/** The version each open document was opened at; see {@link openedVersion}. */
|
|
207
|
+
protected readonly __openedVersions = new Map<CanonicalUri, TextVersion>();
|
|
208
|
+
|
|
206
209
|
protected readonly tracer: Tracer;
|
|
207
210
|
protected readonly configuration: TextDocumentsConfiguration<T>;
|
|
208
211
|
protected __textLedger: TextLedger | undefined;
|
|
@@ -598,6 +601,7 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
598
601
|
this.textLedger.clearAuthors(uri);
|
|
599
602
|
const cleanAnnouncement = this.dirtyStateTracker.release(uri);
|
|
600
603
|
this.__syncedDocuments.delete(uri);
|
|
604
|
+
this.__openedVersions.delete(uri);
|
|
601
605
|
this.__sessions.forgetClientVersions(uri);
|
|
602
606
|
// A stage is for a first open; one released unconsumed is stale.
|
|
603
607
|
this.__pendingContent.delete(uri);
|
|
@@ -787,6 +791,7 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
787
791
|
this.log(uri, `Open document: Version ${version} by ${this.formatClientId(clientId)} [first client${source}]`);
|
|
788
792
|
document = this.create(uri, td.languageId, version, text);
|
|
789
793
|
this.commitText(uri, document, clientId);
|
|
794
|
+
this.__openedVersions.set(uri, version);
|
|
790
795
|
// The opener's text, not the staged content: a session's open read
|
|
791
796
|
// it from the file, and an editor opened its buffer from there. An
|
|
792
797
|
// editor that opens a buffer it never saved is taken as clean.
|
|
@@ -922,6 +927,14 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
922
927
|
return this.get(uri)?.version ?? this.textLedger.recordOf(this.documentKey(uri))?.version ?? 0;
|
|
923
928
|
}
|
|
924
929
|
|
|
930
|
+
/**
|
|
931
|
+
* The version the document at `uri` was opened at, which a client's write
|
|
932
|
+
* or an integrity repair steps past; `undefined` while it is not open.
|
|
933
|
+
*/
|
|
934
|
+
openedVersion(uri: DocumentUri): TextVersion | undefined {
|
|
935
|
+
return this.__openedVersions.get(this.documentKey(uri));
|
|
936
|
+
}
|
|
937
|
+
|
|
925
938
|
/**
|
|
926
939
|
* The text the store holds for `uri`: an open document's, or for a closed
|
|
927
940
|
* one, the clean text its version sequence left off at, which for a deleted
|
|
@@ -1319,17 +1332,16 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
1319
1332
|
/**
|
|
1320
1333
|
* Stages integrity-updated content for a document no client holds.
|
|
1321
1334
|
*
|
|
1322
|
-
*
|
|
1323
|
-
* disk
|
|
1324
|
-
* content
|
|
1325
|
-
*
|
|
1335
|
+
* Nothing is pushed to a file no client holds. The next open of it reads
|
|
1336
|
+
* disk, which lacks the update; {@link notifyDidOpenTextDocument} takes this
|
|
1337
|
+
* staged content in place of that text, and the open's sync then delivers
|
|
1338
|
+
* it to an editor as an unsaved change.
|
|
1326
1339
|
*
|
|
1327
1340
|
* Only a FIRST open consumes it, so stage only for a URI no client holds
|
|
1328
1341
|
* ({@link isOpenInAnyClient} is `false`). A URI held only through another
|
|
1329
1342
|
* head is not closed: an editor attaching to it joins the existing entry and
|
|
1330
|
-
* never reads the stage, and the release discards it. An entry
|
|
1331
|
-
*
|
|
1332
|
-
* memory cost is one serialised string per URI.
|
|
1343
|
+
* never reads the stage, and the release discards it. An entry waits for
|
|
1344
|
+
* that first open, at one serialised string per URI.
|
|
1333
1345
|
*/
|
|
1334
1346
|
stagePendingContent(uri: DocumentUri, text: string): void {
|
|
1335
1347
|
this.__pendingContent.set(this.documentKey(uri), text);
|
|
@@ -1344,6 +1356,9 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
1344
1356
|
* - The shared services have no LSP {@link Connection} bound (non-LSP
|
|
1345
1357
|
* hosts like CLI / tests).
|
|
1346
1358
|
* - The shadow already matches `newText` (no edits needed; quiet skip).
|
|
1359
|
+
* - The language client has not opened the document. A client applies an
|
|
1360
|
+
* edit to a closed file by opening it, and that open races the edit;
|
|
1361
|
+
* the document's own open delivers its text instead.
|
|
1347
1362
|
*
|
|
1348
1363
|
* On `applyEdit` rejection (`result.applied === false`) or RPC failure the
|
|
1349
1364
|
* shadow is invalidated so the next call sends a full-replace baseline.
|
|
@@ -1381,7 +1396,7 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
1381
1396
|
// under each URI it opened, and each is diffed against its own baseline.
|
|
1382
1397
|
const key = this.documentKey(uri);
|
|
1383
1398
|
let lastResult: ApplyWorkspaceEditResult | undefined;
|
|
1384
|
-
for (const clientUri of this.languageClientShadow.pushTargets(key
|
|
1399
|
+
for (const clientUri of this.languageClientShadow.pushTargets(key)) {
|
|
1385
1400
|
// Prepared per target, after the previous target's reply: prepared up
|
|
1386
1401
|
// front, a later target is diffed against what it held before a change
|
|
1387
1402
|
// that arrived meanwhile.
|
|
@@ -1421,10 +1436,13 @@ export class HydraniumTextDocuments<T extends TextDocument = TextDocument> exten
|
|
|
1421
1436
|
}
|
|
1422
1437
|
|
|
1423
1438
|
/**
|
|
1424
|
-
* Explicitly baseline the language-client shadow for a URI
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
* paths from the LSP language client already auto-track the
|
|
1439
|
+
* Explicitly baseline the language-client shadow for a URI the client has
|
|
1440
|
+
* open, for an adopter that knows what the client holds without a
|
|
1441
|
+
* `didChange` saying so (e.g. after a sideband save). Normal didOpen /
|
|
1442
|
+
* didChange paths from the LSP language client already auto-track the
|
|
1443
|
+
* shadow. A seed for a URI the client has not opened is kept but changes no
|
|
1444
|
+
* push, since nothing is pushed there; its open replaces it with the text it
|
|
1445
|
+
* declares.
|
|
1428
1446
|
*/
|
|
1429
1447
|
setLanguageClientText(uri: DocumentUri, text: string): void {
|
|
1430
1448
|
this.languageClientShadow.setClientText(this.toLanguageClientUri(uri), text);
|
|
@@ -47,10 +47,10 @@ export interface PendingLanguageClientPush {
|
|
|
47
47
|
* and applied to the already-advanced synced text the line they insert
|
|
48
48
|
* lands twice.
|
|
49
49
|
*
|
|
50
|
-
* `undefined` only when that buffer is unknown
|
|
51
|
-
*
|
|
52
|
-
* reconstructed against the synced text, which
|
|
53
|
-
* position-independent (full-text) change.
|
|
50
|
+
* `undefined` only when that buffer is unknown, because a rejection or
|
|
51
|
+
* {@link LanguageClientShadow.invalidateClientText} dropped what was
|
|
52
|
+
* tracked. The echo is then reconstructed against the synced text, which
|
|
53
|
+
* is sound only for a position-independent (full-text) change.
|
|
54
54
|
*/
|
|
55
55
|
readonly before: string | undefined;
|
|
56
56
|
/** {@link textHash} of the text this push moves the client to. */
|
|
@@ -97,7 +97,7 @@ export type LanguageClientChangeVerdict =
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* The store's model of what the LSP language client holds for each URI it
|
|
100
|
-
* opened
|
|
100
|
+
* opened: the text, the version it declared and the version a push moved it
|
|
101
101
|
* to, and the pushes it has not echoed yet. The store speaks the protocol;
|
|
102
102
|
* this answers what an incoming change is and what an outgoing push sends.
|
|
103
103
|
*
|
|
@@ -112,10 +112,10 @@ export type LanguageClientChangeVerdict =
|
|
|
112
112
|
export interface LanguageClientShadow {
|
|
113
113
|
/**
|
|
114
114
|
* The client opened `key` under `clientUri`. `firstOpen`, the store's first
|
|
115
|
-
* open of `key`: its text becomes the diff baseline
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* already open.
|
|
115
|
+
* open of `key`: its text becomes the diff baseline; otherwise an
|
|
116
|
+
* equality-only baseline, since a buffer opened from disk can lag the
|
|
117
|
+
* synced text. Either replaces what was tracked for `clientUri` before the
|
|
118
|
+
* open. A no-op when this URI is already open.
|
|
119
119
|
*/
|
|
120
120
|
addOpen(key: CanonicalUri, clientUri: LanguageClientUri, version: number, text: string, firstOpen: boolean): void;
|
|
121
121
|
/** The version the client last declared for `key` under `clientUri`; `undefined` when it never opened it there. */
|
|
@@ -140,12 +140,18 @@ export interface LanguageClientShadow {
|
|
|
140
140
|
isOpen(key: CanonicalUri, clientUri?: LanguageClientUri): boolean;
|
|
141
141
|
/** Forget every URI the client holds `key` under. */
|
|
142
142
|
removeAllOpens(key: CanonicalUri): void;
|
|
143
|
-
/**
|
|
144
|
-
|
|
143
|
+
/**
|
|
144
|
+
* The URIs a push of `key` goes to: each open of it, none when the client
|
|
145
|
+
* has not opened it. A client applies a push to a closed file by opening
|
|
146
|
+
* it, and an open racing its own push leaves no text to diff against.
|
|
147
|
+
*/
|
|
148
|
+
pushTargets(key: CanonicalUri): LanguageClientUri[];
|
|
145
149
|
/**
|
|
146
150
|
* Diff `text` against what the client holds under `clientUri` and queue the
|
|
147
151
|
* push for echo correlation, which has to precede the RPC because an echo
|
|
148
|
-
* can arrive before its response. `undefined` when there is nothing to send
|
|
152
|
+
* can arrive before its response. `undefined` when there is nothing to send,
|
|
153
|
+
* which includes a `clientUri` the client no longer has open: a close can
|
|
154
|
+
* land while the push to another of its URIs is in flight.
|
|
149
155
|
*/
|
|
150
156
|
preparePush(key: CanonicalUri, clientUri: LanguageClientUri, text: string): PreparedLanguageClientPush | undefined;
|
|
151
157
|
/** Forget what the client holds under `clientUri`; its next push is a full replace. */
|
|
@@ -209,12 +215,13 @@ export class DefaultLanguageClientShadow<T extends TextDocument = TextDocument>
|
|
|
209
215
|
this.opens.set(key, uris);
|
|
210
216
|
}
|
|
211
217
|
uris.set(clientUri, { declaredVersion: version });
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// afterwards, so a tracked text already names what it is about to hold.
|
|
218
|
+
// Dropped: a seed set before the open names no buffer, and a line diff
|
|
219
|
+
// against it splices text the client does not hold.
|
|
220
|
+
this.invalidateClientText(clientUri);
|
|
221
|
+
if (firstOpen) {
|
|
217
222
|
this.baselines.set(clientUri, text);
|
|
223
|
+
} else {
|
|
224
|
+
this.openedTexts.set(clientUri, text);
|
|
218
225
|
}
|
|
219
226
|
}
|
|
220
227
|
|
|
@@ -312,12 +319,16 @@ export class DefaultLanguageClientShadow<T extends TextDocument = TextDocument>
|
|
|
312
319
|
this.opens.delete(key);
|
|
313
320
|
}
|
|
314
321
|
|
|
315
|
-
pushTargets(key: CanonicalUri
|
|
316
|
-
|
|
317
|
-
return uris && uris.size > 0 ? [...uris.keys()] : [fallback];
|
|
322
|
+
pushTargets(key: CanonicalUri): LanguageClientUri[] {
|
|
323
|
+
return [...(this.opens.get(key)?.keys() ?? [])];
|
|
318
324
|
}
|
|
319
325
|
|
|
320
326
|
preparePush(key: CanonicalUri, clientUri: LanguageClientUri, text: string): PreparedLanguageClientPush | undefined {
|
|
327
|
+
// Captured now: a reopen while the push is in flight replaces the state.
|
|
328
|
+
const state = this.opens.get(key)?.get(clientUri);
|
|
329
|
+
if (state === undefined) {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
321
332
|
// Read before computing the edits, which moves the baseline: this is the
|
|
322
333
|
// only text the push's echo can be reconstructed against.
|
|
323
334
|
const before = this.clientText(clientUri);
|
|
@@ -326,12 +337,9 @@ export class DefaultLanguageClientShadow<T extends TextDocument = TextDocument>
|
|
|
326
337
|
return undefined;
|
|
327
338
|
}
|
|
328
339
|
this.recordPendingPush(clientUri, before, text);
|
|
329
|
-
// Captured now: a reopen while the push is in flight replaces the state.
|
|
330
|
-
const state = this.opens.get(key)?.get(clientUri);
|
|
331
340
|
// Gating a full replace turns a stale-by-one version into a refused
|
|
332
341
|
// update for no safety gain, and refuses the retry after a rejection.
|
|
333
|
-
const version =
|
|
334
|
-
isFullReplace(edits) || state === undefined ? null : Math.max(state.declaredVersion, state.pushedVersion ?? state.declaredVersion);
|
|
342
|
+
const version = isFullReplace(edits) ? null : Math.max(state.declaredVersion, state.pushedVersion ?? state.declaredVersion);
|
|
335
343
|
let settled = false;
|
|
336
344
|
return {
|
|
337
345
|
clientUri,
|
|
@@ -344,7 +352,7 @@ export class DefaultLanguageClientShadow<T extends TextDocument = TextDocument>
|
|
|
344
352
|
this.tracer
|
|
345
353
|
.with(key)
|
|
346
354
|
.warn(
|
|
347
|
-
`Language client refused applyEdit addressed at version ${version} (it last declared version ${state
|
|
355
|
+
`Language client refused applyEdit addressed at version ${version} (it last declared version ${state.declaredVersion})`
|
|
348
356
|
);
|
|
349
357
|
}
|
|
350
358
|
this.settle(clientUri, version, state, outcome);
|
|
@@ -59,12 +59,12 @@ export namespace IntegrityPhase {
|
|
|
59
59
|
* correction. Otherwise — the holder has unsaved edits, disk changed behind
|
|
60
60
|
* the server, or disk cannot be read — the correction waits in the text
|
|
61
61
|
* store for the next save.
|
|
62
|
-
* - `editor`:
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* mark only their own edits unsaved, nothing shows that
|
|
67
|
-
* differ until a save or the last close.
|
|
62
|
+
* - `editor`: leave disk alone and stage the correction; the next open of the
|
|
63
|
+
* file takes it over the disk text, and an editor shows it as an unsaved
|
|
64
|
+
* change. Until then no editor sees it. A file held only through the data
|
|
65
|
+
* or GLSP head is not staged: the correction stays in the text store, and
|
|
66
|
+
* since those heads mark only their own edits unsaved, nothing shows that
|
|
67
|
+
* store and disk differ until a save or the last close.
|
|
68
68
|
*/
|
|
69
69
|
export type IntegritySyncMode = 'silent' | 'editor';
|
|
70
70
|
|
|
@@ -469,7 +469,8 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
|
|
|
469
469
|
* otherwise it waits for the next save. Editor mode always waits, and the
|
|
470
470
|
* repair leaves the document dirty until a save.
|
|
471
471
|
* - **Closed, silent mode** → write to disk directly (disk is authoritative).
|
|
472
|
-
* - **Closed, editor mode** → stage so the next open
|
|
472
|
+
* - **Closed, editor mode** → stage, so the next open takes the correction
|
|
473
|
+
* over the disk text.
|
|
473
474
|
*
|
|
474
475
|
* A held URI is not closed. Writing it as one persists the holder's unsaved
|
|
475
476
|
* edits with the repair, and staging it leaves text no open reads, because
|
|
@@ -520,7 +521,7 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
|
|
|
520
521
|
return;
|
|
521
522
|
}
|
|
522
523
|
|
|
523
|
-
// Closed files, editor mode: stage content
|
|
524
|
+
// Closed files, editor mode: stage content for the next open.
|
|
524
525
|
this.textDocuments.stagePendingContent(document.uri, document.getText());
|
|
525
526
|
}
|
|
526
527
|
|
|
@@ -262,6 +262,11 @@ export interface ModelService<
|
|
|
262
262
|
validated(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
263
263
|
|
|
264
264
|
isOpen(uri: string): boolean;
|
|
265
|
+
/**
|
|
266
|
+
* The version the document at `uri` was opened at, which a client's write
|
|
267
|
+
* or an integrity repair steps past; `undefined` while it is not open.
|
|
268
|
+
*/
|
|
269
|
+
openedVersion(uri: string): TextVersion | undefined;
|
|
265
270
|
snapshot(uri: string): AstDocument<TAst, TDiagnostic> | undefined;
|
|
266
271
|
/**
|
|
267
272
|
* The text a session's write of `model` to `uri` applies: a textual model
|
|
@@ -839,6 +844,10 @@ export class DefaultModelService<
|
|
|
839
844
|
return this.services.workspace.AstDocumentManager.isOpen(uri);
|
|
840
845
|
}
|
|
841
846
|
|
|
847
|
+
openedVersion(uri: string): TextVersion | undefined {
|
|
848
|
+
return this.services.workspace.TextDocuments.openedVersion(uri);
|
|
849
|
+
}
|
|
850
|
+
|
|
842
851
|
/**
|
|
843
852
|
* Snapshot of `uri` as it stands RIGHT NOW — the synchronous sibling of the
|
|
844
853
|
* phase reads, which all wait. `undefined` when no document is registered,
|
|
@@ -89,6 +89,7 @@ export interface StubHydraniumTextDocuments extends Pick<
|
|
|
89
89
|
| 'onDidReleaseDocument'
|
|
90
90
|
| 'isDirty'
|
|
91
91
|
| 'textState'
|
|
92
|
+
| 'openedVersion'
|
|
92
93
|
| 'onDidChangeDirty'
|
|
93
94
|
| 'setDiskBaseline'
|
|
94
95
|
> {
|
|
@@ -135,15 +136,20 @@ export interface StubHydraniumTextDocuments extends Pick<
|
|
|
135
136
|
/**
|
|
136
137
|
* Decide what {@link applyEditToLanguageClient} answers, so a suite can drive
|
|
137
138
|
* the rejection path (`{ applied: false }`) the real client takes when its
|
|
138
|
-
* buffer has outrun the version the push was addressed at. The
|
|
139
|
-
* recorded in {@link appliedEdits} either way. Defaults to accepting
|
|
139
|
+
* buffer has outrun the version the push was addressed at. The push is still
|
|
140
|
+
* recorded in {@link appliedEdits} either way. Defaults to accepting; not
|
|
141
|
+
* called for a URI not open in the language client.
|
|
140
142
|
*
|
|
141
143
|
* The handler runs INSIDE the call, which is also the only place a suite can
|
|
142
144
|
* simulate something happening while the push is in flight — a newer settle,
|
|
143
145
|
* a concurrent client edit.
|
|
144
146
|
*/
|
|
145
147
|
setApplyEditHandler(handler: (uri: string, text: string) => ApplyWorkspaceEditResult | undefined): void;
|
|
146
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Replay of every push {@link applyEditToLanguageClient} sent (the text
|
|
150
|
+
* channel to Monaco): none for a URI not seeded open in the language client,
|
|
151
|
+
* as the real store sends none.
|
|
152
|
+
*/
|
|
147
153
|
readonly appliedEdits: readonly { uri: string; text: string; label?: string }[];
|
|
148
154
|
/** Replay of every {@link stagePendingContent} call (the not-yet-open staging path). */
|
|
149
155
|
readonly staged: readonly { uri: string; text: string }[];
|
|
@@ -167,6 +173,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
167
173
|
const key = (uri: string): CanonicalUri => uri as CanonicalUri;
|
|
168
174
|
const documentReleased = new Emitter<DocumentReleasedEvent>();
|
|
169
175
|
const baselines = new Map<string, string | undefined>();
|
|
176
|
+
const openedVersions = new Map<string, number>();
|
|
170
177
|
const dirty = new Set<string>();
|
|
171
178
|
const dirtyChanged = new Emitter<DocumentDirtyChangedEvent>();
|
|
172
179
|
// As the real store: dirty while held with text other than the baseline,
|
|
@@ -209,6 +216,9 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
209
216
|
version(uri) {
|
|
210
217
|
return docs.get(uri)?.version ?? 0;
|
|
211
218
|
},
|
|
219
|
+
openedVersion(uri) {
|
|
220
|
+
return docs.has(uri) ? openedVersions.get(uri) : undefined;
|
|
221
|
+
},
|
|
212
222
|
textState(uri) {
|
|
213
223
|
const held = docs.get(uri);
|
|
214
224
|
return held && { version: held.version, hash: textHash(held.text), dirty: dirty.has(uri) };
|
|
@@ -331,6 +341,9 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
331
341
|
applyEditHandler = handler;
|
|
332
342
|
},
|
|
333
343
|
async applyEditToLanguageClient(uri, newText, options): Promise<ApplyWorkspaceEditResult | undefined> {
|
|
344
|
+
if (!languageClientOpen.has(uri)) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
334
347
|
appliedEdits.push({ uri, text: newText, label: options?.label });
|
|
335
348
|
return applyEditHandler(uri, newText);
|
|
336
349
|
},
|
|
@@ -340,6 +353,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
340
353
|
seedOpen(uri, text, clientId) {
|
|
341
354
|
sessions.addOpen(key(uri), clientId);
|
|
342
355
|
docs.set(uri, { uri, version: 1, text, clientId });
|
|
356
|
+
openedVersions.set(uri, 1);
|
|
343
357
|
baselines.set(uri, text);
|
|
344
358
|
},
|
|
345
359
|
seedOpenInLanguageClient(uri) {
|
|
@@ -379,6 +393,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
379
393
|
appliedEdits.length = 0;
|
|
380
394
|
staged.length = 0;
|
|
381
395
|
baselines.clear();
|
|
396
|
+
openedVersions.clear();
|
|
382
397
|
dirty.clear();
|
|
383
398
|
saveListeners.length = 0;
|
|
384
399
|
closeListeners.length = 0;
|