@hydranium/core 1.0.0-next.205 → 1.0.0-next.208
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/ast-document-manager.js +4 -4
- package/lib/documents/ast-document-manager.js.map +1 -1
- package/lib/documents/client-ids.d.ts +1 -1
- package/lib/documents/client-ids.d.ts.map +1 -1
- package/lib/documents/client-ids.js +1 -1
- package/lib/documents/client-ids.js.map +1 -1
- package/lib/documents/client-session-registry.d.ts +24 -17
- package/lib/documents/client-session-registry.d.ts.map +1 -1
- package/lib/documents/client-session-registry.js +49 -29
- package/lib/documents/client-session-registry.js.map +1 -1
- package/lib/documents/dirty-state-tracker.d.ts +79 -0
- package/lib/documents/dirty-state-tracker.d.ts.map +1 -0
- package/lib/documents/dirty-state-tracker.js +69 -0
- package/lib/documents/dirty-state-tracker.js.map +1 -0
- package/lib/documents/document-release-handler.d.ts +179 -0
- package/lib/documents/document-release-handler.d.ts.map +1 -0
- package/lib/documents/document-release-handler.js +283 -0
- package/lib/documents/document-release-handler.js.map +1 -0
- package/lib/documents/document-release-scheduler.d.ts +67 -0
- package/lib/documents/document-release-scheduler.d.ts.map +1 -0
- package/lib/documents/document-release-scheduler.js +80 -0
- package/lib/documents/document-release-scheduler.js.map +1 -0
- package/lib/documents/hydranium-text-documents.d.ts +137 -455
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +373 -835
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/documents/index.d.ts +5 -1
- package/lib/documents/index.d.ts.map +1 -1
- package/lib/documents/index.js +5 -1
- package/lib/documents/index.js.map +1 -1
- package/lib/documents/language-client-shadow.d.ts +221 -0
- package/lib/documents/language-client-shadow.d.ts.map +1 -0
- package/lib/documents/language-client-shadow.js +288 -0
- package/lib/documents/language-client-shadow.js.map +1 -0
- package/lib/documents/text-ledger.d.ts +64 -0
- package/lib/documents/text-ledger.d.ts.map +1 -0
- package/lib/documents/text-ledger.js +63 -0
- package/lib/documents/text-ledger.js.map +1 -0
- package/lib/langium/document-builder/document-builder.d.ts +8 -7
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +8 -7
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/integrity/integrity-service.d.ts +1 -1
- package/lib/langium/integrity/integrity-service.js +4 -4
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/model-service/client-session.d.ts +4 -4
- package/lib/langium/model-service/client-session.js +2 -2
- package/lib/langium/model-service/client-session.js.map +1 -1
- package/lib/langium/model-service/model-events.d.ts +8 -11
- package/lib/langium/model-service/model-events.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.d.ts +18 -4
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +4 -1
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/module.d.ts +7 -0
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js +3 -1
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/workspace/file-not-found.d.ts +7 -1
- package/lib/langium/workspace/file-not-found.d.ts.map +1 -1
- package/lib/langium/workspace/file-not-found.js +14 -3
- package/lib/langium/workspace/file-not-found.js.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.d.ts +6 -6
- package/lib/lsp/hydranium-document-update-handler.js +10 -10
- package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
- package/lib/testing/make-test-services.d.ts +3 -0
- package/lib/testing/make-test-services.d.ts.map +1 -1
- package/lib/testing/make-test-services.js +5 -2
- package/lib/testing/make-test-services.js.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.d.ts +9 -6
- package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.js +25 -11
- package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
- package/package.json +5 -5
- package/src/documents/ast-document-manager.ts +4 -4
- package/src/documents/client-ids.ts +1 -1
- package/src/documents/client-session-registry.ts +55 -34
- package/src/documents/dirty-state-tracker.ts +130 -0
- package/src/documents/document-release-handler.ts +365 -0
- package/src/documents/document-release-scheduler.ts +121 -0
- package/src/documents/hydranium-text-documents.ts +407 -1033
- package/src/documents/index.ts +5 -1
- package/src/documents/language-client-shadow.ts +463 -0
- package/src/documents/text-ledger.ts +112 -0
- package/src/langium/document-builder/document-builder.ts +8 -7
- package/src/langium/integrity/integrity-service.ts +4 -4
- package/src/langium/model-service/client-session.ts +6 -6
- package/src/langium/model-service/model-events.ts +12 -11
- package/src/langium/model-service/model-service.ts +22 -4
- package/src/langium/module.ts +9 -1
- package/src/langium/workspace/file-not-found.ts +15 -3
- package/src/lsp/hydranium-document-update-handler.ts +10 -10
- package/src/testing/make-test-services.ts +7 -2
- package/src/testing/stub-hydranium-text-documents.ts +41 -21
- package/lib/documents/language-client-text-shadow.d.ts +0 -133
- package/lib/documents/language-client-text-shadow.d.ts.map +0 -1
- package/lib/documents/language-client-text-shadow.js +0 -201
- package/lib/documents/language-client-text-shadow.js.map +0 -1
- package/src/documents/language-client-text-shadow.ts +0 -223
|
@@ -80,7 +80,7 @@ export interface ClientSession<
|
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Open `uri` for this session, reading it from disk unless some client has
|
|
83
|
-
* it open already or it holds this client's unsaved text within its
|
|
83
|
+
* it open already or it holds this client's unsaved text within its release
|
|
84
84
|
* grace. `options` are kept for this open until it closes; a repeat open,
|
|
85
85
|
* concurrent or not, keeps the options of the first.
|
|
86
86
|
*/
|
|
@@ -90,7 +90,7 @@ export interface ClientSession<
|
|
|
90
90
|
/**
|
|
91
91
|
* Create a document with `text` and open it for this session. It reaches
|
|
92
92
|
* disk on the first `save`. Fails when the file exists, any client has the
|
|
93
|
-
* URI open, or the URI waits out the
|
|
93
|
+
* URI open, or the URI waits out the release grace, and of two creates of one
|
|
94
94
|
* URI at most one succeeds.
|
|
95
95
|
*
|
|
96
96
|
* Resolves with the version the created document took, which a write based
|
|
@@ -137,8 +137,8 @@ export interface ClientSession<
|
|
|
137
137
|
* End the session: close everything it has open and free its id.
|
|
138
138
|
* Idempotent. `cause` is `'lost'` when the session ends because its
|
|
139
139
|
* client's connection went away, which lets each document it was the last
|
|
140
|
-
* to have open wait out the store's
|
|
141
|
-
* reason
|
|
140
|
+
* to have open wait out the store's release grace; ending it for any other
|
|
141
|
+
* reason releases such a document at once.
|
|
142
142
|
*/
|
|
143
143
|
dispose(cause?: SessionEndCause): void;
|
|
144
144
|
/**
|
|
@@ -497,8 +497,8 @@ export class DefaultClientSession<
|
|
|
497
497
|
throw new Error(`Cannot create ${uri}: the file exists`);
|
|
498
498
|
}
|
|
499
499
|
this.assertLive();
|
|
500
|
-
if (this.services.workspace.TextDocuments.
|
|
501
|
-
throw new Error(`Cannot create ${uri}: it holds the unsaved text of a lost client, waiting out the
|
|
500
|
+
if (this.services.workspace.TextDocuments.isReleaseDeferred(uri)) {
|
|
501
|
+
throw new Error(`Cannot create ${uri}: it holds the unsaved text of a lost client, waiting out the release grace`);
|
|
502
502
|
}
|
|
503
503
|
if (this.modelService.isOpen(uri)) {
|
|
504
504
|
throw new Error(`Cannot create ${uri}: it is open in a client`);
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { type CanonicalUri } from '@hydranium/protocol';
|
|
11
11
|
import { type AstNode, type DocumentState } from '@hydranium/langium';
|
|
12
|
-
import { type AstDocumentUpdatedEvent } from '../../documents/ast-document-manager.js';
|
|
13
|
-
import { type DocumentDirtyChangedEvent } from '../../documents/
|
|
12
|
+
import { type AstDocumentSavedEvent, type AstDocumentUpdatedEvent } from '../../documents/ast-document-manager.js';
|
|
13
|
+
import { type DocumentDirtyChangedEvent } from '../../documents/dirty-state-tracker.js';
|
|
14
|
+
import { type DocumentReleasedEvent } from '../../documents/hydranium-text-documents.js';
|
|
14
15
|
import { type AstDiagnostic } from '../validation/document-validator.js';
|
|
15
16
|
|
|
16
17
|
/** Which documents a `ModelService` subscription hears about. */
|
|
@@ -61,14 +62,14 @@ export interface ModelDeletedEvent {
|
|
|
61
62
|
readonly uri: CanonicalUri;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
/** A document's
|
|
65
|
+
/** A document was saved: the AST manager's event, as `ModelService` hands it out. */
|
|
66
|
+
export type ModelSavedEvent<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> = AstDocumentSavedEvent<
|
|
67
|
+
TAst,
|
|
68
|
+
TDiagnostic
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
/** A document's text started or stopped differing from its file: the store's event, as `ModelService` hands it out. */
|
|
65
72
|
export type ModelDirtyChangedEvent = DocumentDirtyChangedEvent;
|
|
66
73
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
* The rebuild that reverts it follows as a {@link ModelUpdatedEvent}, unless a
|
|
70
|
-
* client opens the document first or its file is deleted.
|
|
71
|
-
*/
|
|
72
|
-
export interface ModelReleasedEvent {
|
|
73
|
-
readonly uri: CanonicalUri;
|
|
74
|
-
}
|
|
74
|
+
/** A document no client has open any more was released: the store's event, as `ModelService` hands it out. */
|
|
75
|
+
export type ModelReleasedEvent = DocumentReleasedEvent;
|
|
@@ -25,7 +25,7 @@ import { type AstDiagnostic } from '../validation/document-validator.js';
|
|
|
25
25
|
import { type DocumentUriPolicy } from '../workspace/document-uri-policy.js';
|
|
26
26
|
import { ReentrantWriteLockError, isInsideWriteLock, isWriteLockScopeInstalled } from '../workspace/write-lock-scope.js';
|
|
27
27
|
import { CancellationToken, Disposable } from 'vscode-languageserver';
|
|
28
|
-
import { AstDocument
|
|
28
|
+
import { AstDocument } from '../../documents/ast-document-manager.js';
|
|
29
29
|
import { isConnectionGoneError } from '../../util/connection-liveness.js';
|
|
30
30
|
import { type LogNameOptions } from '../diagnostics/logger.js';
|
|
31
31
|
import { IntegrityService } from '../integrity/integrity-service.js';
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type ModelEventFilter,
|
|
41
41
|
type ModelPhaseFilter,
|
|
42
42
|
type ModelReleasedEvent,
|
|
43
|
+
type ModelSavedEvent,
|
|
43
44
|
type ModelsBuiltEvent,
|
|
44
45
|
type ModelUpdatedEvent
|
|
45
46
|
} from './model-events.js';
|
|
@@ -261,6 +262,11 @@ export interface ModelService<
|
|
|
261
262
|
validated(uri: string, cancelToken?: CancellationToken): Promise<AstDocument<TAst, TDiagnostic>>;
|
|
262
263
|
|
|
263
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;
|
|
264
270
|
snapshot(uri: string): AstDocument<TAst, TDiagnostic> | undefined;
|
|
265
271
|
/**
|
|
266
272
|
* The text a session's write of `model` to `uri` applies: a textual model
|
|
@@ -285,9 +291,17 @@ export interface ModelService<
|
|
|
285
291
|
* it. A document saved before its first build arrives as an empty envelope
|
|
286
292
|
* at `UNRECORDED_VERSION`, whose `root` is `undefined` despite its type.
|
|
287
293
|
*/
|
|
288
|
-
onModelSaved(listener: (event:
|
|
294
|
+
onModelSaved(listener: (event: ModelSavedEvent<TAst, TDiagnostic>) => void, filter?: ModelEventFilter): Disposable;
|
|
289
295
|
onModelDeleted(listener: (event: ModelDeletedEvent) => void, filter?: ModelEventFilter): Disposable;
|
|
296
|
+
/** Fires when a document's text starts or stops differing from its file. */
|
|
290
297
|
onDirtyChanged(listener: (event: ModelDirtyChangedEvent) => void, filter?: ModelEventFilter): Disposable;
|
|
298
|
+
/**
|
|
299
|
+
* Fires when the store releases a document no client has open any more.
|
|
300
|
+
* What the build keeps for it is up to the
|
|
301
|
+
* `DocumentReleaseHandler` slot; the default reverts it to disk, and that
|
|
302
|
+
* rebuild follows as an {@link onModelUpdated} event, unless a client opens
|
|
303
|
+
* the document first or its file is deleted.
|
|
304
|
+
*/
|
|
291
305
|
onModelReleased(listener: (event: ModelReleasedEvent) => void, filter?: ModelEventFilter): Disposable;
|
|
292
306
|
/** Fires when `filter.clientId` closes `filter.uri`, its session ending included. */
|
|
293
307
|
onClientClosed(listener: () => void, filter: { readonly uri: string; readonly clientId: string }): Disposable;
|
|
@@ -830,6 +844,10 @@ export class DefaultModelService<
|
|
|
830
844
|
return this.services.workspace.AstDocumentManager.isOpen(uri);
|
|
831
845
|
}
|
|
832
846
|
|
|
847
|
+
openedVersion(uri: string): TextVersion | undefined {
|
|
848
|
+
return this.services.workspace.TextDocuments.openedVersion(uri);
|
|
849
|
+
}
|
|
850
|
+
|
|
833
851
|
/**
|
|
834
852
|
* Snapshot of `uri` as it stands RIGHT NOW — the synchronous sibling of the
|
|
835
853
|
* phase reads, which all wait. `undefined` when no document is registered,
|
|
@@ -1145,7 +1163,7 @@ export class DefaultModelService<
|
|
|
1145
1163
|
});
|
|
1146
1164
|
}
|
|
1147
1165
|
|
|
1148
|
-
onModelSaved(listener: (event:
|
|
1166
|
+
onModelSaved(listener: (event: ModelSavedEvent<TAst, TDiagnostic>) => void, filter: ModelEventFilter = {}): Disposable {
|
|
1149
1167
|
const target = this.filterUri(filter);
|
|
1150
1168
|
return this.services.workspace.TextDocuments.onDidSave(event => {
|
|
1151
1169
|
// LangiumDocuments keys by the canonical form, the saved event by the
|
|
@@ -1180,7 +1198,7 @@ export class DefaultModelService<
|
|
|
1180
1198
|
|
|
1181
1199
|
onModelReleased(listener: (event: ModelReleasedEvent) => void, filter: ModelEventFilter = {}): Disposable {
|
|
1182
1200
|
const target = this.filterUri(filter);
|
|
1183
|
-
return this.services.workspace.TextDocuments.
|
|
1201
|
+
return this.services.workspace.TextDocuments.onDidReleaseDocument(event => {
|
|
1184
1202
|
if (this.matches(target, event.uri)) {
|
|
1185
1203
|
listener(event);
|
|
1186
1204
|
}
|
package/src/langium/module.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { DefaultAstDocumentManager, type AstDocumentManager } from '../documents
|
|
|
38
38
|
import { DefaultSelfSaveRegistry, type SelfSaveRegistry } from './workspace/self-save-registry.js';
|
|
39
39
|
import { DefaultFileSystemTaskQueue, type FileSystemTaskQueue } from '../documents/file-system-task-queue.js';
|
|
40
40
|
import { DefaultModelLedger, type ModelLedger } from '../documents/model-ledger.js';
|
|
41
|
+
import { DefaultDocumentReleaseHandler, type DocumentReleaseHandler } from '../documents/document-release-handler.js';
|
|
41
42
|
import { DefaultVersionSyncService, type VersionSyncService } from '../documents/version-sync-service.js';
|
|
42
43
|
import {
|
|
43
44
|
DefaultEmptyFileSystemProvider,
|
|
@@ -286,6 +287,12 @@ export interface ServerAddedSharedServices<
|
|
|
286
287
|
ModelLedger: ModelLedger;
|
|
287
288
|
/** Reconciles each produced root with the store, and owns every build that syncs a root to its text or recovers one. */
|
|
288
289
|
VersionSyncService: VersionSyncService;
|
|
290
|
+
/**
|
|
291
|
+
* What the build keeps for a document {@link HydraniumTextDocuments} has
|
|
292
|
+
* released, for every head. Bound to {@link DefaultDocumentReleaseHandler},
|
|
293
|
+
* which reverts it to disk.
|
|
294
|
+
*/
|
|
295
|
+
DocumentReleaseHandler: DocumentReleaseHandler;
|
|
289
296
|
};
|
|
290
297
|
/**
|
|
291
298
|
* In-process workspace facade ({@link ModelService}) + AST→transfer
|
|
@@ -562,7 +569,8 @@ export function createServerSharedModule(
|
|
|
562
569
|
// first build.
|
|
563
570
|
CstResidencyService: services => new DefaultCstResidencyService(services),
|
|
564
571
|
ModelLedger: () => new DefaultModelLedger(),
|
|
565
|
-
VersionSyncService: services => new DefaultVersionSyncService(services)
|
|
572
|
+
VersionSyncService: services => new DefaultVersionSyncService(services),
|
|
573
|
+
DocumentReleaseHandler: services => new DefaultDocumentReleaseHandler(services)
|
|
566
574
|
},
|
|
567
575
|
model: {
|
|
568
576
|
// Generic walker — adopters with a typed `$type → wire shape` overlay
|
|
@@ -9,15 +9,27 @@
|
|
|
9
9
|
|
|
10
10
|
import { type URI } from '@hydranium/langium';
|
|
11
11
|
|
|
12
|
-
// Not re-exported from the workspace barrel: the framework's providers
|
|
13
|
-
// it, and it is no API of theirs.
|
|
12
|
+
// Not re-exported from the workspace barrel: the framework's providers and
|
|
13
|
+
// the release handler share it, and it is no API of theirs.
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* A missing-file error shaped as a Node file system raises it, with code
|
|
17
17
|
* `ENOENT` and the path: callers tell a missing file from any other failure by
|
|
18
|
-
* those two fields, whichever provider is bound. The
|
|
18
|
+
* those two fields, whichever provider is bound. The release relies on it to
|
|
19
19
|
* tell a document gone meanwhile from any other failed rebuild.
|
|
20
20
|
*/
|
|
21
21
|
export function notFound(message: string, uri: URI): Error {
|
|
22
22
|
return Object.assign(new Error(message), { code: 'ENOENT', path: uri.fsPath });
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether `err` reports that the file of `target` does not exist, as a Node
|
|
27
|
+
* file system and {@link notFound} do: code `ENOENT`, with `target`'s path. A
|
|
28
|
+
* missing file of another document is a different failure.
|
|
29
|
+
*/
|
|
30
|
+
export function isFileNotFound(err: unknown, target: URI): boolean {
|
|
31
|
+
if (typeof err !== 'object' || err === null || !('code' in err) || err.code !== 'ENOENT') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return !('path' in err) || err.path === target.fsPath;
|
|
35
|
+
}
|
|
@@ -93,12 +93,12 @@ export interface HydraniumDocumentUpdateHandlerOptions {
|
|
|
93
93
|
* Both waits are capped by
|
|
94
94
|
* {@link HydraniumDocumentUpdateHandlerOptions.willSaveGateMs}.
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
* the
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* or removes it.
|
|
96
|
+
* What the build keeps after a document's last close is not dispatched here:
|
|
97
|
+
* the store hands the released document to the `DocumentReleaseHandler` for
|
|
98
|
+
* every head, the LSP head included, so a `didCloseDocument` added in a
|
|
99
|
+
* subclass would build the document twice. A change still debounced for the
|
|
100
|
+
* document is dropped when the store releases it: the release handler rebuilds
|
|
101
|
+
* the document from the file system provider or removes it.
|
|
102
102
|
*
|
|
103
103
|
* Adopters with their own handler subclass should extend this class
|
|
104
104
|
* (not Langium's `DefaultDocumentUpdateHandler`) so all of them are
|
|
@@ -175,10 +175,10 @@ export class HydraniumDocumentUpdateHandler extends DefaultDocumentUpdateHandler
|
|
|
175
175
|
uri => this.pendingChanged.has(uri.toString()) || this.pendingDeleted.has(uri.toString())
|
|
176
176
|
);
|
|
177
177
|
// A change still debounced for a document the store has released would
|
|
178
|
-
// build it once more after the
|
|
179
|
-
// again, or, for a document the
|
|
180
|
-
// is not there.
|
|
181
|
-
this.textDocuments.
|
|
178
|
+
// build it once more after the release handler's build: from the
|
|
179
|
+
// provider again, or, for a document the release removed, by reading a
|
|
180
|
+
// file that is not there.
|
|
181
|
+
this.textDocuments.onDidReleaseDocument(event => {
|
|
182
182
|
this.pendingChanged.delete(URI.parse(event.uri).toString());
|
|
183
183
|
if (this.pendingChanged.size === 0 && this.pendingDeleted.size === 0) {
|
|
184
184
|
this.pendingReason = undefined;
|
|
@@ -36,6 +36,7 @@ import { DefaultDocumentUriPolicy, type DocumentUriPolicy } from '../langium/wor
|
|
|
36
36
|
import { HydraniumWorkspaceLock } from '../langium/workspace/hydranium-workspace-lock.js';
|
|
37
37
|
import { DefaultFileSystemTaskQueue, type FileSystemTaskQueue } from '../documents/file-system-task-queue.js';
|
|
38
38
|
import { DefaultModelLedger, type ModelLedger } from '../documents/model-ledger.js';
|
|
39
|
+
import { DefaultDocumentReleaseHandler, type DocumentReleaseHandler } from '../documents/document-release-handler.js';
|
|
39
40
|
import { DefaultVersionSyncService, type VersionSyncService } from '../documents/version-sync-service.js';
|
|
40
41
|
import type { FakeDocumentOptions } from './fake-document.js';
|
|
41
42
|
import { makeStubDocumentBuilder, type StubDocumentBuilder } from './stub-document-builder.js';
|
|
@@ -115,6 +116,8 @@ export interface TestSharedServices<
|
|
|
115
116
|
ModelLedger: ModelLedger;
|
|
116
117
|
/** The REAL service, over this tree's stubs; it reconciles nothing against the stub store. */
|
|
117
118
|
VersionSyncService: VersionSyncService;
|
|
119
|
+
/** The REAL handler, over this tree's stubs; the stub store releases nothing to it. */
|
|
120
|
+
DocumentReleaseHandler: DocumentReleaseHandler;
|
|
118
121
|
};
|
|
119
122
|
readonly model: {
|
|
120
123
|
TransferEncoder: TransferEncoder<TTransferDiagnostic>;
|
|
@@ -378,8 +381,9 @@ export function makeTestServices<
|
|
|
378
381
|
WorkspaceLock: workspaceLock,
|
|
379
382
|
FileSystemTaskQueue: new DefaultFileSystemTaskQueue({ workspace: { DocumentUriPolicy: documentUriPolicy } }),
|
|
380
383
|
ModelLedger: modelLedger,
|
|
381
|
-
// Patched in below
|
|
382
|
-
VersionSyncService: {} as VersionSyncService
|
|
384
|
+
// Patched in below, as both read the tree they belong to.
|
|
385
|
+
VersionSyncService: {} as VersionSyncService,
|
|
386
|
+
DocumentReleaseHandler: {} as DocumentReleaseHandler
|
|
383
387
|
},
|
|
384
388
|
model: {} as TestSharedServices<TAst, TDiagnostic, TTransfer, TProject, TTransferDiagnostic>['model'],
|
|
385
389
|
ServerLocale: {} as ServerLocale,
|
|
@@ -390,6 +394,7 @@ export function makeTestServices<
|
|
|
390
394
|
// Patched in after the literal, like `model` below: both read the tree they
|
|
391
395
|
// belong to, and the renderer reads the locale service.
|
|
392
396
|
services.workspace.VersionSyncService = new DefaultVersionSyncService(sharedServices);
|
|
397
|
+
services.workspace.DocumentReleaseHandler = new DefaultDocumentReleaseHandler(sharedServices);
|
|
393
398
|
const mutableMessages = services as { ServerLocale: ServerLocale; MessageRenderer: MessageRenderer };
|
|
394
399
|
const serverLocale = new DefaultServerLocale(sharedServices);
|
|
395
400
|
if (options.locale) {
|
|
@@ -13,11 +13,11 @@ import { Disposable, Emitter } from 'vscode-languageserver';
|
|
|
13
13
|
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
14
14
|
import { LANGUAGE_CLIENT_ID } from '../documents/client-ids.js';
|
|
15
15
|
import { ClientSessionRegistry } from '../documents/client-session-registry.js';
|
|
16
|
+
import type { DocumentDirtyChangedEvent } from '../documents/dirty-state-tracker.js';
|
|
16
17
|
import type {
|
|
17
18
|
ClientTextDocumentChangeEvent,
|
|
18
|
-
DocumentDirtyChangedEvent,
|
|
19
19
|
HydraniumTextDocuments,
|
|
20
|
-
|
|
20
|
+
DocumentReleasedEvent
|
|
21
21
|
} from '../documents/hydranium-text-documents.js';
|
|
22
22
|
|
|
23
23
|
/** Snapshot of an open document tracked by the stub. */
|
|
@@ -33,11 +33,11 @@ export interface StubTextDocumentEntry {
|
|
|
33
33
|
* reads from on test paths — the content channel
|
|
34
34
|
* (`notifyDidChangeTextDocument` / `applyContentChange` / `version` /
|
|
35
35
|
* `getAuthor`), the open-state probes (`isOpenInLanguageClient` /
|
|
36
|
-
* `isOpenInAnyClient` / `isOpenInClient` / `
|
|
36
|
+
* `isOpenInAnyClient` / `isOpenInClient` / `isReleaseDeferred` /
|
|
37
37
|
* `openDocuments`), the push channel
|
|
38
38
|
* to the language client (`applyEditToLanguageClient` / `stagePendingContent`),
|
|
39
39
|
* the save / close notifications, the dirty state (`isDirty` / `textState` /
|
|
40
|
-
* `onDidChangeDirty` / `
|
|
40
|
+
* `onDidChangeDirty` / `setDiskBaseline`, against a baseline {@link seedOpen}
|
|
41
41
|
* sets and a save that carries its text moves), and the client-session table
|
|
42
42
|
* (`registerSession` / `closeSession` / `onDidCloseSession`), which is a real
|
|
43
43
|
* `ClientSessionRegistry` — plus test-only helpers:
|
|
@@ -77,7 +77,7 @@ export interface StubHydraniumTextDocuments extends Pick<
|
|
|
77
77
|
| 'getAuthor'
|
|
78
78
|
| 'isOpenInLanguageClient'
|
|
79
79
|
| 'isOpenInAnyClient'
|
|
80
|
-
| '
|
|
80
|
+
| 'isReleaseDeferred'
|
|
81
81
|
| 'applyEditToLanguageClient'
|
|
82
82
|
| 'stagePendingContent'
|
|
83
83
|
| 'openDocuments'
|
|
@@ -86,11 +86,12 @@ export interface StubHydraniumTextDocuments extends Pick<
|
|
|
86
86
|
| 'registerSession'
|
|
87
87
|
| 'closeSession'
|
|
88
88
|
| 'onDidCloseSession'
|
|
89
|
-
| '
|
|
89
|
+
| 'onDidReleaseDocument'
|
|
90
90
|
| 'isDirty'
|
|
91
91
|
| 'textState'
|
|
92
|
+
| 'openedVersion'
|
|
92
93
|
| 'onDidChangeDirty'
|
|
93
|
-
| '
|
|
94
|
+
| 'setDiskBaseline'
|
|
94
95
|
> {
|
|
95
96
|
/**
|
|
96
97
|
* Stub-tailored read accessor. Returns just the surface the framework's
|
|
@@ -114,9 +115,12 @@ export interface StubHydraniumTextDocuments extends Pick<
|
|
|
114
115
|
/** Mark `uri` as open in the LSP textual language client (drives {@link isOpenInLanguageClient}). */
|
|
115
116
|
seedOpenInLanguageClient(uri: string): void;
|
|
116
117
|
/**
|
|
117
|
-
* Synchronously deliver `onDidClose` to subscribers, then `
|
|
118
|
-
* when no client has `uri` open any more
|
|
119
|
-
*
|
|
118
|
+
* Synchronously deliver `onDidClose` to subscribers, then `onDidReleaseDocument`
|
|
119
|
+
* when no client has `uri` open any more, then the dirty flip of a dropped
|
|
120
|
+
* holder: the real store's order. The stub keeps no release grace and runs
|
|
121
|
+
* no release handler, so the flip comes at once and without text, where the
|
|
122
|
+
* real store announces it once the handler settles, with text while the
|
|
123
|
+
* build still has the document.
|
|
120
124
|
*/
|
|
121
125
|
fireClose(uri: string, clientId: string): void;
|
|
122
126
|
/**
|
|
@@ -162,8 +166,9 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
162
166
|
// stand in for canonical URIs (one stub-boundary cast, like `fireClose`).
|
|
163
167
|
const sessions = new ClientSessionRegistry();
|
|
164
168
|
const key = (uri: string): CanonicalUri => uri as CanonicalUri;
|
|
165
|
-
const
|
|
169
|
+
const documentReleased = new Emitter<DocumentReleasedEvent>();
|
|
166
170
|
const baselines = new Map<string, string | undefined>();
|
|
171
|
+
const openedVersions = new Map<string, number>();
|
|
167
172
|
const dirty = new Set<string>();
|
|
168
173
|
const dirtyChanged = new Emitter<DocumentDirtyChangedEvent>();
|
|
169
174
|
// As the real store: dirty while held with text other than the baseline,
|
|
@@ -177,7 +182,12 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
177
182
|
} else {
|
|
178
183
|
dirty.delete(uri);
|
|
179
184
|
}
|
|
180
|
-
|
|
185
|
+
// No build behind the stub, so a document no longer held is announced without text.
|
|
186
|
+
dirtyChanged.fire(
|
|
187
|
+
held === undefined
|
|
188
|
+
? { uri: key(uri) }
|
|
189
|
+
: { uri: key(uri), text: { version: held.version, hash: textHash(held.text), dirty: now } }
|
|
190
|
+
);
|
|
181
191
|
}
|
|
182
192
|
};
|
|
183
193
|
|
|
@@ -201,6 +211,9 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
201
211
|
version(uri) {
|
|
202
212
|
return docs.get(uri)?.version ?? 0;
|
|
203
213
|
},
|
|
214
|
+
openedVersion(uri) {
|
|
215
|
+
return docs.has(uri) ? openedVersions.get(uri) : undefined;
|
|
216
|
+
},
|
|
204
217
|
textState(uri) {
|
|
205
218
|
const held = docs.get(uri);
|
|
206
219
|
return held && { version: held.version, hash: textHash(held.text), dirty: dirty.has(uri) };
|
|
@@ -237,7 +250,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
237
250
|
},
|
|
238
251
|
notifyDidSaveTextDocument(event, clientId) {
|
|
239
252
|
if (event.text !== undefined) {
|
|
240
|
-
stub.
|
|
253
|
+
stub.setDiskBaseline(event.textDocument.uri, event.text);
|
|
241
254
|
}
|
|
242
255
|
saves.push({ uri: event.textDocument.uri, clientId });
|
|
243
256
|
for (const listener of saveListeners.slice()) {
|
|
@@ -274,8 +287,9 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
274
287
|
isOpenInClient(uri, clientId) {
|
|
275
288
|
return sessions.isOpenIn(key(uri), clientId);
|
|
276
289
|
},
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
isReleaseDeferred() {
|
|
291
|
+
// The stub keeps no release grace.
|
|
292
|
+
return false;
|
|
279
293
|
},
|
|
280
294
|
attachClient(uri, clientId) {
|
|
281
295
|
return docs.has(uri) && sessions.addOpen(key(uri), clientId);
|
|
@@ -298,8 +312,8 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
298
312
|
get onDidCloseSession() {
|
|
299
313
|
return sessions.onDidCloseSession;
|
|
300
314
|
},
|
|
301
|
-
get
|
|
302
|
-
return
|
|
315
|
+
get onDidReleaseDocument() {
|
|
316
|
+
return documentReleased.event;
|
|
303
317
|
},
|
|
304
318
|
isDirty(uri) {
|
|
305
319
|
return dirty.has(uri);
|
|
@@ -307,7 +321,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
307
321
|
get onDidChangeDirty() {
|
|
308
322
|
return dirtyChanged.event;
|
|
309
323
|
},
|
|
310
|
-
|
|
324
|
+
setDiskBaseline(uri, text) {
|
|
311
325
|
if (docs.has(uri)) {
|
|
312
326
|
baselines.set(uri, text);
|
|
313
327
|
refreshDirty(uri);
|
|
@@ -331,6 +345,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
331
345
|
seedOpen(uri, text, clientId) {
|
|
332
346
|
sessions.addOpen(key(uri), clientId);
|
|
333
347
|
docs.set(uri, { uri, version: 1, text, clientId });
|
|
348
|
+
openedVersions.set(uri, 1);
|
|
334
349
|
baselines.set(uri, text);
|
|
335
350
|
},
|
|
336
351
|
seedOpenInLanguageClient(uri) {
|
|
@@ -342,9 +357,9 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
342
357
|
// class: listeners detect the last-close transition by consulting
|
|
343
358
|
// `isOpenInAnyClient`, which reads the already-decremented state.
|
|
344
359
|
const holder = docs.get(uri);
|
|
345
|
-
|
|
360
|
+
const dropped = holder !== undefined && holder.clientId === clientId;
|
|
361
|
+
if (dropped) {
|
|
346
362
|
docs.delete(uri);
|
|
347
|
-
refreshDirty(uri);
|
|
348
363
|
}
|
|
349
364
|
const removed = sessions.removeOpen(key(uri), clientId);
|
|
350
365
|
if (clientId === LANGUAGE_CLIENT_ID) {
|
|
@@ -355,7 +370,11 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
355
370
|
listener(event);
|
|
356
371
|
}
|
|
357
372
|
if (removed && !sessions.isOpen(key(uri))) {
|
|
358
|
-
|
|
373
|
+
documentReleased.fire({ uri: key(uri) });
|
|
374
|
+
}
|
|
375
|
+
// After the release event, in the order the real store announces them.
|
|
376
|
+
if (dropped) {
|
|
377
|
+
refreshDirty(uri);
|
|
359
378
|
}
|
|
360
379
|
},
|
|
361
380
|
reset() {
|
|
@@ -366,6 +385,7 @@ export function makeStubHydraniumTextDocuments(): StubHydraniumTextDocuments {
|
|
|
366
385
|
appliedEdits.length = 0;
|
|
367
386
|
staged.length = 0;
|
|
368
387
|
baselines.clear();
|
|
388
|
+
openedVersions.clear();
|
|
369
389
|
dirty.clear();
|
|
370
390
|
saveListeners.length = 0;
|
|
371
391
|
closeListeners.length = 0;
|
|
@@ -1,133 +0,0 @@
|
|
|
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
|
-
import { type TextEdit } from 'vscode-languageserver';
|
|
10
|
-
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
11
|
-
/**
|
|
12
|
-
* The minimum surface the shadow needs from a `HydraniumTextDocuments`:
|
|
13
|
-
* a `create` method that materialises a fresh text document via the
|
|
14
|
-
* configured `TextDocumentsConfiguration`. Declared as a narrow
|
|
15
|
-
* structural interface so the manager satisfies it without an explicit
|
|
16
|
-
* import (sidestepping the circular `HydraniumTextDocuments → shadow → manager`
|
|
17
|
-
* dependency) and so tests can stub it with a minimal object literal.
|
|
18
|
-
*/
|
|
19
|
-
export interface ShadowDocumentSource {
|
|
20
|
-
create(uri: string, languageId: string, version: number, content: string): TextDocument;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Whether `edits` is the single full-document replace {@link LanguageClientTextShadow.computeEdits}
|
|
24
|
-
* emits when it has no usable baseline.
|
|
25
|
-
*
|
|
26
|
-
* The distinction matters at the `workspace/applyEdit` egress: a full replace is
|
|
27
|
-
* position-INDEPENDENT and therefore lands correctly on any client buffer, while
|
|
28
|
-
* a line-keyed diff only lands correctly on the exact text it was diffed
|
|
29
|
-
* against. Only the latter needs a client-version gate.
|
|
30
|
-
*/
|
|
31
|
-
export declare function isFullReplace(edits: readonly TextEdit[]): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Tracks the text content the language client (typically Monaco) is believed to
|
|
34
|
-
* currently hold so outgoing `workspace/applyEdit` requests can ship minimal
|
|
35
|
-
* diffs instead of full-document replaces.
|
|
36
|
-
*
|
|
37
|
-
* Why it matters: a full-range replace on a moderately-sized YAML or DSL source
|
|
38
|
-
* forces Monaco to re-tokenise the entire document (observed multi-second hangs
|
|
39
|
-
* on large diagrams), whereas line-level diffs usually reduce that to a handful
|
|
40
|
-
* of small edits.
|
|
41
|
-
*
|
|
42
|
-
* Update points the caller is responsible for:
|
|
43
|
-
* - {@link computeEdits} after each outgoing server→client sync (updates the shadow optimistically).
|
|
44
|
-
* - {@link set} when the server receives a `didChange` from the language client (the client now
|
|
45
|
-
* holds that text; we shouldn't resend it).
|
|
46
|
-
* - {@link setOpenedText} when the client opens a document the server did not baseline for it —
|
|
47
|
-
* an equality-only baseline, since that text is a snapshot rather than a tracked one.
|
|
48
|
-
* - {@link invalidate} when the client rejects an edit (`applied=false`) or a doc is closed —
|
|
49
|
-
* the shadow is now stale and the next sync must fall back to a full replace.
|
|
50
|
-
*/
|
|
51
|
-
export declare class LanguageClientTextShadow {
|
|
52
|
-
protected onFallback: (uri: string, reason: string) => void;
|
|
53
|
-
protected documents: ShadowDocumentSource;
|
|
54
|
-
protected shadow: Map<string, string>;
|
|
55
|
-
/**
|
|
56
|
-
* Per URI, the text the client declared it opened the document with, held
|
|
57
|
-
* only until a real baseline exists. Consulted by {@link computeEdits} when
|
|
58
|
-
* there is no shadow, and dropped on {@link invalidate}: a snapshot that
|
|
59
|
-
* outlived what the client holds would let the equality check skip an edit
|
|
60
|
-
* the client still needs.
|
|
61
|
-
*/
|
|
62
|
-
protected opened: Map<string, string>;
|
|
63
|
-
/**
|
|
64
|
-
* @param onFallback invoked when {@link computeEdits} falls back to a full-range replace due to
|
|
65
|
-
* apply-verify mismatch. Useful for logging regressions.
|
|
66
|
-
* @param documents the {@link ShadowDocumentSource} used to materialise the throwaway probe
|
|
67
|
-
* document in {@link computeEdits}. In production this is the owning
|
|
68
|
-
* `HydraniumTextDocuments` (whose `create` threads through its configured
|
|
69
|
-
* `TextDocumentsConfiguration`); tests pass a minimal stub. Routing
|
|
70
|
-
* through the manager rather than `TextDocument.create` directly avoids the
|
|
71
|
-
* `instanceof FullTextDocument` gate in the bare LSP `TextDocument.update`,
|
|
72
|
-
* keeping the verifier honest for adopters with a custom text-document type.
|
|
73
|
-
*/
|
|
74
|
-
constructor(onFallback: (uri: string, reason: string) => void, documents: ShadowDocumentSource);
|
|
75
|
-
/** Record the text the language client is known to hold (e.g. after a `didChange` echo). */
|
|
76
|
-
set(uri: string, text: string): void;
|
|
77
|
-
/**
|
|
78
|
-
* Record the buffer the language client declared at `didOpen`, for a URI
|
|
79
|
-
* with no baseline yet.
|
|
80
|
-
*
|
|
81
|
-
* Deliberately NOT a {@link set}: this text is only ever compared for
|
|
82
|
-
* EQUALITY, never diffed against. A line-keyed diff is position-dependent,
|
|
83
|
-
* so keying one to a `didOpen` snapshot the client may have moved past
|
|
84
|
-
* splices its buffer — the failure {@link computeEdits}'s full-replace
|
|
85
|
-
* fallback exists to avoid.
|
|
86
|
-
*/
|
|
87
|
-
setOpenedText(uri: string, text: string): void;
|
|
88
|
-
/**
|
|
89
|
-
* The text the language client is believed to hold: the tracked text, else
|
|
90
|
-
* the buffer it declared at `didOpen`.
|
|
91
|
-
*
|
|
92
|
-
* Read at the `workspace/applyEdit` egress BEFORE {@link computeEdits},
|
|
93
|
-
* which overwrites the tracked text and drops the opened snapshot. A change
|
|
94
|
-
* the client sends carries ranges addressing this text and nothing else, so
|
|
95
|
-
* losing it leaves that change unreconstructable — and applying its ranges
|
|
96
|
-
* to the already-advanced synced text splices lines they never addressed.
|
|
97
|
-
*
|
|
98
|
-
* Deliberately wider than what {@link computeEdits} may diff from: an
|
|
99
|
-
* opened snapshot is a position-dependent key to a buffer the client may
|
|
100
|
-
* have moved past, so keying a diff to it splices that buffer.
|
|
101
|
-
*/
|
|
102
|
-
clientText(uri: string): string | undefined;
|
|
103
|
-
/**
|
|
104
|
-
* Whether a tracked text exists for `uri`, which is what decides whether a
|
|
105
|
-
* push can be diffed at all. Distinct from {@link clientText}, which also
|
|
106
|
-
* answers from the opened snapshot and so cannot settle this.
|
|
107
|
-
*/
|
|
108
|
-
isTracked(uri: string): boolean;
|
|
109
|
-
/** Forget the shadow for a URI; next {@link computeEdits} falls back to a full-document replace. */
|
|
110
|
-
invalidate(uri: string): void;
|
|
111
|
-
/**
|
|
112
|
-
* Return the minimal LSP {@link TextEdit}s required to bring the language client's content from
|
|
113
|
-
* the tracked shadow to `newText`. Updates the shadow to `newText` so subsequent calls diff
|
|
114
|
-
* against it.
|
|
115
|
-
*
|
|
116
|
-
* - Empty array when the shadow already equals `newText` (caller should skip the RPC),
|
|
117
|
-
* or when there is no shadow and the client opened the document with exactly `newText`
|
|
118
|
-
* (see {@link setOpenedText}) — pushing it anyway dirties the client's buffer on open.
|
|
119
|
-
* - Full-range replace when there is no shadow (first sync or post-{@link invalidate}).
|
|
120
|
-
* - Line-level diff otherwise. Before returning, we locally reconstruct `newText` by applying
|
|
121
|
-
* the diff to `old`; if that doesn't match, we fall back to a full-range replace and notify
|
|
122
|
-
* the fallback callback. This catches any algorithmic regression in {@link diffToEdits}
|
|
123
|
-
* without risking client corruption.
|
|
124
|
-
*/
|
|
125
|
-
computeEdits(uri: string, newText: string): TextEdit[];
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Convert a {@link diffLines} result to a list of LSP {@link TextEdit}s keyed on line positions
|
|
129
|
-
* in the *old* text. Contiguous added/removed hunks are coalesced into a single replace so the
|
|
130
|
-
* client sees one edit per changed region.
|
|
131
|
-
*/
|
|
132
|
-
export declare function diffToEdits(oldText: string, newText: string): TextEdit[];
|
|
133
|
-
//# sourceMappingURL=language-client-text-shadow.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"language-client-text-shadow.d.ts","sourceRoot":"","sources":["../../src/documents/language-client-text-shadow.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,EAAS,KAAK,QAAQ,EAAY,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IAClC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAAC;CAC1F;AAKD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,CAMjE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,wBAAwB;IAwB/B,SAAS,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI;IAC3D,SAAS,CAAC,SAAS,EAAE,oBAAoB;IAxB5C,SAAS,CAAC,MAAM,sBAA6B;IAE7C;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,sBAA6B;IAE7C;;;;;;;;;;OAUG;IACH,YACa,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,EACjD,SAAS,EAAE,oBAAoB,EACxC;IAEJ,4FAA4F;IAC5F,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnC;IAED;;;;;;;;;OASG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE7C;IAED;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE1C;IAED;;;;OAIG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9B;IAED,oGAAoG;IACpG,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAO5B;IAED;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAqBrD;CACH;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CA+BxE"}
|