@hydranium/core 1.0.0-next.35 → 1.0.0-next.351
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/README.md +78 -82
- package/lib/browser/host-heads-in-worker.d.ts +77 -0
- package/lib/browser/host-heads-in-worker.d.ts.map +1 -0
- package/lib/browser/host-heads-in-worker.js +169 -0
- package/lib/browser/host-heads-in-worker.js.map +1 -0
- package/lib/browser/index.d.ts +12 -0
- package/lib/browser/index.d.ts.map +1 -0
- package/lib/browser/index.js +17 -0
- package/lib/browser/index.js.map +1 -0
- package/lib/browser/indexeddb-file-system-store.d.ts +82 -0
- package/lib/browser/indexeddb-file-system-store.d.ts.map +1 -0
- package/lib/browser/indexeddb-file-system-store.js +181 -0
- package/lib/browser/indexeddb-file-system-store.js.map +1 -0
- package/lib/browser/start-language-server-in-worker.d.ts +32 -0
- package/lib/browser/start-language-server-in-worker.d.ts.map +1 -0
- package/lib/browser/start-language-server-in-worker.js +40 -0
- package/lib/browser/start-language-server-in-worker.js.map +1 -0
- package/lib/documents/ast-document-manager.d.ts +266 -124
- package/lib/documents/ast-document-manager.d.ts.map +1 -1
- package/lib/documents/ast-document-manager.js +293 -132
- package/lib/documents/ast-document-manager.js.map +1 -1
- package/lib/documents/client-ids.d.ts +5 -19
- package/lib/documents/client-ids.d.ts.map +1 -1
- package/lib/documents/client-ids.js +5 -19
- package/lib/documents/client-ids.js.map +1 -1
- package/lib/documents/client-session-errors.d.ts +15 -0
- package/lib/documents/client-session-errors.d.ts.map +1 -0
- package/lib/documents/client-session-errors.js +15 -0
- package/lib/documents/client-session-errors.js.map +1 -0
- package/lib/documents/client-session-registry.d.ts +146 -0
- package/lib/documents/client-session-registry.d.ts.map +1 -0
- package/lib/documents/client-session-registry.js +207 -0
- package/lib/documents/client-session-registry.js.map +1 -0
- package/lib/documents/dirty-state-tracker.d.ts +101 -0
- package/lib/documents/dirty-state-tracker.d.ts.map +1 -0
- package/lib/documents/dirty-state-tracker.js +85 -0
- package/lib/documents/dirty-state-tracker.js.map +1 -0
- package/lib/documents/document-release-handler.d.ts +204 -0
- package/lib/documents/document-release-handler.d.ts.map +1 -0
- package/lib/documents/document-release-handler.js +317 -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/file-system-task-queue.d.ts +52 -0
- package/lib/documents/file-system-task-queue.d.ts.map +1 -0
- package/lib/documents/file-system-task-queue.js +37 -0
- package/lib/documents/file-system-task-queue.js.map +1 -0
- package/lib/documents/hydranium-text-documents.d.ts +445 -282
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +903 -483
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/documents/index.d.ts +10 -2
- package/lib/documents/index.d.ts.map +1 -1
- package/lib/documents/index.js +10 -2
- package/lib/documents/index.js.map +1 -1
- package/lib/documents/language-client-shadow.d.ts +234 -0
- package/lib/documents/language-client-shadow.d.ts.map +1 -0
- package/lib/documents/language-client-shadow.js +306 -0
- package/lib/documents/language-client-shadow.js.map +1 -0
- package/lib/documents/model-ledger.d.ts +47 -0
- package/lib/documents/model-ledger.d.ts.map +1 -0
- package/lib/documents/model-ledger.js +30 -0
- package/lib/documents/model-ledger.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/documents/version-sync-service.d.ts +138 -0
- package/lib/documents/version-sync-service.d.ts.map +1 -0
- package/lib/documents/version-sync-service.js +173 -0
- package/lib/documents/version-sync-service.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/langium/ast-extension/ast-extension-service.d.ts +42 -31
- package/lib/langium/ast-extension/ast-extension-service.d.ts.map +1 -1
- package/lib/langium/ast-extension/ast-extension-service.js +45 -19
- package/lib/langium/ast-extension/ast-extension-service.js.map +1 -1
- package/lib/langium/ast-extension/ast-node-builder.d.ts +12 -7
- package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
- package/lib/langium/ast-extension/ast-node-builder.js +25 -9
- package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
- package/lib/langium/bootstrap.d.ts +49 -36
- package/lib/langium/bootstrap.d.ts.map +1 -1
- package/lib/langium/bootstrap.js +83 -44
- package/lib/langium/bootstrap.js.map +1 -1
- package/lib/langium/build-phase-pass/build-phase-pass-service.d.ts +9 -9
- package/lib/langium/build-phase-pass/build-phase-pass-service.d.ts.map +1 -1
- package/lib/langium/build-phase-pass/build-phase-pass-service.js +25 -16
- package/lib/langium/build-phase-pass/build-phase-pass-service.js.map +1 -1
- package/lib/langium/build-phase-pass/build-phase-pass.d.ts +20 -8
- package/lib/langium/build-phase-pass/build-phase-pass.d.ts.map +1 -1
- package/lib/langium/build-phase-pass/build-phase-pass.js +20 -0
- package/lib/langium/build-phase-pass/build-phase-pass.js.map +1 -1
- package/lib/langium/composite-ast-reflection.d.ts +46 -0
- package/lib/langium/composite-ast-reflection.d.ts.map +1 -0
- package/lib/langium/composite-ast-reflection.js +97 -0
- package/lib/langium/composite-ast-reflection.js.map +1 -0
- package/lib/langium/config/configuration-provider.d.ts +81 -0
- package/lib/langium/config/configuration-provider.d.ts.map +1 -0
- package/lib/langium/config/configuration-provider.js +112 -0
- package/lib/langium/config/configuration-provider.js.map +1 -0
- package/lib/langium/config/index.d.ts +1 -0
- package/lib/langium/config/index.d.ts.map +1 -1
- package/lib/langium/config/index.js +1 -0
- package/lib/langium/config/index.js.map +1 -1
- package/lib/langium/config/settings.js.map +1 -1
- package/lib/langium/diagnostics/console-tee.d.ts +27 -0
- package/lib/langium/diagnostics/console-tee.d.ts.map +1 -0
- package/lib/langium/diagnostics/console-tee.js +142 -0
- package/lib/langium/diagnostics/console-tee.js.map +1 -0
- package/lib/langium/diagnostics/format-log-arg.d.ts +18 -0
- package/lib/langium/diagnostics/format-log-arg.d.ts.map +1 -0
- package/lib/langium/diagnostics/format-log-arg.js +36 -0
- package/lib/langium/diagnostics/format-log-arg.js.map +1 -0
- package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts +9 -20
- package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts.map +1 -1
- package/lib/langium/diagnostics/hydranium-langium-profiler.js +15 -24
- package/lib/langium/diagnostics/hydranium-langium-profiler.js.map +1 -1
- package/lib/langium/diagnostics/log-preamble.js +1 -1
- package/lib/langium/diagnostics/log-preamble.js.map +1 -1
- package/lib/langium/diagnostics/logger.d.ts +25 -6
- package/lib/langium/diagnostics/logger.d.ts.map +1 -1
- package/lib/langium/diagnostics/logger.js +49 -17
- package/lib/langium/diagnostics/logger.js.map +1 -1
- package/lib/langium/diagnostics/lsp-logger.d.ts +25 -0
- package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
- package/lib/langium/diagnostics/lsp-logger.js +81 -45
- package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
- package/lib/langium/diagnostics/server-tracer.d.ts.map +1 -1
- package/lib/langium/diagnostics/server-tracer.js.map +1 -1
- package/lib/langium/document-builder/build-pipeline-integration.d.ts +21 -1
- package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
- package/lib/langium/document-builder/build-pipeline-integration.js +28 -30
- package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
- package/lib/langium/document-builder/build-session.d.ts +6 -5
- package/lib/langium/document-builder/build-session.d.ts.map +1 -1
- package/lib/langium/document-builder/build-session.js +5 -5
- package/lib/langium/document-builder/build-session.js.map +1 -1
- package/lib/langium/document-builder/document-builder.d.ts +310 -41
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +490 -79
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/documentation/comment-provider.d.ts.map +1 -1
- package/lib/langium/documentation/comment-provider.js.map +1 -1
- package/lib/langium/integration-services.d.ts +40 -24
- package/lib/langium/integration-services.d.ts.map +1 -1
- package/lib/langium/integration-services.js +7 -4
- package/lib/langium/integration-services.js.map +1 -1
- package/lib/langium/integrity/integrity-rule.d.ts +17 -8
- package/lib/langium/integrity/integrity-rule.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-rule.js.map +1 -1
- package/lib/langium/integrity/integrity-service.d.ts +95 -27
- package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-service.js +303 -63
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
- package/lib/langium/keys/name-based-key-provider.js.map +1 -1
- package/lib/langium/keys/positional-key-provider.d.ts.map +1 -1
- package/lib/langium/keys/positional-key-provider.js.map +1 -1
- package/lib/langium/labeling/label-provider.d.ts.map +1 -1
- package/lib/langium/labeling/label-provider.js.map +1 -1
- package/lib/langium/language-module.d.ts +30 -5
- package/lib/langium/language-module.d.ts.map +1 -1
- package/lib/langium/language-module.js +18 -4
- package/lib/langium/language-module.js.map +1 -1
- package/lib/langium/model-service/client-session.d.ts +383 -0
- package/lib/langium/model-service/client-session.d.ts.map +1 -0
- package/lib/langium/model-service/client-session.js +455 -0
- package/lib/langium/model-service/client-session.js.map +1 -0
- package/lib/langium/model-service/index.d.ts +2 -0
- package/lib/langium/model-service/index.d.ts.map +1 -1
- package/lib/langium/model-service/index.js +2 -0
- package/lib/langium/model-service/index.js.map +1 -1
- package/lib/langium/model-service/model-events.d.ts +66 -0
- package/lib/langium/model-service/model-events.d.ts.map +1 -0
- package/lib/langium/model-service/model-events.js +10 -0
- package/lib/langium/model-service/model-events.js.map +1 -0
- package/lib/langium/model-service/model-service.d.ts +456 -300
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +571 -466
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/module.d.ts +141 -40
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js +52 -40
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/naming/name-provider.d.ts +32 -7
- package/lib/langium/naming/name-provider.d.ts.map +1 -1
- package/lib/langium/naming/name-provider.js +10 -4
- package/lib/langium/naming/name-provider.js.map +1 -1
- package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
- package/lib/langium/project/abstract-project-manager.d.ts.map +1 -1
- package/lib/langium/project/abstract-project-manager.js +13 -6
- package/lib/langium/project/abstract-project-manager.js.map +1 -1
- package/lib/langium/project/project-change-event.d.ts +2 -2
- package/lib/langium/project/single-project-manager.d.ts.map +1 -1
- package/lib/langium/project/single-project-manager.js.map +1 -1
- package/lib/langium/residency/cst-residency-service.d.ts +38 -14
- package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
- package/lib/langium/residency/cst-residency-service.js +24 -58
- package/lib/langium/residency/cst-residency-service.js.map +1 -1
- package/lib/langium/scope/ast-node-description-provider.d.ts.map +1 -1
- package/lib/langium/scope/ast-node-description-provider.js.map +1 -1
- package/lib/langium/scope/fallback-scope.d.ts +27 -0
- package/lib/langium/scope/fallback-scope.d.ts.map +1 -0
- package/lib/langium/scope/fallback-scope.js +35 -0
- package/lib/langium/scope/fallback-scope.js.map +1 -0
- package/lib/langium/scope/hydranium-scope-computation.d.ts +21 -18
- package/lib/langium/scope/hydranium-scope-computation.d.ts.map +1 -1
- package/lib/langium/scope/hydranium-scope-computation.js +38 -39
- package/lib/langium/scope/hydranium-scope-computation.js.map +1 -1
- package/lib/langium/scope/hydranium-scope-provider.d.ts +115 -32
- package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
- package/lib/langium/scope/hydranium-scope-provider.js +201 -39
- package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
- package/lib/langium/scope/index.d.ts +1 -0
- package/lib/langium/scope/index.d.ts.map +1 -1
- package/lib/langium/scope/index.js +1 -0
- package/lib/langium/scope/index.js.map +1 -1
- package/lib/langium/scope/reference-builder.d.ts.map +1 -1
- package/lib/langium/scope/reference-builder.js.map +1 -1
- package/lib/langium/scope/reference-candidate-provider.d.ts +6 -5
- package/lib/langium/scope/reference-candidate-provider.d.ts.map +1 -1
- package/lib/langium/scope/reference-candidate-provider.js +3 -2
- package/lib/langium/scope/reference-candidate-provider.js.map +1 -1
- package/lib/langium/scope/scope-extension-service.d.ts +69 -37
- package/lib/langium/scope/scope-extension-service.d.ts.map +1 -1
- package/lib/langium/scope/scope-extension-service.js +102 -29
- package/lib/langium/scope/scope-extension-service.js.map +1 -1
- package/lib/langium/scope/scoped-ast-node-description.d.ts +1 -3
- package/lib/langium/scope/scoped-ast-node-description.d.ts.map +1 -1
- package/lib/langium/scope/scoped-ast-node-description.js +1 -3
- package/lib/langium/scope/scoped-ast-node-description.js.map +1 -1
- package/lib/langium/serialization/abstract-serializer.d.ts +38 -27
- package/lib/langium/serialization/abstract-serializer.d.ts.map +1 -1
- package/lib/langium/serialization/abstract-serializer.js +54 -30
- package/lib/langium/serialization/abstract-serializer.js.map +1 -1
- package/lib/langium/serialization/json-serializer.d.ts +8 -1
- package/lib/langium/serialization/json-serializer.d.ts.map +1 -1
- package/lib/langium/serialization/json-serializer.js +13 -3
- package/lib/langium/serialization/json-serializer.js.map +1 -1
- package/lib/langium/serialization/serializer.d.ts +1 -1
- package/lib/langium/serialization/yaml-serializer.d.ts +12 -0
- package/lib/langium/serialization/yaml-serializer.d.ts.map +1 -1
- package/lib/langium/serialization/yaml-serializer.js +24 -4
- package/lib/langium/serialization/yaml-serializer.js.map +1 -1
- package/lib/langium/service-registry.d.ts +29 -1
- package/lib/langium/service-registry.d.ts.map +1 -1
- package/lib/langium/service-registry.js +37 -0
- package/lib/langium/service-registry.js.map +1 -1
- package/lib/langium/shared-services.d.ts +13 -7
- package/lib/langium/shared-services.d.ts.map +1 -1
- package/lib/langium/shared-services.js.map +1 -1
- package/lib/langium/single-langium-copy.d.ts +22 -0
- package/lib/langium/single-langium-copy.d.ts.map +1 -0
- package/lib/langium/single-langium-copy.js +36 -0
- package/lib/langium/single-langium-copy.js.map +1 -0
- package/lib/langium/transfer/transfer-encoder.d.ts +86 -37
- package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
- package/lib/langium/transfer/transfer-encoder.js +36 -25
- package/lib/langium/transfer/transfer-encoder.js.map +1 -1
- package/lib/langium/trivia/comment-preserver.d.ts +543 -0
- package/lib/langium/trivia/comment-preserver.d.ts.map +1 -0
- package/lib/langium/trivia/comment-preserver.js +1164 -0
- package/lib/langium/trivia/comment-preserver.js.map +1 -0
- package/lib/langium/trivia/document-ending-preserver.d.ts +51 -0
- package/lib/langium/trivia/document-ending-preserver.d.ts.map +1 -0
- package/lib/langium/trivia/document-ending-preserver.js +62 -0
- package/lib/langium/trivia/document-ending-preserver.js.map +1 -0
- package/lib/langium/trivia/index.d.ts +14 -0
- package/lib/langium/trivia/index.d.ts.map +1 -0
- package/lib/langium/trivia/index.js +14 -0
- package/lib/langium/trivia/index.js.map +1 -0
- package/lib/langium/trivia/trivia-contribution.d.ts +37 -0
- package/lib/langium/trivia/trivia-contribution.d.ts.map +1 -0
- package/lib/langium/trivia/trivia-contribution.js +10 -0
- package/lib/langium/trivia/trivia-contribution.js.map +1 -0
- package/lib/langium/trivia/trivia-preserver.d.ts +52 -0
- package/lib/langium/trivia/trivia-preserver.d.ts.map +1 -0
- package/lib/langium/trivia/trivia-preserver.js +10 -0
- package/lib/langium/trivia/trivia-preserver.js.map +1 -0
- package/lib/langium/trivia/trivia-service.d.ts +70 -0
- package/lib/langium/trivia/trivia-service.d.ts.map +1 -0
- package/lib/langium/trivia/trivia-service.js +56 -0
- package/lib/langium/trivia/trivia-service.js.map +1 -0
- package/lib/langium/update-rewrite/normalize-empty-strings.d.ts.map +1 -1
- package/lib/langium/update-rewrite/update-rewrite-service.d.ts +1 -1
- package/lib/langium/update-rewrite/update-rewrite-service.d.ts.map +1 -1
- package/lib/langium/update-rewrite/update-rewrite-service.js.map +1 -1
- package/lib/langium/update-rewrite/update-rewrite.d.ts +1 -1
- package/lib/langium/validation/document-validator.d.ts +168 -23
- package/lib/langium/validation/document-validator.d.ts.map +1 -1
- package/lib/langium/validation/document-validator.js +264 -26
- package/lib/langium/validation/document-validator.js.map +1 -1
- package/lib/langium/validation/validation-contribution-collector.d.ts +9 -1
- package/lib/langium/validation/validation-contribution-collector.d.ts.map +1 -1
- package/lib/langium/validation/validation-contribution-collector.js +1 -1
- package/lib/langium/validation/validation-contribution-collector.js.map +1 -1
- package/lib/langium/workspace/document-uri-policy.d.ts +24 -15
- package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
- package/lib/langium/workspace/document-uri-policy.js +22 -13
- package/lib/langium/workspace/document-uri-policy.js.map +1 -1
- package/lib/langium/workspace/file-not-found.d.ts +23 -0
- package/lib/langium/workspace/file-not-found.d.ts.map +1 -0
- package/lib/langium/workspace/file-not-found.js +31 -0
- package/lib/langium/workspace/file-not-found.js.map +1 -0
- package/lib/langium/workspace/file-system-provider.d.ts +167 -8
- package/lib/langium/workspace/file-system-provider.d.ts.map +1 -1
- package/lib/langium/workspace/file-system-provider.js +115 -13
- package/lib/langium/workspace/file-system-provider.js.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +31 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-langium-document-factory.js +54 -0
- package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-lock.d.ts +43 -1
- package/lib/langium/workspace/hydranium-workspace-lock.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-lock.js +58 -1
- package/lib/langium/workspace/hydranium-workspace-lock.js.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-manager.d.ts +103 -21
- package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-manager.js +184 -31
- package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
- package/lib/langium/workspace/in-memory-file-system-provider.d.ts +26 -9
- package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
- package/lib/langium/workspace/in-memory-file-system-provider.js +45 -22
- package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
- package/lib/langium/workspace/index-manager.d.ts.map +1 -1
- package/lib/langium/workspace/index-manager.js.map +1 -1
- package/lib/langium/workspace/index.d.ts +1 -0
- package/lib/langium/workspace/index.d.ts.map +1 -1
- package/lib/langium/workspace/index.js +1 -0
- package/lib/langium/workspace/index.js.map +1 -1
- package/lib/langium/workspace/initialize-workspace.d.ts +40 -3
- package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
- package/lib/langium/workspace/initialize-workspace.js +68 -3
- package/lib/langium/workspace/initialize-workspace.js.map +1 -1
- package/lib/langium/workspace/langium-documents.d.ts +7 -0
- package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
- package/lib/langium/workspace/langium-documents.js +18 -1
- package/lib/langium/workspace/langium-documents.js.map +1 -1
- package/lib/langium/workspace/persistent-file-system-provider.d.ts +16 -10
- package/lib/langium/workspace/persistent-file-system-provider.d.ts.map +1 -1
- package/lib/langium/workspace/persistent-file-system-provider.js +25 -13
- package/lib/langium/workspace/persistent-file-system-provider.js.map +1 -1
- package/lib/langium/workspace/self-save-registry.d.ts +84 -0
- package/lib/langium/workspace/self-save-registry.d.ts.map +1 -0
- package/lib/{documents → langium/workspace}/self-save-registry.js +33 -22
- package/lib/langium/workspace/self-save-registry.js.map +1 -0
- package/lib/langium/workspace/virtual-document.d.ts +55 -21
- package/lib/langium/workspace/virtual-document.d.ts.map +1 -1
- package/lib/langium/workspace/virtual-document.js +87 -24
- package/lib/langium/workspace/virtual-document.js.map +1 -1
- package/lib/langium/workspace/write-lock-scope.d.ts +22 -10
- package/lib/langium/workspace/write-lock-scope.d.ts.map +1 -1
- package/lib/langium/workspace/write-lock-scope.js +15 -5
- package/lib/langium/workspace/write-lock-scope.js.map +1 -1
- package/lib/launcher/integrated-server.d.ts +3 -1
- package/lib/launcher/integrated-server.d.ts.map +1 -1
- package/lib/locale/server-locale.d.ts +19 -5
- package/lib/locale/server-locale.d.ts.map +1 -1
- package/lib/locale/server-locale.js +1 -1
- package/lib/locale/server-locale.js.map +1 -1
- package/lib/lsp/completion/hydranium-completion-provider.d.ts +44 -2
- package/lib/lsp/completion/hydranium-completion-provider.d.ts.map +1 -1
- package/lib/lsp/completion/hydranium-completion-provider.js +68 -0
- package/lib/lsp/completion/hydranium-completion-provider.js.map +1 -1
- package/lib/lsp/connection-features.d.ts +23 -0
- package/lib/lsp/connection-features.d.ts.map +1 -0
- package/lib/lsp/connection-features.js +60 -0
- package/lib/lsp/connection-features.js.map +1 -0
- package/lib/lsp/diagnostics-connection.d.ts +27 -0
- package/lib/lsp/diagnostics-connection.d.ts.map +1 -0
- package/lib/lsp/diagnostics-connection.js +53 -0
- package/lib/lsp/diagnostics-connection.js.map +1 -0
- package/lib/lsp/edit-requests-guard.d.ts +31 -0
- package/lib/lsp/edit-requests-guard.d.ts.map +1 -0
- package/lib/lsp/edit-requests-guard.js +125 -0
- package/lib/lsp/edit-requests-guard.js.map +1 -0
- package/lib/lsp/hydranium-document-update-handler.d.ts +126 -82
- package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.js +235 -102
- package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
- package/lib/lsp/index.d.ts +6 -2
- package/lib/lsp/index.d.ts.map +1 -1
- package/lib/lsp/index.js +6 -2
- package/lib/lsp/index.js.map +1 -1
- package/lib/lsp/lsp-latency.d.ts +39 -0
- package/lib/lsp/lsp-latency.d.ts.map +1 -0
- package/lib/lsp/lsp-latency.js +58 -0
- package/lib/lsp/lsp-latency.js.map +1 -0
- package/lib/lsp/save-text-document.d.ts +31 -0
- package/lib/lsp/save-text-document.d.ts.map +1 -0
- package/lib/lsp/save-text-document.js +52 -0
- package/lib/lsp/save-text-document.js.map +1 -0
- package/lib/lsp/semantic-token-provider.d.ts +58 -2
- package/lib/lsp/semantic-token-provider.d.ts.map +1 -1
- package/lib/lsp/semantic-token-provider.js +98 -9
- package/lib/lsp/semantic-token-provider.js.map +1 -1
- package/lib/lsp/shared-module.d.ts +4 -8
- package/lib/lsp/shared-module.d.ts.map +1 -1
- package/lib/lsp/shared-module.js.map +1 -1
- package/lib/lsp/start-language-server.d.ts +12 -7
- package/lib/lsp/start-language-server.d.ts.map +1 -1
- package/lib/lsp/start-language-server.js +21 -7
- package/lib/lsp/start-language-server.js.map +1 -1
- package/lib/messages/carriers.d.ts +4 -6
- package/lib/messages/carriers.d.ts.map +1 -1
- package/lib/messages/carriers.js +4 -6
- package/lib/messages/carriers.js.map +1 -1
- package/lib/messages/index.d.ts +2 -2
- package/lib/messages/index.d.ts.map +1 -1
- package/lib/messages/index.js +2 -2
- package/lib/messages/index.js.map +1 -1
- package/lib/messages/renderer.d.ts +32 -7
- package/lib/messages/renderer.d.ts.map +1 -1
- package/lib/messages/renderer.js +7 -1
- package/lib/messages/renderer.js.map +1 -1
- package/lib/node/event-loop-monitor.d.ts.map +1 -1
- package/lib/node/event-loop-monitor.js +2 -1
- package/lib/node/event-loop-monitor.js.map +1 -1
- package/lib/node/heap-ceiling.d.ts +61 -0
- package/lib/node/heap-ceiling.d.ts.map +1 -0
- package/lib/node/heap-ceiling.js +72 -0
- package/lib/node/heap-ceiling.js.map +1 -0
- package/lib/node/index.d.ts +1 -1
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +5 -1
- package/lib/node/index.js.map +1 -1
- package/lib/node/latency-from-env.d.ts +1 -1
- package/lib/node/latency-from-env.js +1 -1
- package/lib/node/lint-grammar.js.map +1 -1
- package/lib/node/log-file-sink.d.ts +3 -2
- package/lib/node/log-file-sink.d.ts.map +1 -1
- package/lib/node/log-file-sink.js +6 -2
- package/lib/node/log-file-sink.js.map +1 -1
- package/lib/node/log-preamble-node.d.ts +5 -3
- package/lib/node/log-preamble-node.d.ts.map +1 -1
- package/lib/node/log-preamble-node.js +13 -10
- package/lib/node/log-preamble-node.js.map +1 -1
- package/lib/node/measure-memory.d.ts.map +1 -1
- package/lib/node/measure-memory.js +5 -4
- package/lib/node/measure-memory.js.map +1 -1
- package/lib/node/memory-monitor.d.ts +2 -2
- package/lib/node/memory-monitor.d.ts.map +1 -1
- package/lib/node/memory-monitor.js +9 -10
- package/lib/node/memory-monitor.js.map +1 -1
- package/lib/node/node-file-system-provider.d.ts +67 -9
- package/lib/node/node-file-system-provider.d.ts.map +1 -1
- package/lib/node/node-file-system-provider.js +198 -53
- package/lib/node/node-file-system-provider.js.map +1 -1
- package/lib/node/profile-capture.d.ts +12 -4
- package/lib/node/profile-capture.d.ts.map +1 -1
- package/lib/node/profile-capture.js +8 -6
- package/lib/node/profile-capture.js.map +1 -1
- package/lib/node/profile-digest.js.map +1 -1
- package/lib/node/profiling-run.d.ts +13 -3
- package/lib/node/profiling-run.d.ts.map +1 -1
- package/lib/node/profiling-run.js +17 -8
- package/lib/node/profiling-run.js.map +1 -1
- package/lib/node/rename-over-open-readers.d.ts +61 -0
- package/lib/node/rename-over-open-readers.d.ts.map +1 -0
- package/lib/node/rename-over-open-readers.js +80 -0
- package/lib/node/rename-over-open-readers.js.map +1 -0
- package/lib/node/server-diagnostics.js +1 -1
- package/lib/node/server-diagnostics.js.map +1 -1
- package/lib/node/server-state-snapshot.js +4 -4
- package/lib/node/server-state-snapshot.js.map +1 -1
- package/lib/node/socket-launcher.d.ts +43 -7
- package/lib/node/socket-launcher.d.ts.map +1 -1
- package/lib/node/socket-launcher.js +42 -7
- package/lib/node/socket-launcher.js.map +1 -1
- package/lib/node/startup-cpu-profile.d.ts +29 -0
- package/lib/node/startup-cpu-profile.d.ts.map +1 -0
- package/lib/node/startup-cpu-profile.js +91 -0
- package/lib/node/startup-cpu-profile.js.map +1 -0
- package/lib/node/stdio-launcher.d.ts +13 -13
- package/lib/node/stdio-launcher.d.ts.map +1 -1
- package/lib/node/stdio-launcher.js +21 -11
- package/lib/node/stdio-launcher.js.map +1 -1
- package/lib/node/validate-workspace.d.ts +1 -1
- package/lib/node/validate-workspace.d.ts.map +1 -1
- package/lib/node/validate-workspace.js +5 -1
- package/lib/node/validate-workspace.js.map +1 -1
- package/lib/testing/document-uri-policy-conformance.js +1 -1
- package/lib/testing/document-uri-policy-conformance.js.map +1 -1
- package/lib/testing/fake-description.js.map +1 -1
- package/lib/testing/fake-document.d.ts +2 -1
- package/lib/testing/fake-document.d.ts.map +1 -1
- package/lib/testing/fake-document.js.map +1 -1
- package/lib/testing/index.d.ts +1 -1
- package/lib/testing/index.d.ts.map +1 -1
- package/lib/testing/index.js +2 -1
- package/lib/testing/index.js.map +1 -1
- package/lib/testing/langium-test-helpers.d.ts.map +1 -1
- package/lib/testing/langium-test-helpers.js +1 -0
- package/lib/testing/langium-test-helpers.js.map +1 -1
- package/lib/testing/make-noop-language-services.js.map +1 -1
- package/lib/testing/make-noop-shared-services.d.ts +4 -4
- package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
- package/lib/testing/make-noop-shared-services.js +4 -4
- package/lib/testing/make-noop-shared-services.js.map +1 -1
- package/lib/testing/make-test-services.d.ts +51 -20
- package/lib/testing/make-test-services.d.ts.map +1 -1
- package/lib/testing/make-test-services.js +29 -13
- package/lib/testing/make-test-services.js.map +1 -1
- package/lib/testing/make-test-tracer.d.ts +12 -0
- package/lib/testing/make-test-tracer.d.ts.map +1 -1
- package/lib/testing/make-test-tracer.js +13 -0
- package/lib/testing/make-test-tracer.js.map +1 -1
- package/lib/testing/node/applied-edit-capture.d.ts +29 -0
- package/lib/testing/node/applied-edit-capture.d.ts.map +1 -0
- package/lib/testing/node/applied-edit-capture.js +117 -0
- package/lib/testing/node/applied-edit-capture.js.map +1 -0
- package/lib/testing/node/golden-corpus.js.map +1 -1
- package/lib/testing/node/index.d.ts +1 -0
- package/lib/testing/node/index.d.ts.map +1 -1
- package/lib/testing/node/index.js +1 -0
- package/lib/testing/node/index.js.map +1 -1
- package/lib/testing/node/lsp-harness.d.ts.map +1 -1
- package/lib/testing/node/lsp-harness.js +2 -0
- package/lib/testing/node/lsp-harness.js.map +1 -1
- package/lib/testing/node/lsp-server-connection.d.ts +21 -6
- package/lib/testing/node/lsp-server-connection.d.ts.map +1 -1
- package/lib/testing/node/lsp-server-connection.js +49 -106
- package/lib/testing/node/lsp-server-connection.js.map +1 -1
- package/lib/testing/node/scratch-workspace.d.ts.map +1 -1
- package/lib/testing/node/scratch-workspace.js +10 -5
- package/lib/testing/node/scratch-workspace.js.map +1 -1
- package/lib/testing/node/spawned-server.d.ts +30 -1
- package/lib/testing/node/spawned-server.d.ts.map +1 -1
- package/lib/testing/node/spawned-server.js +39 -2
- package/lib/testing/node/spawned-server.js.map +1 -1
- package/lib/testing/node/unhandled-rejections.d.ts +20 -0
- package/lib/testing/node/unhandled-rejections.d.ts.map +1 -0
- package/lib/testing/node/unhandled-rejections.js +32 -0
- package/lib/testing/node/unhandled-rejections.js.map +1 -0
- package/lib/testing/playwright/browser-capture-bridge.js.map +1 -1
- package/lib/testing/playwright/e2e-profiling.js.map +1 -1
- package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
- package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
- package/lib/testing/playwright/flaky-network-proxy.js +164 -0
- package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
- package/lib/testing/playwright/index.d.ts +1 -0
- package/lib/testing/playwright/index.d.ts.map +1 -1
- package/lib/testing/playwright/index.js +1 -0
- package/lib/testing/playwright/index.js.map +1 -1
- package/lib/testing/playwright/server-log-capture.d.ts +46 -16
- package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
- package/lib/testing/playwright/server-log-capture.js +111 -40
- package/lib/testing/playwright/server-log-capture.js.map +1 -1
- package/lib/testing/playwright/server-log-fixture.d.ts +16 -0
- package/lib/testing/playwright/server-log-fixture.d.ts.map +1 -1
- package/lib/testing/playwright/server-log-fixture.js +25 -6
- package/lib/testing/playwright/server-log-fixture.js.map +1 -1
- package/lib/testing/playwright/server-log-region.d.ts +15 -0
- package/lib/testing/playwright/server-log-region.d.ts.map +1 -0
- package/lib/testing/playwright/server-log-region.js +30 -0
- package/lib/testing/playwright/server-log-region.js.map +1 -0
- package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
- package/lib/testing/playwright/server-log-rename-reporter.js +4 -3
- package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
- package/lib/testing/run-update-pipeline.d.ts +8 -5
- package/lib/testing/run-update-pipeline.d.ts.map +1 -1
- package/lib/testing/run-update-pipeline.js.map +1 -1
- package/lib/testing/stub-ast-document-manager.d.ts +11 -52
- package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
- package/lib/testing/stub-ast-document-manager.js +31 -62
- package/lib/testing/stub-ast-document-manager.js.map +1 -1
- package/lib/testing/stub-document-builder.d.ts +33 -11
- package/lib/testing/stub-document-builder.d.ts.map +1 -1
- package/lib/testing/stub-document-builder.js +103 -5
- package/lib/testing/stub-document-builder.js.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.d.ts +33 -10
- package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
- package/lib/testing/stub-hydranium-text-documents.js +132 -16
- package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
- package/lib/testing/stub-index-manager.js.map +1 -1
- package/lib/testing/stub-langium-documents.d.ts +3 -2
- package/lib/testing/stub-langium-documents.d.ts.map +1 -1
- package/lib/testing/stub-langium-documents.js.map +1 -1
- package/lib/testing/stub-model-service.d.ts +9 -8
- package/lib/testing/stub-model-service.d.ts.map +1 -1
- package/lib/testing/stub-model-service.js +6 -6
- package/lib/testing/stub-model-service.js.map +1 -1
- package/lib/testing/stub-project-manager.js.map +1 -1
- package/lib/testing/stub-self-save-registry.d.ts +1 -1
- package/lib/testing/stub-self-save-registry.d.ts.map +1 -1
- package/lib/testing/stub-service-registry.d.ts +1 -1
- package/lib/testing/stub-service-registry.js.map +1 -1
- package/lib/testing/stub-writable-file-system.d.ts +1 -1
- package/lib/testing/stub-writable-file-system.d.ts.map +1 -1
- package/lib/testing/stub-writable-file-system.js +2 -1
- package/lib/testing/stub-writable-file-system.js.map +1 -1
- package/lib/util/connection-liveness.d.ts +22 -6
- package/lib/util/connection-liveness.d.ts.map +1 -1
- package/lib/util/connection-liveness.js +74 -15
- package/lib/util/connection-liveness.js.map +1 -1
- package/lib/util/environment.d.ts +7 -0
- package/lib/util/environment.d.ts.map +1 -1
- package/lib/util/environment.js +15 -1
- package/lib/util/environment.js.map +1 -1
- package/lib/util/registry.d.ts.map +1 -1
- package/package.json +41 -60
- package/src/browser/host-heads-in-worker.ts +218 -0
- package/src/browser/index.ts +17 -0
- package/src/browser/indexeddb-file-system-store.ts +192 -0
- package/src/browser/start-language-server-in-worker.ts +56 -0
- package/src/documents/ast-document-manager.ts +479 -233
- package/src/documents/client-ids.ts +5 -22
- package/src/documents/client-session-errors.ts +24 -0
- package/src/documents/client-session-registry.ts +262 -0
- package/src/documents/dirty-state-tracker.ts +167 -0
- package/src/documents/document-release-handler.ts +404 -0
- package/src/documents/document-release-scheduler.ts +121 -0
- package/src/documents/file-system-task-queue.ts +74 -0
- package/src/documents/hydranium-text-documents.ts +1029 -595
- package/src/documents/index.ts +10 -2
- package/src/documents/language-client-shadow.ts +501 -0
- package/src/documents/model-ledger.ts +66 -0
- package/src/documents/text-ledger.ts +112 -0
- package/src/documents/version-sync-service.ts +275 -0
- package/src/index.ts +7 -0
- package/src/langium/ast-extension/ast-extension-service.ts +77 -41
- package/src/langium/ast-extension/ast-node-builder.ts +35 -13
- package/src/langium/bootstrap.ts +87 -51
- package/src/langium/build-phase-pass/build-phase-pass-service.ts +29 -19
- package/src/langium/build-phase-pass/build-phase-pass.ts +37 -8
- package/src/langium/composite-ast-reflection.ts +112 -0
- package/src/langium/config/configuration-provider.ts +119 -0
- package/src/langium/config/index.ts +1 -0
- package/src/langium/diagnostics/console-tee.ts +151 -0
- package/src/langium/diagnostics/format-log-arg.ts +36 -0
- package/src/langium/diagnostics/hydranium-langium-profiler.ts +15 -39
- package/src/langium/diagnostics/log-preamble.ts +1 -1
- package/src/langium/diagnostics/logger.ts +51 -17
- package/src/langium/diagnostics/lsp-logger.ts +83 -44
- package/src/langium/document-builder/build-pipeline-integration.ts +46 -32
- package/src/langium/document-builder/build-session.ts +4 -3
- package/src/langium/document-builder/document-builder.ts +615 -79
- package/src/langium/integration-services.ts +59 -28
- package/src/langium/integrity/integrity-rule.ts +17 -8
- package/src/langium/integrity/integrity-service.ts +332 -69
- package/src/langium/language-module.ts +49 -8
- package/src/langium/model-service/client-session.ts +726 -0
- package/src/langium/model-service/index.ts +2 -0
- package/src/langium/model-service/model-events.ts +80 -0
- package/src/langium/model-service/model-service.ts +878 -494
- package/src/langium/module.ts +195 -72
- package/src/langium/naming/name-provider.ts +34 -8
- package/src/langium/project/abstract-project-manager.ts +13 -5
- package/src/langium/project/project-change-event.ts +2 -2
- package/src/langium/residency/cst-residency-service.ts +54 -19
- package/src/langium/scope/fallback-scope.ts +38 -0
- package/src/langium/scope/hydranium-scope-computation.ts +45 -44
- package/src/langium/scope/hydranium-scope-provider.ts +228 -41
- package/src/langium/scope/index.ts +1 -0
- package/src/langium/scope/reference-candidate-provider.ts +6 -5
- package/src/langium/scope/scope-extension-service.ts +148 -56
- package/src/langium/scope/scoped-ast-node-description.ts +1 -3
- package/src/langium/serialization/abstract-serializer.ts +57 -41
- package/src/langium/serialization/json-serializer.ts +14 -3
- package/src/langium/serialization/serializer.ts +1 -1
- package/src/langium/serialization/yaml-serializer.ts +26 -4
- package/src/langium/service-registry.ts +41 -2
- package/src/langium/shared-services.ts +16 -7
- package/src/langium/single-langium-copy.ts +42 -0
- package/src/langium/transfer/transfer-encoder.ts +104 -47
- package/src/langium/trivia/comment-preserver.ts +1409 -0
- package/src/langium/trivia/document-ending-preserver.ts +71 -0
- package/src/langium/trivia/index.ts +14 -0
- package/src/langium/trivia/trivia-contribution.ts +39 -0
- package/src/langium/trivia/trivia-preserver.ts +56 -0
- package/src/langium/trivia/trivia-service.ts +99 -0
- package/src/langium/update-rewrite/update-rewrite-service.ts +1 -1
- package/src/langium/update-rewrite/update-rewrite.ts +1 -1
- package/src/langium/validation/document-validator.ts +312 -31
- package/src/langium/validation/validation-contribution-collector.ts +10 -1
- package/src/langium/workspace/document-uri-policy.ts +32 -18
- package/src/langium/workspace/file-not-found.ts +35 -0
- package/src/langium/workspace/file-system-provider.ts +258 -16
- package/src/langium/workspace/hydranium-langium-document-factory.ts +83 -0
- package/src/langium/workspace/hydranium-workspace-lock.ts +86 -2
- package/src/langium/workspace/hydranium-workspace-manager.ts +212 -35
- package/src/langium/workspace/in-memory-file-system-provider.ts +54 -23
- package/src/langium/workspace/index.ts +1 -0
- package/src/langium/workspace/initialize-workspace.ts +96 -4
- package/src/langium/workspace/langium-documents.ts +19 -1
- package/src/langium/workspace/persistent-file-system-provider.ts +27 -15
- package/src/langium/workspace/self-save-registry.ts +118 -0
- package/src/langium/workspace/virtual-document.ts +106 -25
- package/src/langium/workspace/write-lock-scope.ts +27 -12
- package/src/launcher/integrated-server.ts +3 -1
- package/src/locale/server-locale.ts +20 -5
- package/src/lsp/completion/hydranium-completion-provider.ts +82 -2
- package/src/lsp/connection-features.ts +67 -0
- package/src/lsp/diagnostics-connection.ts +56 -0
- package/src/lsp/edit-requests-guard.ts +152 -0
- package/src/lsp/hydranium-document-update-handler.ts +285 -111
- package/src/lsp/index.ts +6 -2
- package/src/lsp/lsp-latency.ts +62 -0
- package/src/lsp/save-text-document.ts +63 -0
- package/src/lsp/semantic-token-provider.ts +146 -8
- package/src/lsp/shared-module.ts +4 -8
- package/src/lsp/start-language-server.ts +21 -7
- package/src/messages/carriers.ts +4 -6
- package/src/messages/index.ts +11 -2
- package/src/messages/renderer.ts +34 -6
- package/src/node/event-loop-monitor.ts +2 -1
- package/src/node/heap-ceiling.ts +108 -0
- package/src/node/index.ts +5 -1
- package/src/node/latency-from-env.ts +1 -1
- package/src/node/log-file-sink.ts +6 -2
- package/src/node/log-preamble-node.ts +20 -10
- package/src/node/measure-memory.ts +5 -4
- package/src/node/memory-monitor.ts +10 -10
- package/src/node/node-file-system-provider.ts +214 -57
- package/src/node/profile-capture.ts +16 -15
- package/src/node/profiling-run.ts +30 -8
- package/src/node/rename-over-open-readers.ts +94 -0
- package/src/node/server-diagnostics.ts +1 -1
- package/src/node/server-state-snapshot.ts +5 -5
- package/src/node/socket-launcher.ts +78 -14
- package/src/node/startup-cpu-profile.ts +94 -0
- package/src/node/stdio-launcher.ts +29 -19
- package/src/node/validate-workspace.ts +8 -2
- package/src/testing/document-uri-policy-conformance.ts +1 -1
- package/src/testing/fake-document.ts +2 -1
- package/src/testing/index.ts +2 -1
- package/src/testing/langium-test-helpers.ts +1 -0
- package/src/testing/make-noop-shared-services.ts +6 -6
- package/src/testing/make-test-services.ts +93 -41
- package/src/testing/make-test-tracer.ts +26 -0
- package/src/testing/node/applied-edit-capture.ts +141 -0
- package/src/testing/node/index.ts +1 -0
- package/src/testing/node/lsp-harness.ts +2 -0
- package/src/testing/node/lsp-server-connection.ts +83 -119
- package/src/testing/node/scratch-workspace.ts +10 -5
- package/src/testing/node/spawned-server.ts +57 -3
- package/src/testing/node/unhandled-rejections.ts +31 -0
- package/src/testing/parse-semantic-root.ts +1 -1
- package/src/testing/playwright/flaky-network-proxy.ts +240 -0
- package/src/testing/playwright/index.ts +1 -0
- package/src/testing/playwright/server-log-capture.ts +137 -47
- package/src/testing/playwright/server-log-fixture.ts +46 -6
- package/src/testing/playwright/server-log-region.ts +32 -0
- package/src/testing/playwright/server-log-rename-reporter.ts +4 -3
- package/src/testing/run-update-pipeline.ts +8 -5
- package/src/testing/stub-ast-document-manager.ts +47 -128
- package/src/testing/stub-document-builder.ts +138 -18
- package/src/testing/stub-hydranium-text-documents.ts +184 -27
- package/src/testing/stub-langium-documents.ts +3 -2
- package/src/testing/stub-model-service.ts +11 -10
- package/src/testing/stub-self-save-registry.ts +1 -1
- package/src/testing/stub-service-registry.ts +1 -1
- package/src/testing/stub-writable-file-system.ts +3 -2
- package/src/util/connection-liveness.ts +80 -15
- package/src/util/environment.ts +24 -2
- package/lib/documents/language-client-text-shadow.d.ts +0 -101
- package/lib/documents/language-client-text-shadow.d.ts.map +0 -1
- package/lib/documents/language-client-text-shadow.js +0 -156
- package/lib/documents/language-client-text-shadow.js.map +0 -1
- package/lib/documents/self-save-registry.d.ts +0 -55
- package/lib/documents/self-save-registry.d.ts.map +0 -1
- package/lib/documents/self-save-registry.js.map +0 -1
- package/lib/lsp/instrument-connection.d.ts +0 -36
- package/lib/lsp/instrument-connection.d.ts.map +0 -1
- package/lib/lsp/instrument-connection.js +0 -62
- package/lib/lsp/instrument-connection.js.map +0 -1
- package/lib/node/process-memory.d.ts +0 -66
- package/lib/node/process-memory.d.ts.map +0 -1
- package/lib/node/process-memory.js +0 -252
- package/lib/node/process-memory.js.map +0 -1
- package/src/documents/language-client-text-shadow.ts +0 -175
- package/src/documents/self-save-registry.ts +0 -81
- package/src/lsp/instrument-connection.ts +0 -66
- package/src/node/process-memory.ts +0 -299
|
@@ -19,11 +19,17 @@
|
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
20
20
|
import { NormalizedTextDocuments } from '@hydranium/langium/lsp';
|
|
21
21
|
import { UriUtils } from '@hydranium/langium';
|
|
22
|
-
import { OptionalVersionedTextDocumentIdentifier, TextDocumentEdit, TextDocumentSyncKind } from 'vscode-languageserver';
|
|
22
|
+
import { Emitter, OptionalVersionedTextDocumentIdentifier, TextDocumentEdit, TextDocumentSyncKind } from 'vscode-languageserver';
|
|
23
23
|
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
24
|
-
import { asLanguageClientUri, DisposableCollection } from '@hydranium/protocol';
|
|
24
|
+
import { asLanguageClientUri, asModelVersion, ConflictError, DisposableCollection, DocumentNotOpenError, STALE_BASE_VERSION_SAVE, STALE_VERSION, UNRECORDED_VERSION, describeError, describeErrorWithStack } from '@hydranium/protocol';
|
|
25
25
|
import { LANGUAGE_CLIENT_ID } from './client-ids.js';
|
|
26
|
-
import {
|
|
26
|
+
import { INTEGRITY_CLIENT_ID } from '../langium/integrity/integrity-rule.js';
|
|
27
|
+
import { ClientSessionRegistry } from './client-session-registry.js';
|
|
28
|
+
import { DefaultLanguageClientShadow } from './language-client-shadow.js';
|
|
29
|
+
import { DefaultTextLedger } from './text-ledger.js';
|
|
30
|
+
import { isDocumentReleaseSkippedError } from './document-release-handler.js';
|
|
31
|
+
import { DefaultDocumentReleaseScheduler } from './document-release-scheduler.js';
|
|
32
|
+
import { DefaultDirtyStateTracker } from './dirty-state-tracker.js';
|
|
27
33
|
/**
|
|
28
34
|
* The LSP spec's "version is intentionally unknown" for an
|
|
29
35
|
* {@link OptionalVersionedTextDocumentIdentifier} is `null`, but
|
|
@@ -33,141 +39,124 @@ import { isFullReplace, LanguageClientTextShadow } from './language-client-text-
|
|
|
33
39
|
*/
|
|
34
40
|
const UNKNOWN_CLIENT_VERSION = null;
|
|
35
41
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* of leaking.
|
|
42
|
+
* The default of {@link HydraniumTextDocumentsOptions.releaseGraceMs}: long
|
|
43
|
+
* enough for a client whose connection dropped to register again and reopen
|
|
44
|
+
* its documents, which a data client does as soon as it has a connection.
|
|
40
45
|
*/
|
|
41
|
-
const
|
|
46
|
+
const DEFAULT_RELEASE_GRACE_MS = 10_000;
|
|
42
47
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
h1 = Math.imul(h1 ^ code, 2654435761);
|
|
56
|
-
h2 = Math.imul(h2 ^ code, 1597334677);
|
|
57
|
-
}
|
|
58
|
-
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
59
|
-
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
60
|
-
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
61
|
-
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
62
|
-
return `${(4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36)}:${text.length}`;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Multi-client text-document tracking on top of Langium's `NormalizedTextDocuments`.
|
|
48
|
+
* The one text store every head writes to, on top of Langium's
|
|
49
|
+
* `NormalizedTextDocuments`, and the LSP text-sync endpoint: every
|
|
50
|
+
* `textDocument/*` notification and every `workspace/applyEdit` push goes
|
|
51
|
+
* through here.
|
|
52
|
+
*
|
|
53
|
+
* Each open, change, close and save is one synchronous transition over the
|
|
54
|
+
* held document and the collaborators its `create…` methods build. A
|
|
55
|
+
* collaborator that defers its part lets a listener of the transition's event
|
|
56
|
+
* read state from before it. A document no client holds any more is
|
|
57
|
+
* released to the `DocumentReleaseHandler` slot, after
|
|
58
|
+
* {@link HydraniumTextDocumentsOptions.releaseGraceMs} when its last client's
|
|
59
|
+
* connection was lost.
|
|
66
60
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* - Pending-content staging used by the integrity service to thread corrections
|
|
78
|
-
* through `workspace/applyEdit` cycles for currently-closed documents.
|
|
79
|
-
* - `didOpen` notifications arriving over the LSP connection wait on the
|
|
80
|
-
* workspace-ready promise, so a client's first open cannot race workspace
|
|
81
|
-
* discovery. Direct {@link notifyDidOpenTextDocument} calls (the non-LSP
|
|
82
|
-
* heads) do not pass that gate — their caller owns the ordering.
|
|
61
|
+
* Client-declared version ids feed only the per-client staleness guard and
|
|
62
|
+
* never a running shared sequence: the two count different things, and
|
|
63
|
+
* splicing them lets versions drift past base-version gate holders. A URI
|
|
64
|
+
* with no ledger record and no built root starts at its opener's declared id:
|
|
65
|
+
* no version was handed out for it.
|
|
66
|
+
*
|
|
67
|
+
* `didOpen` notifications arriving over the LSP connection wait on the
|
|
68
|
+
* workspace-ready promise, so a client's first open cannot race workspace
|
|
69
|
+
* discovery. Direct {@link notifyDidOpenTextDocument} calls (the non-LSP
|
|
70
|
+
* heads) do not pass that gate — their caller owns the ordering.
|
|
83
71
|
*/
|
|
84
72
|
export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
85
73
|
services;
|
|
74
|
+
options;
|
|
75
|
+
/** Content staged by integrity rules for a document no client holds, consumed by its first open. */
|
|
76
|
+
__pendingContent = new Map();
|
|
86
77
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* {@link __shadow} (its own abstraction); the parsed-document store stays in
|
|
92
|
-
* the inherited `__syncedDocuments`.
|
|
93
|
-
*/
|
|
94
|
-
__documents = new Map();
|
|
95
|
-
/**
|
|
96
|
-
* Per-URI shared-version continuity across close/reopen cycles
|
|
97
|
-
* ({@link VersionSequence}), written at last-client close and consulted by
|
|
98
|
-
* the next first-client open. DELIBERATELY outside {@link DocumentTracking}:
|
|
99
|
-
* that record is deleted on last close, while the version sequence must
|
|
100
|
-
* survive it — the shared version is a server-owned, monotonic,
|
|
101
|
-
* advances-iff-content-changes counter that never resets while the server
|
|
102
|
-
* lives. That invariant is what makes an optimistic `baseVersion` gate
|
|
103
|
-
* sound: "version unchanged ⇔ content unchanged", with no false conflicts
|
|
104
|
-
* from close/reopen version resets and no false passes from a reopened
|
|
105
|
-
* sequence coincidentally landing on a stale writer's number.
|
|
106
|
-
*
|
|
107
|
-
* Never pruned: two small values per URI ever touched — bounded by
|
|
108
|
-
* workspace size, not by activity.
|
|
109
|
-
*/
|
|
110
|
-
__versionSequences = new Map();
|
|
111
|
-
/**
|
|
112
|
-
* Texts pushed to the LSP textual language client via
|
|
113
|
-
* {@link applyEditToLanguageClient} whose echoes have not come back yet
|
|
114
|
-
* ({@link PendingPush}), keyed like the shadow by language-client URI.
|
|
115
|
-
* Outbound pushes and inbound echoes are uncorrelated on the wire; this
|
|
116
|
-
* FIFO is the explicit correlation, and it is what
|
|
117
|
-
* {@link classifyLanguageClientChange} reconstructs against.
|
|
118
|
-
*
|
|
119
|
-
* **Each entry keeps the client's PRE-push text, not only a hash of the
|
|
120
|
-
* post-push one.** A hash alone can classify a full-text echo, whose
|
|
121
|
-
* application is a no-op either way — and that is all it ever classified,
|
|
122
|
-
* because a conforming client echoes INCREMENTAL ranges keyed to its
|
|
123
|
-
* previous buffer. Those ranges cannot be applied to the synced text (which
|
|
124
|
-
* the authored write already advanced) and cannot be reconstructed without
|
|
125
|
-
* the baseline: the line they insert lands twice, validates cleanly, and
|
|
126
|
-
* compounds on every later edit.
|
|
127
|
-
*
|
|
128
|
-
* Lifecycle: entries are consumed by the matching echo (together with any
|
|
129
|
-
* older entries it supersedes), and the whole queue drops when the client
|
|
130
|
-
* stops being a pure mirror — a divergent change, an `applyEdit`
|
|
131
|
-
* failure/rejection (shadow invalidation), a close, or an explicit
|
|
132
|
-
* shadow (re)baseline. {@link PENDING_ECHO_CAP} bounds the queue against
|
|
133
|
-
* a pathological echo that never arrives; the memory cost until then is
|
|
134
|
-
* one pre-push text per in-flight push, for milliseconds.
|
|
135
|
-
*/
|
|
136
|
-
__pendingPushes = new Map();
|
|
137
|
-
/**
|
|
138
|
-
* Tracked text content per URI for the LSP textual language client (Monaco / VS Code).
|
|
139
|
-
* Owned here so {@link applyEditToLanguageClient} can compute minimal `workspace/applyEdit`
|
|
140
|
-
* diffs instead of full-document replaces (5–20 s → <500 ms on 20-30 KB YAML diagrams).
|
|
141
|
-
*
|
|
142
|
-
* Auto-tracked from the multi-client text-document events: open / change / close of
|
|
143
|
-
* the language client (re)baseline or invalidate the shadow. Other client ids
|
|
144
|
-
* (form editor, GLSP, integrity) do NOT touch the shadow — only what Monaco believes
|
|
145
|
-
* it has matters for the diff.
|
|
146
|
-
*
|
|
147
|
-
* Apply-verify safety net is built in: if the diff doesn't reconstruct `newText`
|
|
148
|
-
* exactly, {@link LanguageClientTextShadow.computeEdits} falls back to a full-range
|
|
149
|
-
* replace and invokes the `onFallback` callback — a diff regression becomes log
|
|
150
|
-
* noise, not a 0-byte save.
|
|
151
|
-
*
|
|
152
|
-
* Assigned in the constructor body so the `onFallback` callback can capture
|
|
153
|
-
* `this.logger` after the parameter-property assignment has run (field
|
|
154
|
-
* initializers fire BEFORE parameter-property assignment in TS).
|
|
78
|
+
* Which client has which document open, which client ids are registered
|
|
79
|
+
* sessions, and each client's declared version, the staleness guard's
|
|
80
|
+
* baseline. Every open-state predicate on this class reads it, so an open
|
|
81
|
+
* recorded anywhere else is invisible to the last-close transition.
|
|
155
82
|
*/
|
|
156
|
-
|
|
83
|
+
__sessions = new ClientSessionRegistry();
|
|
84
|
+
/** The version each open document was opened at; see {@link openedVersion}. */
|
|
85
|
+
__openedVersions = new Map();
|
|
157
86
|
tracer;
|
|
158
87
|
configuration;
|
|
88
|
+
__textLedger;
|
|
89
|
+
__languageClientShadow;
|
|
90
|
+
__dirtyStateTracker;
|
|
91
|
+
__documentReleaseScheduler;
|
|
92
|
+
documentReleasedEmitter = new Emitter();
|
|
93
|
+
languageClientSavedEmitter = new Emitter();
|
|
159
94
|
constructor(services, options = {}) {
|
|
160
95
|
const configuration = options.configuration ?? TextDocument;
|
|
161
96
|
super(configuration);
|
|
162
97
|
this.services = services;
|
|
98
|
+
this.options = options;
|
|
163
99
|
this.configuration = configuration;
|
|
164
100
|
this.tracer = services.Tracer.for(options.logName ?? 'TextDocuments').trace('instantiated');
|
|
165
|
-
this.__shadow = new LanguageClientTextShadow((uri, reason) => this.tracer.with(uri).warn(`Diff apply-verify fallback (${reason}) — using full-document replace`), this);
|
|
166
101
|
}
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
102
|
+
// Each collaborator is built on first use, after every constructor has run,
|
|
103
|
+
// so a create method may read its subclass's fields and the other collaborators.
|
|
104
|
+
get textLedger() {
|
|
105
|
+
return (this.__textLedger ??= this.createTextLedger());
|
|
106
|
+
}
|
|
107
|
+
get languageClientShadow() {
|
|
108
|
+
return (this.__languageClientShadow ??= this.createLanguageClientShadow());
|
|
109
|
+
}
|
|
110
|
+
get dirtyStateTracker() {
|
|
111
|
+
return (this.__dirtyStateTracker ??= this.createDirtyStateTracker());
|
|
112
|
+
}
|
|
113
|
+
get documentReleaseScheduler() {
|
|
114
|
+
return (this.__documentReleaseScheduler ??= this.createDocumentReleaseScheduler());
|
|
115
|
+
}
|
|
116
|
+
createTextLedger() {
|
|
117
|
+
return new DefaultTextLedger();
|
|
118
|
+
}
|
|
119
|
+
createLanguageClientShadow() {
|
|
120
|
+
return new DefaultLanguageClientShadow(this, this.tracer);
|
|
121
|
+
}
|
|
122
|
+
createDirtyStateTracker() {
|
|
123
|
+
return new DefaultDirtyStateTracker(this.textLedger);
|
|
124
|
+
}
|
|
125
|
+
createDocumentReleaseScheduler() {
|
|
126
|
+
return new DefaultDocumentReleaseScheduler(this.services.Clock, this.options.releaseGraceMs ?? DEFAULT_RELEASE_GRACE_MS);
|
|
127
|
+
}
|
|
128
|
+
/** Hold `document` as the text of `key`, a new version authored by `author`. */
|
|
129
|
+
commitText(key, document, author) {
|
|
130
|
+
this.__syncedDocuments.set(key, document);
|
|
131
|
+
this.setAuthor(key, document.version, author);
|
|
132
|
+
this.dirtyStateTracker.refreshDirty(key, document);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Apply `changes` to `document`, the held text of `key`, and hold the
|
|
136
|
+
* result. The version steps only when the text changes, which is what a
|
|
137
|
+
* base-version gate relies on: unchanged text keeps its version and its
|
|
138
|
+
* author. The new text is known only once the changes are applied, so they
|
|
139
|
+
* go in at a tentative step that an identical result rolls back.
|
|
140
|
+
*/
|
|
141
|
+
commitChange(key, document, changes, author) {
|
|
142
|
+
const previousText = document.getText();
|
|
143
|
+
const version = document.version;
|
|
144
|
+
let next = this.update(document, changes, version + 1);
|
|
145
|
+
const changed = next.getText() !== previousText;
|
|
146
|
+
if (changed) {
|
|
147
|
+
this.commitText(key, next, author);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// An empty-changes update only re-stamps the version.
|
|
151
|
+
next = this.update(next, [], version);
|
|
152
|
+
this.__syncedDocuments.set(key, next);
|
|
153
|
+
}
|
|
154
|
+
return { document: next, changed };
|
|
155
|
+
}
|
|
156
|
+
// The configuration's factories. Every document the store makes or changes
|
|
157
|
+
// goes through them, so an override sees each call: the store's own writes,
|
|
158
|
+
// callers outside the didOpen/didChange flow, and the shadow's throwaway
|
|
159
|
+
// probes, at version 0 under a client URI.
|
|
171
160
|
create(uri, languageId, version, content) {
|
|
172
161
|
return this.configuration.create(uri, languageId, version, content);
|
|
173
162
|
}
|
|
@@ -221,7 +210,7 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
221
210
|
connection.__textDocumentSync = TextDocumentSyncKind.Incremental;
|
|
222
211
|
const disposables = new DisposableCollection();
|
|
223
212
|
disposables.push(connection.onDidOpenTextDocument(async (event) => {
|
|
224
|
-
await this.
|
|
213
|
+
await this.initialBuildFinished();
|
|
225
214
|
this.notifyDidOpenTextDocument(event);
|
|
226
215
|
}));
|
|
227
216
|
disposables.push(connection.onDidChangeTextDocument((event) => {
|
|
@@ -235,7 +224,7 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
235
224
|
}));
|
|
236
225
|
disposables.push(connection.onWillSaveTextDocumentWaitUntil((event, token) => this.notifyWillSaveTextDocumentWaitUntil(event, token)));
|
|
237
226
|
disposables.push(connection.onDidSaveTextDocument((event) => {
|
|
238
|
-
this.
|
|
227
|
+
void this.notifyLanguageClientSave(event);
|
|
239
228
|
}));
|
|
240
229
|
return disposables;
|
|
241
230
|
}
|
|
@@ -252,80 +241,58 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
252
241
|
const uri = this.documentKey(td.uri);
|
|
253
242
|
let document = this.__syncedDocuments.get(uri);
|
|
254
243
|
if (document !== undefined) {
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
244
|
+
// Client version ids are the client's own, so compared against what
|
|
245
|
+
// that client declared, never against the shared version: an authored
|
|
246
|
+
// write advances that without the client knowing, and gating on it
|
|
247
|
+
// drops real edits. The editor is checked per URI, since each is its
|
|
248
|
+
// own buffer; its client-wide entry is only the fallback for a URI it
|
|
249
|
+
// never opened, and may name another URI's buffer. A client with no
|
|
250
|
+
// baseline falls back to the shared version.
|
|
251
|
+
const clientUri = this.toLanguageClientUri(td.uri);
|
|
252
|
+
const editor = clientId === LANGUAGE_CLIENT_ID;
|
|
253
|
+
const lastSeen = (editor ? this.languageClientShadow.declaredVersion(uri, clientUri) : undefined) ??
|
|
254
|
+
this.__sessions.clientVersionOf(uri, clientId) ??
|
|
255
|
+
document.version;
|
|
266
256
|
if (lastSeen >= td.version) {
|
|
267
257
|
// Distinguish "already at this version" (common: an echo from the client that triggered
|
|
268
258
|
// the update) from "incoming version older than ours" (stale race).
|
|
269
259
|
const reason = lastSeen === td.version ? `already at version ${lastSeen}` : `incoming version ${td.version} older than current ${lastSeen}`;
|
|
270
|
-
this.logUri(uri, `Ignore update by ${clientId}: ${reason}`, 'debug');
|
|
260
|
+
this.logUri(uri, `Ignore update by ${this.formatClientId(clientId)}: ${reason}`, 'debug');
|
|
271
261
|
return;
|
|
272
262
|
}
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
const
|
|
279
|
-
? this.
|
|
280
|
-
:
|
|
281
|
-
if (
|
|
282
|
-
// The
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
// counting); the shadow is deliberately NOT touched — it
|
|
287
|
-
// optimistically tracks the NEWEST pushed text, and dragging it
|
|
288
|
-
// back would make the next outbound diff wrong against what the
|
|
289
|
-
// client actually holds.
|
|
263
|
+
this.__sessions.setClientVersion(uri, clientId, td.version);
|
|
264
|
+
// An editor change is keyed to the buffer the editor holds, which is
|
|
265
|
+
// the synced text only while the two agree — an authored write
|
|
266
|
+
// advances the synced text without the editor knowing, and a push in
|
|
267
|
+
// flight moves the editor without the store knowing.
|
|
268
|
+
const verdict = editor
|
|
269
|
+
? this.languageClientShadow.acceptChange(uri, clientUri, td.version, document, changes)
|
|
270
|
+
: { kind: 'direct' };
|
|
271
|
+
if (verdict.kind === 'echo') {
|
|
272
|
+
// The synced document is already there, so the changes must not be
|
|
273
|
+
// applied a second time: nothing is minted and no rebuild fires. The
|
|
274
|
+
// shadow keeps the newest pushed text, which is what the next
|
|
275
|
+
// outbound diff has to be keyed to.
|
|
290
276
|
this.logUri(uri, `Skip rebuild: echo of a server-authored push (client version ${td.version})`, 'debug');
|
|
291
277
|
return;
|
|
292
278
|
}
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
document = this.configuration.update(document, inFlight === undefined ? changes : [{ text: inFlight.text }], sharedVersion + 1);
|
|
305
|
-
const changed = document.getText() !== previousText;
|
|
306
|
-
if (!changed) {
|
|
307
|
-
document = this.configuration.update(document, [], sharedVersion);
|
|
308
|
-
}
|
|
309
|
-
this.__syncedDocuments.set(uri, document);
|
|
310
|
-
if (changed) {
|
|
311
|
-
this.setAuthor(uri, document.version, clientId);
|
|
312
|
-
}
|
|
313
|
-
if (clientId === LANGUAGE_CLIENT_ID) {
|
|
314
|
-
// Monaco just told us about its new content; record it so the next outbound
|
|
315
|
-
// applyEditToLanguageClient diffs against the right baseline. Keyed by the
|
|
316
|
-
// language-client URI (what Monaco holds), not the canonical document key.
|
|
317
|
-
this.__shadow.set(this.toLanguageClientUri(td.uri), document.getText());
|
|
318
|
-
// Content-identical echo: the language client is echoing text we already had
|
|
319
|
-
// (e.g. Monaco re-emitting a server-pushed applyEditToLanguageClient). The model is
|
|
320
|
-
// unchanged, so skip the rebuild. The per-client baseline + shadow still advanced
|
|
321
|
-
// above so future staleness checks and diffs are correct. Restricted to the
|
|
322
|
-
// language client: a ModelService-authored change is never skipped.
|
|
323
|
-
if (!changed) {
|
|
279
|
+
// A divergent change is applied as its reconstructed text rather than
|
|
280
|
+
// as its own ranges: those ranges address the editor's own buffer, so
|
|
281
|
+
// applying them here would splice the wrong lines.
|
|
282
|
+
const committed = this.commitChange(uri, document, verdict.kind === 'direct' ? changes : [{ text: verdict.text }], clientId);
|
|
283
|
+
document = committed.document;
|
|
284
|
+
if (editor) {
|
|
285
|
+
this.languageClientShadow.setLanguageClientText(clientUri, document.getText(), document.version);
|
|
286
|
+
// Content-identical echo: the editor is echoing text we already had.
|
|
287
|
+
// The model is unchanged, so skip the rebuild. Restricted to the
|
|
288
|
+
// editor: a ModelService-authored change is never skipped.
|
|
289
|
+
if (!committed.changed) {
|
|
324
290
|
this.logUri(uri, `Skip rebuild: content unchanged (echo at client version ${td.version})`, 'debug');
|
|
325
291
|
return;
|
|
326
292
|
}
|
|
327
293
|
}
|
|
328
|
-
|
|
294
|
+
// Diagnosis: one comes with every keystroke.
|
|
295
|
+
this.logUri(document.uri, `Update to version ${document.version} by ${this.formatClientId(clientId)}${committed.changed ? '' : ' (content unchanged)'}`, 'debug');
|
|
329
296
|
this.__onDidChangeContent.fire(Object.freeze({ document, clientId }));
|
|
330
297
|
}
|
|
331
298
|
}
|
|
@@ -341,70 +308,175 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
341
308
|
* A content-identical write still fires the change event (rebuild): the
|
|
342
309
|
* authored write's server-side rebuild is correctness-bearing, it just
|
|
343
310
|
* mints no new version — nothing observable changed, so watchers'
|
|
344
|
-
*
|
|
311
|
+
* base versions stay valid.
|
|
345
312
|
*
|
|
346
313
|
* Returns the resulting shared version. Throws when the document is not
|
|
347
314
|
* open — callers (`AstDocumentManager.update`) open first.
|
|
348
315
|
*/
|
|
349
316
|
applyContentChange(uri, text, clientId) {
|
|
350
317
|
const key = this.documentKey(uri);
|
|
351
|
-
|
|
352
|
-
if (
|
|
318
|
+
const synced = this.__syncedDocuments.get(key);
|
|
319
|
+
if (synced === undefined) {
|
|
353
320
|
throw new Error(`Document ${uri} is not open for content changes`);
|
|
354
321
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
this.__syncedDocuments.set(key, document);
|
|
359
|
-
this.setAuthor(key, document.version, clientId);
|
|
360
|
-
}
|
|
361
|
-
this.log(document.uri, `Update to version ${document.version} by ${clientId}${changed ? '' : ' (content unchanged)'}`);
|
|
322
|
+
// Unchanged text calls no update: a configuration may return a new document for one.
|
|
323
|
+
const { document, changed } = synced.getText() === text ? { document: synced, changed: false } : this.commitChange(key, synced, [{ text }], clientId);
|
|
324
|
+
this.logUri(document.uri, `Update to version ${document.version} by ${this.formatClientId(clientId)}${changed ? '' : ' (content unchanged)'}`, 'debug');
|
|
362
325
|
this.__onDidChangeContent.fire(Object.freeze({ document, clientId }));
|
|
363
326
|
return document.version;
|
|
364
327
|
}
|
|
365
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Close `clientId`'s open of the document. When it was the last open, the
|
|
330
|
+
* document is released, at once or, for a `'lost'`
|
|
331
|
+
* close, after {@link HydraniumTextDocumentsOptions.releaseGraceMs}.
|
|
332
|
+
*/
|
|
333
|
+
notifyDidCloseTextDocument(event, clientId = LANGUAGE_CLIENT_ID, cause = 'closed') {
|
|
366
334
|
const uri = this.documentKey(event.textDocument.uri);
|
|
367
|
-
if (
|
|
335
|
+
if (clientId === LANGUAGE_CLIENT_ID) {
|
|
336
|
+
const clientUri = this.toLanguageClientUri(event.textDocument.uri);
|
|
337
|
+
// A close under a URI the editor never opened the document under ends its hold.
|
|
338
|
+
if (this.languageClientShadow.isOpen(uri, clientUri)) {
|
|
339
|
+
this.languageClientShadow.removeOpen(uri, clientUri);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
this.languageClientShadow.removeAllOpens(uri);
|
|
343
|
+
}
|
|
344
|
+
// Another URI still holds the document, so only this one's buffer goes.
|
|
345
|
+
if (this.languageClientShadow.isOpen(uri)) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!this.__sessions.removeOpen(uri, clientId)) {
|
|
368
350
|
return;
|
|
369
351
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
352
|
+
if (cause === 'lost') {
|
|
353
|
+
this.documentReleaseScheduler.recordLoss(uri, clientId);
|
|
354
|
+
}
|
|
373
355
|
const syncedDocument = this.__syncedDocuments.get(uri);
|
|
374
356
|
if (syncedDocument !== undefined) {
|
|
375
|
-
this.log(syncedDocument.uri, `Closed synced document: ${syncedDocument.version} by ${clientId}`);
|
|
357
|
+
this.log(syncedDocument.uri, `Closed synced document: ${syncedDocument.version} by ${this.formatClientId(clientId)}`);
|
|
376
358
|
this.__onDidClose.fire(Object.freeze({ document: syncedDocument, clientId }));
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this.__pendingPushes.delete(droppedUri);
|
|
359
|
+
if (!this.isOpenInAnyClient(uri)) {
|
|
360
|
+
if (cause === 'lost') {
|
|
361
|
+
this.deferRelease(uri);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
this.releaseDocument(uri);
|
|
365
|
+
}
|
|
385
366
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
contentHash: contentHash(syncedDocument.getText())
|
|
400
|
-
});
|
|
401
|
-
this.__syncedDocuments.delete(uri);
|
|
402
|
-
// One delete clears every per-URI axis (version history + any staged
|
|
403
|
-
// pending content) so a future open with the same URI starts fresh.
|
|
404
|
-
this.__documents.delete(uri);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Keep the document, text and all, for the release grace, then release it.
|
|
371
|
+
* The document stays in the store meanwhile, so a lost client that opens it
|
|
372
|
+
* again within its own grace attaches to it and finds its unsaved text
|
|
373
|
+
* rather than reading disk; see {@link resolveDeferredRelease} for any other
|
|
374
|
+
* open.
|
|
375
|
+
*/
|
|
376
|
+
deferRelease(uri) {
|
|
377
|
+
this.documentReleaseScheduler.defer(uri, () => {
|
|
378
|
+
if (!this.isOpenInAnyClient(uri)) {
|
|
379
|
+
this.releaseDocument(uri);
|
|
405
380
|
}
|
|
381
|
+
});
|
|
382
|
+
if (this.documentReleaseScheduler.isDeferred(uri)) {
|
|
383
|
+
this.log(uri, `No client left; release deferred for ${this.documentReleaseScheduler.graceMs} ms (connection lost)`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/** Resolve a deferred release of `uri` for an open by `clientId`: a release the scheduler decides on runs now. */
|
|
387
|
+
resolveDeferredRelease(uri, clientId) {
|
|
388
|
+
if (this.documentReleaseScheduler.resolveOpen(uri, clientId) === 'release') {
|
|
389
|
+
this.releaseDocument(uri);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Release now a document waiting out the grace, as its last close would have; one that is not waiting is left alone. */
|
|
393
|
+
releaseIfDeferred(uri) {
|
|
394
|
+
if (this.documentReleaseScheduler.isDeferred(uri)) {
|
|
395
|
+
this.documentReleaseScheduler.cancel(uri);
|
|
396
|
+
this.releaseDocument(uri);
|
|
406
397
|
}
|
|
407
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Drop the document no client has open any more, announce it on
|
|
401
|
+
* {@link onDidReleaseDocument}, then hand it to the
|
|
402
|
+
* `DocumentReleaseHandler` slot: its listeners, such as the update
|
|
403
|
+
* handler dropping a change it still holds back, act before any build the
|
|
404
|
+
* handler runs.
|
|
405
|
+
*/
|
|
406
|
+
releaseDocument(uri) {
|
|
407
|
+
const syncedDocument = this.__syncedDocuments.get(uri);
|
|
408
|
+
if (syncedDocument === undefined) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
this.log(syncedDocument.uri, `Remove synced document: ${syncedDocument.version} (no client left)`);
|
|
412
|
+
// The next open continues the sequence instead of restarting at the
|
|
413
|
+
// reopening client's declared id.
|
|
414
|
+
this.textLedger.record(uri, syncedDocument);
|
|
415
|
+
this.textLedger.clearAuthors(uri);
|
|
416
|
+
// Before the tracker's release, which forgets whether a file was behind it.
|
|
417
|
+
const released = this.toReleasedDocument(uri);
|
|
418
|
+
const cleanAnnouncement = this.dirtyStateTracker.release(uri);
|
|
419
|
+
this.__syncedDocuments.delete(uri);
|
|
420
|
+
this.__openedVersions.delete(uri);
|
|
421
|
+
this.__sessions.forgetClientVersions(uri);
|
|
422
|
+
// A stage is for a first open; one released unconsumed is stale.
|
|
423
|
+
this.__pendingContent.delete(uri);
|
|
424
|
+
this.documentReleaseScheduler.clearLosses(uri);
|
|
425
|
+
this.documentReleasedEmitter.fire(Object.freeze({ uri }));
|
|
426
|
+
this.handOverRelease(released, cleanAnnouncement);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Call the `DocumentReleaseHandler` slot, and announce a document released
|
|
430
|
+
* dirty clean once the promise it returns settles, which is after the
|
|
431
|
+
* release event. A failure is logged rather than thrown: the store has let
|
|
432
|
+
* go of the document by now, and a throw would abort the transition that
|
|
433
|
+
* released it, a session's close of its other documents included.
|
|
434
|
+
*/
|
|
435
|
+
handOverRelease(released, cleanAnnouncement) {
|
|
436
|
+
let settled;
|
|
437
|
+
try {
|
|
438
|
+
// Read here: a lazily built slot whose factory throws throws on this read.
|
|
439
|
+
const handler = this.services.workspace.DocumentReleaseHandler;
|
|
440
|
+
settled =
|
|
441
|
+
handler === undefined
|
|
442
|
+
? Promise.reject(new Error('no workspace.DocumentReleaseHandler bound'))
|
|
443
|
+
: Promise.resolve(handler.didReleaseDocument(released));
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
settled = Promise.reject(err);
|
|
447
|
+
}
|
|
448
|
+
// Names the text the build holds at the settle, so it follows anything a
|
|
449
|
+
// release listener did meanwhile: none when its build failed or it no
|
|
450
|
+
// longer has the document, since a removal leaves the record on the
|
|
451
|
+
// discarded text.
|
|
452
|
+
const announceClean = (built) => {
|
|
453
|
+
if (cleanAnnouncement?.isOwed()) {
|
|
454
|
+
const inBuild = built && this.services.workspace.LangiumDocuments.getDocument(UriUtils.toUri(released.uri)) !== undefined;
|
|
455
|
+
cleanAnnouncement.announce(inBuild ? this.textState(released.uri) : undefined);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
settled.then(() => announceClean(true), (err) => {
|
|
459
|
+
// A release skipped at teardown, its peer or its workspace gone, is
|
|
460
|
+
// routine, not a fault to investigate.
|
|
461
|
+
if (isDocumentReleaseSkippedError(err)) {
|
|
462
|
+
this.tracer.with(released.uri).debug(err.message);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
this.tracer.with(released.uri).error(`Release handler failed. ${describeErrorWithStack(err)}`);
|
|
466
|
+
}
|
|
467
|
+
// Left unsettled, a watcher keeps the document dirty though the store answers clean.
|
|
468
|
+
announceClean(false);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
/** `uri` as the `DocumentReleaseHandler` slot receives it. */
|
|
472
|
+
toReleasedDocument(uri) {
|
|
473
|
+
return {
|
|
474
|
+
uri,
|
|
475
|
+
wasOnDisk: this.dirtyStateTracker.hasDiskBaseline(uri),
|
|
476
|
+
isFor: other => this.documentKey(other) === uri,
|
|
477
|
+
isReclaimed: () => this.isOpenInAnyClient(uri) || this.__syncedDocuments.has(uri)
|
|
478
|
+
};
|
|
479
|
+
}
|
|
408
480
|
notifyWillSaveTextDocument(event) {
|
|
409
481
|
const syncedDocument = this.__syncedDocuments.get(this.documentKey(event.textDocument.uri));
|
|
410
482
|
if (syncedDocument !== undefined) {
|
|
@@ -420,86 +492,172 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
420
492
|
return [];
|
|
421
493
|
}
|
|
422
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Handle a save the language client reports: fire
|
|
497
|
+
* {@link onDidSaveInLanguageClient}, then announce it on {@link onDidSave}
|
|
498
|
+
* only once the file read back from disk holds the store's current text.
|
|
499
|
+
*/
|
|
500
|
+
async notifyLanguageClientSave(event) {
|
|
501
|
+
const syncedDocument = this.__syncedDocuments.get(this.documentKey(event.textDocument.uri));
|
|
502
|
+
if (syncedDocument === undefined) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const uri = syncedDocument.uri;
|
|
506
|
+
this.languageClientSavedEmitter.fire(Object.freeze({ uri }));
|
|
507
|
+
// `onDidSave` tells its listeners that the shared document is on disk.
|
|
508
|
+
// The editor wrote its own buffer, which lags the store while another
|
|
509
|
+
// client's edit is still on its way to it, so only the file says
|
|
510
|
+
// whether that holds. Read it rather than trust the editor: `didSave`
|
|
511
|
+
// carries text only for a client that honours `includeText`, and asking
|
|
512
|
+
// for it means rewriting Langium's initialize answer, which advertises
|
|
513
|
+
// `save` as a Boolean. The read goes through the disk queue and waits on
|
|
514
|
+
// nothing but the provider, so it follows a server save queued
|
|
515
|
+
// meanwhile. A file changed again between the editor's write and this
|
|
516
|
+
// read compares unequal, and the save goes unannounced.
|
|
517
|
+
let onDisk;
|
|
518
|
+
try {
|
|
519
|
+
onDisk = await this.services.workspace.FileSystemTaskQueue.enqueue(uri, () => this.services.workspace.FileSystemProvider.readFile(UriUtils.toUri(uri)));
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
this.tracer.with(uri).debug(`Editor save not announced: reading the file back failed. ${describeError(err)}`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
// What the file holds, not what the editor meant to write: an editor
|
|
526
|
+
// that saved older text leaves the document dirty.
|
|
527
|
+
this.setDiskBaseline(uri, onDisk);
|
|
528
|
+
// An editor that saves and then closes releases the document while the
|
|
529
|
+
// read is under way; its save is still a save of the text it held.
|
|
530
|
+
const document = this.__syncedDocuments.get(this.documentKey(uri)) ?? syncedDocument;
|
|
531
|
+
if (onDisk !== document.getText()) {
|
|
532
|
+
this.tracer.with(uri).debug(`Editor save not announced: the file does not hold the text of version ${document.version}`);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
this.announceSave(document, LANGUAGE_CLIENT_ID);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Announce a save the server made. `event.text`, when given, is what the
|
|
539
|
+
* file now holds, written or found there, and becomes the disk baseline
|
|
540
|
+
* before the save is announced. It lags the store when an edit landed
|
|
541
|
+
* after the save took its text, and the document then stays dirty.
|
|
542
|
+
*/
|
|
423
543
|
notifyDidSaveTextDocument(event, clientId = LANGUAGE_CLIENT_ID) {
|
|
424
544
|
const syncedDocument = this.__syncedDocuments.get(this.documentKey(event.textDocument.uri));
|
|
425
545
|
if (syncedDocument !== undefined) {
|
|
426
|
-
|
|
427
|
-
|
|
546
|
+
if (event.text !== undefined) {
|
|
547
|
+
this.setDiskBaseline(syncedDocument.uri, event.text);
|
|
548
|
+
}
|
|
549
|
+
this.announceSave(syncedDocument, clientId);
|
|
428
550
|
}
|
|
429
551
|
}
|
|
552
|
+
/** Fire {@link onDidSave} for `document`, which need not be synced any more. */
|
|
553
|
+
announceSave(document, clientId) {
|
|
554
|
+
this.log(document.uri, `Saved synced document: ${document.version} by ${this.formatClientId(clientId)}`);
|
|
555
|
+
this.__onDidSave.fire(Object.freeze({ document, clientId }));
|
|
556
|
+
}
|
|
430
557
|
notifyDidOpenTextDocument(event, clientId = LANGUAGE_CLIENT_ID) {
|
|
431
558
|
const td = event.textDocument;
|
|
432
559
|
const uri = this.documentKey(td.uri);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
// open/rebuild; the document is already live.
|
|
439
|
-
if (clientId === LANGUAGE_CLIENT_ID) {
|
|
440
|
-
const clientFacing = this.toLanguageClientUri(td.uri);
|
|
441
|
-
const record = this.__documents.get(uri);
|
|
442
|
-
if (record && !record.languageClientUris?.has(clientFacing)) {
|
|
443
|
-
(record.languageClientUris ??= new Set()).add(clientFacing);
|
|
444
|
-
const open = this.__syncedDocuments.get(uri);
|
|
445
|
-
if (open) {
|
|
446
|
-
this.__shadow.set(clientFacing, open.getText());
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return;
|
|
560
|
+
// Before the deferred release is resolved: refused after it, the open
|
|
561
|
+
// would leave a document whose grace it ended with no holder and no timer.
|
|
562
|
+
// A repeat open stays a no-op.
|
|
563
|
+
if (!this.__sessions.isOpenIn(uri, clientId)) {
|
|
564
|
+
this.__sessions.assertCanOpen(clientId);
|
|
451
565
|
}
|
|
566
|
+
this.resolveDeferredRelease(uri, clientId);
|
|
452
567
|
let document = this.__syncedDocuments.get(uri);
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
568
|
+
const editor = clientId === LANGUAGE_CLIENT_ID;
|
|
569
|
+
// The client's declared text, never the synced document: staged
|
|
570
|
+
// content this open consumes leaves the two different, and a baseline
|
|
571
|
+
// asserting the client already holds it suppresses the one sync that
|
|
572
|
+
// would deliver it. On an attach, another client may already have
|
|
573
|
+
// changed the synced text, so the baseline is equality-only; a refresh
|
|
574
|
+
// pushing the full text would dirty the file on open. A first open's
|
|
575
|
+
// version is known only once its document is made, below.
|
|
576
|
+
if (editor) {
|
|
577
|
+
const held = document?.getText() === td.text ? document.version : undefined;
|
|
578
|
+
this.languageClientShadow.addOpen(uri, this.toLanguageClientUri(td.uri), td.version, td.text, document === undefined, held);
|
|
579
|
+
}
|
|
580
|
+
if (this.isOpenInClient(uri, clientId)) {
|
|
581
|
+
// A repeat open, or the editor's second URI for the file, e.g. a
|
|
582
|
+
// symlink and its real path: the shadow records it so pushes reach it,
|
|
583
|
+
// and nothing re-fires.
|
|
584
|
+
return;
|
|
463
585
|
}
|
|
586
|
+
const existingClients = this.__sessions.clientsOf(uri);
|
|
587
|
+
this.__sessions.addOpen(uri, clientId);
|
|
588
|
+
this.__sessions.setClientVersion(uri, clientId, td.version);
|
|
464
589
|
if (!document) {
|
|
465
590
|
// Use integrity-staged content if available, otherwise the client-provided (disk) text.
|
|
466
591
|
const pendingText = this.consumePendingContent(uri);
|
|
467
592
|
const text = pendingText ?? td.text;
|
|
468
593
|
const source = pendingText ? ', source=pending' : '';
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// the optimistic gate). Only a first-ever open adopts the client's
|
|
474
|
-
// declared id as the sequence seed.
|
|
475
|
-
const sequence = this.__versionSequences.get(uri);
|
|
476
|
-
const version = sequence === undefined ? td.version : sequence.contentHash === contentHash(text) ? sequence.version : sequence.version + 1;
|
|
477
|
-
this.log(uri, `Open document: Version ${version} by ${clientId} [first client${source}]`);
|
|
478
|
-
document = this.configuration.create(uri, td.languageId, version, text);
|
|
479
|
-
this.__syncedDocuments.set(uri, document);
|
|
480
|
-
this.setAuthor(uri, version, clientId);
|
|
481
|
-
if (clientId === LANGUAGE_CLIENT_ID) {
|
|
482
|
-
// Baseline the shadow to what Monaco just opened so the next outbound
|
|
483
|
-
// applyEditToLanguageClient diffs against the right starting point. Keyed by the
|
|
484
|
-
// language-client URI (what Monaco holds), not the canonical document key.
|
|
485
|
-
this.__shadow.set(this.toLanguageClientUri(td.uri), document.getText());
|
|
594
|
+
const version = this.textLedger.openingVersion(uri, text) ?? this.builtRootOpeningVersion(uri, text) ?? td.version;
|
|
595
|
+
if (editor && text === td.text) {
|
|
596
|
+
// What `addOpen` already recorded, now with the version it is held at.
|
|
597
|
+
this.languageClientShadow.setLanguageClientText(this.toLanguageClientUri(td.uri), text, version);
|
|
486
598
|
}
|
|
599
|
+
this.log(uri, `Open document: Version ${version} by ${this.formatClientId(clientId)} [first client${source}]`);
|
|
600
|
+
document = this.create(uri, td.languageId, version, text);
|
|
601
|
+
this.commitText(uri, document, clientId);
|
|
602
|
+
this.__openedVersions.set(uri, version);
|
|
603
|
+
// The opener's text, not the staged content: a session's open read
|
|
604
|
+
// it from the file, and an editor opened its buffer from there. An
|
|
605
|
+
// editor that opens a buffer it never saved is taken as clean.
|
|
606
|
+
this.dirtyStateTracker.track(uri, document, td.text);
|
|
487
607
|
const toFire = Object.freeze({ document, clientId });
|
|
488
608
|
this.__onDidOpen.fire(toFire);
|
|
489
609
|
this.__onDidChangeContent.fire(toFire);
|
|
490
610
|
}
|
|
491
611
|
else {
|
|
492
612
|
// An additional client attaches to a document already open by another client.
|
|
493
|
-
this.
|
|
494
|
-
`now open in: ${[...existingClients, clientId].join(', ')})`);
|
|
613
|
+
this.logClientJoined(uri, clientId, document.version, existingClients);
|
|
495
614
|
this.refreshContent(uri, clientId);
|
|
496
615
|
}
|
|
497
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Record `clientId` as an additional holder of a document another client
|
|
619
|
+
* already has open.
|
|
620
|
+
*
|
|
621
|
+
* Unlike the attach branch of {@link notifyDidOpenTextDocument} this does
|
|
622
|
+
* not refresh. That branch re-renders an attaching TEXTUAL view; a client
|
|
623
|
+
* reaching a document through a non-textual route has nothing to re-render,
|
|
624
|
+
* and refreshing per attach turns each into a Langium rebuild and dependent
|
|
625
|
+
* relink cascade.
|
|
626
|
+
*
|
|
627
|
+
* Returns whether a hold was added — `false` when `uri` is not open,
|
|
628
|
+
* `clientId` already holds it, or the document was waiting out the release
|
|
629
|
+
* grace for other clients and was released instead (see
|
|
630
|
+
* {@link resolveDeferredRelease}); the caller then opens it anew.
|
|
631
|
+
*/
|
|
632
|
+
attachClient(uri, clientId) {
|
|
633
|
+
const key = this.documentKey(uri);
|
|
634
|
+
if (!this.__syncedDocuments.has(key) || this.isOpenInClient(key, clientId)) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
// Before the deferred release is resolved, as for an open.
|
|
638
|
+
this.__sessions.assertCanOpen(clientId);
|
|
639
|
+
this.resolveDeferredRelease(key, clientId);
|
|
640
|
+
const document = this.__syncedDocuments.get(key);
|
|
641
|
+
if (!document) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
const existingClients = this.__sessions.clientsOf(key);
|
|
645
|
+
this.__sessions.addOpen(key, clientId);
|
|
646
|
+
// A client arriving this way holds no buffer of its own, so its guard
|
|
647
|
+
// starts at the synced version.
|
|
648
|
+
this.__sessions.setClientVersion(key, clientId, document.version);
|
|
649
|
+
this.logClientJoined(key, clientId, document.version, existingClients);
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
logClientJoined(uri, clientId, version, existingClients) {
|
|
653
|
+
this.log(uri, `Attach client: ${this.formatClientId(clientId)} joined existing document (version ${version}, ` +
|
|
654
|
+
`now open in: ${[...existingClients, clientId].map(id => this.formatClientId(id)).join(', ')})`);
|
|
655
|
+
}
|
|
498
656
|
refreshContent(uri, clientId) {
|
|
499
657
|
const syncedDocument = this.__syncedDocuments.get(this.documentKey(uri));
|
|
500
658
|
if (syncedDocument) {
|
|
501
659
|
// Trigger a (re-)build by firing a change event.
|
|
502
|
-
const timer = this.startTimerForUri(syncedDocument.uri, `Refresh synced document: Version ${syncedDocument.version} by ${clientId}`);
|
|
660
|
+
const timer = this.startTimerForUri(syncedDocument.uri, `Refresh synced document: Version ${syncedDocument.version} by ${this.formatClientId(clientId)}`);
|
|
503
661
|
this.__onDidChangeContent.fire(Object.freeze({ document: syncedDocument, clientId }));
|
|
504
662
|
timer.dispose();
|
|
505
663
|
}
|
|
@@ -511,7 +669,7 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
511
669
|
* two URIs for one physical file (a symlink path and its real path) into a
|
|
512
670
|
* single registration — dedup at the editor layer, not just in
|
|
513
671
|
* `LangiumDocuments`. The URI the client opened under is preserved separately
|
|
514
|
-
* for egress addressing
|
|
672
|
+
* for egress addressing, by the {@link LanguageClientShadow}. The
|
|
515
673
|
* policy is always bound (the framework defaults it to
|
|
516
674
|
* `DefaultDocumentUriPolicy`, where canonical ≡ syntactic normalize).
|
|
517
675
|
*/
|
|
@@ -529,17 +687,8 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
529
687
|
toLanguageClientUri(uri) {
|
|
530
688
|
return asLanguageClientUri(UriUtils.normalize(uri));
|
|
531
689
|
}
|
|
532
|
-
/** Get-or-create the per-URI tracking record. `uri` must already be a {@link documentKey}. */
|
|
533
|
-
trackingFor(uri) {
|
|
534
|
-
let record = this.__documents.get(uri);
|
|
535
|
-
if (!record) {
|
|
536
|
-
record = { clients: new Set(), versionAuthors: [], clientVersions: new Map() };
|
|
537
|
-
this.__documents.set(uri, record);
|
|
538
|
-
}
|
|
539
|
-
return record;
|
|
540
|
-
}
|
|
541
690
|
setAuthor(uri, version, author) {
|
|
542
|
-
this.
|
|
691
|
+
this.textLedger.setAuthor(this.documentKey(uri), version, author);
|
|
543
692
|
}
|
|
544
693
|
/**
|
|
545
694
|
* Resolve the synced `TextDocument` for `uri`. The store keys documents by
|
|
@@ -562,55 +711,190 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
562
711
|
}
|
|
563
712
|
/**
|
|
564
713
|
* Current SHARED version of the document at `uri`: the open document's
|
|
565
|
-
* version, else where
|
|
566
|
-
*
|
|
714
|
+
* version, else where its persisted sequence left off, else `0` for a URI
|
|
715
|
+
* this store has never seen. The shared sequence is
|
|
567
716
|
* server-owned and monotonic across close/reopen cycles, and advances
|
|
568
717
|
* exactly when the synced content changes — which is what makes it a sound
|
|
569
|
-
* optimistic-concurrency token (
|
|
718
|
+
* optimistic-concurrency token (base-version gates): version unchanged ⇔
|
|
570
719
|
* content unchanged.
|
|
571
720
|
*/
|
|
572
721
|
version(uri) {
|
|
573
|
-
return this.get(uri)?.version ?? this.
|
|
722
|
+
return this.get(uri)?.version ?? this.textLedger.recordOf(this.documentKey(uri))?.version ?? 0;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* The version the document at `uri` was opened at, which a client's write
|
|
726
|
+
* or an integrity repair steps past; `undefined` while it is not open.
|
|
727
|
+
*/
|
|
728
|
+
openedVersion(uri) {
|
|
729
|
+
return this.__openedVersions.get(this.documentKey(uri));
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Refuse unless the editor's buffer under `uri`, at its own `version`, holds
|
|
733
|
+
* the synced text, so a save the editor asks for persists what it shows.
|
|
734
|
+
* The editor's version counts its own buffer and the store's counts the
|
|
735
|
+
* synced text, so neither stands in for the other: `version` has to be the
|
|
736
|
+
* one the editor last declared, and the shadow has to know the buffer to
|
|
737
|
+
* hold the store's current version, which a push in flight breaks.
|
|
738
|
+
*
|
|
739
|
+
* A refusal is a {@link ConflictError} based on the store version the buffer
|
|
740
|
+
* last held, or on {@link STALE_VERSION} when it never held one or `version`
|
|
741
|
+
* names a buffer the editor never declared. It is a
|
|
742
|
+
* {@link DocumentNotOpenError} when the editor does not have `uri` open, or
|
|
743
|
+
* a delete of it is under way, which a save landing before the deletion
|
|
744
|
+
* build would undo by writing the file back.
|
|
745
|
+
*/
|
|
746
|
+
assertLanguageClientHolds(uri, version) {
|
|
747
|
+
const key = this.documentKey(uri);
|
|
748
|
+
const clientUri = this.toLanguageClientUri(uri);
|
|
749
|
+
const declared = this.languageClientShadow.declaredVersion(key, clientUri);
|
|
750
|
+
const document = this.__syncedDocuments.get(key);
|
|
751
|
+
if (declared === undefined || document === undefined || this.services.workspace.AstDocumentManager.isDeleting(key)) {
|
|
752
|
+
throw new DocumentNotOpenError(uri, LANGUAGE_CLIENT_ID);
|
|
753
|
+
}
|
|
754
|
+
const held = declared === version ? this.languageClientShadow.heldVersion(key, clientUri) : undefined;
|
|
755
|
+
if (held !== document.version) {
|
|
756
|
+
const base = held ?? STALE_VERSION;
|
|
757
|
+
this.logUri(key, `Refuse editor save: buffer v${version} holds text v${base}, store holds v${document.version}`, 'debug');
|
|
758
|
+
// A snapshot, as the brand means: recorded when the buffer held that text, not read live.
|
|
759
|
+
throw new ConflictError(uri, asModelVersion(base), document.version, STALE_BASE_VERSION_SAVE);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Whether the editor holds text older than the store's text at `version`,
|
|
764
|
+
* or text the store never held. The first is the case while the push of
|
|
765
|
+
* that text is in flight. An answer computed on the store's text at
|
|
766
|
+
* `version` then refers to text the editor does not have. `false` once the
|
|
767
|
+
* editor holds that text or a later one, and when it does not have `uri`
|
|
768
|
+
* open.
|
|
769
|
+
*/
|
|
770
|
+
isLanguageClientBehind(uri, version) {
|
|
771
|
+
const key = this.documentKey(uri);
|
|
772
|
+
const clientUri = this.toLanguageClientUri(uri);
|
|
773
|
+
if (!this.languageClientShadow.isOpen(key, clientUri)) {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
const held = this.languageClientShadow.heldVersion(key, clientUri);
|
|
777
|
+
return held === undefined || held < version;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* The text the store holds for `uri`: an open document's, or for a closed
|
|
781
|
+
* one, the clean text its version sequence left off at, which for a deleted
|
|
782
|
+
* document is its last text. `undefined` for a URI no build and no client
|
|
783
|
+
* gave the store.
|
|
784
|
+
*/
|
|
785
|
+
textState(uri) {
|
|
786
|
+
const document = this.get(uri);
|
|
787
|
+
if (document) {
|
|
788
|
+
return { version: document.version, hash: this.textLedger.hashOf(document), dirty: this.isDirty(uri) };
|
|
789
|
+
}
|
|
790
|
+
const recorded = this.textLedger.recordOf(this.documentKey(uri));
|
|
791
|
+
return recorded && { version: recorded.version, hash: recorded.hash, dirty: false };
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* The built root's recorded version for a first open of `key` with `text`,
|
|
795
|
+
* one on when the text differs from the root's; `undefined` when the root
|
|
796
|
+
* records no store version. Seeded from the opener's declared version
|
|
797
|
+
* instead, a write based on the built root passes the gate over other text,
|
|
798
|
+
* and the same text looks newer than its model. A built root records none
|
|
799
|
+
* only under a `LangiumDocuments` that does not reconcile at registration.
|
|
800
|
+
*/
|
|
801
|
+
builtRootOpeningVersion(key, text) {
|
|
802
|
+
const built = this.services.workspace.LangiumDocuments.getDocument(UriUtils.toUri(key));
|
|
803
|
+
if (built === undefined) {
|
|
804
|
+
return undefined;
|
|
805
|
+
}
|
|
806
|
+
const ledger = this.services.workspace.ModelLedger;
|
|
807
|
+
const root = built.parseResult.value;
|
|
808
|
+
const recorded = ledger.versionOf(root);
|
|
809
|
+
if (recorded === UNRECORDED_VERSION || recorded === STALE_VERSION) {
|
|
810
|
+
return undefined;
|
|
811
|
+
}
|
|
812
|
+
return (ledger.textOf(root) ?? built.textDocument.getText()) === text ? recorded : recorded + 1;
|
|
574
813
|
}
|
|
575
814
|
/**
|
|
576
815
|
* Reconcile the persisted version sequence with content that reached the
|
|
577
816
|
* build OUTSIDE the store's write paths — a closed document rebuilt from
|
|
578
|
-
* disk
|
|
817
|
+
* disk after its release, or replaced by a watched-file change. Steps the
|
|
579
818
|
* sequence iff `text` differs from the sequence's last-known content and
|
|
580
819
|
* returns the resulting sequence version so the caller can re-stamp the
|
|
581
|
-
* rebuilt document (
|
|
820
|
+
* rebuilt document (`VersionSyncService.modelProduced`) —
|
|
582
821
|
* keeping the "version advances iff content changes" invariant for
|
|
583
822
|
* documents no client currently holds.
|
|
584
823
|
*
|
|
585
|
-
*
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
824
|
+
* For an open document, its version when it holds `text`, since a build
|
|
825
|
+
* that read the file while the document was closed can finish after an
|
|
826
|
+
* open; else `undefined`, and the caller has to build the document again.
|
|
827
|
+
*
|
|
828
|
+
* A document without a sequence starts one at `0` with `text`: snapshots
|
|
829
|
+
* hand out versions for documents no client opened, so an uncounted change
|
|
830
|
+
* would let a write based on the old text pass the gate.
|
|
590
831
|
*/
|
|
591
832
|
reconcileExternalContent(uri, text) {
|
|
592
833
|
const key = this.documentKey(uri);
|
|
593
|
-
|
|
594
|
-
|
|
834
|
+
const open = this.__syncedDocuments.get(key);
|
|
835
|
+
if (open !== undefined) {
|
|
836
|
+
return open.getText() === text ? open.version : undefined;
|
|
595
837
|
}
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
838
|
+
const before = this.textLedger.recordOf(key)?.version;
|
|
839
|
+
const version = this.textLedger.reconcile(key, text);
|
|
840
|
+
if (before !== undefined && version !== before) {
|
|
841
|
+
this.logUri(key, `External content change while closed: sequence stepped to version ${version}`, 'debug');
|
|
599
842
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
843
|
+
return version;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Commit an integrity repair into the OPEN document for `uri`, returning the
|
|
847
|
+
* document that now holds it, or `undefined` when there was nothing here to
|
|
848
|
+
* commit into.
|
|
849
|
+
*
|
|
850
|
+
* The one thing this exists to do is address the store BY URI. A repair
|
|
851
|
+
* reaches the resync as a text-document object, and that object is the
|
|
852
|
+
* store's own only on the LSP path; a document built for an already-open URI
|
|
853
|
+
* through `LangiumDocumentFactory.fromString` carries its own, so writing the
|
|
854
|
+
* repair into it corrects a copy the store does not know about and the editor
|
|
855
|
+
* never sees.
|
|
856
|
+
*
|
|
857
|
+
* `parsedFrom` is the text the repaired AST was parsed from, and a mismatch
|
|
858
|
+
* against the current content REFUSES the commit: the store has moved on,
|
|
859
|
+
* which means an editor change landed after that parse, and committing would
|
|
860
|
+
* overwrite a newer edit with a repair computed against text the user has
|
|
861
|
+
* already replaced. The build that change provokes recomputes the repair.
|
|
862
|
+
*
|
|
863
|
+
* Deliberately NOT compared by version. A separately created document seeds
|
|
864
|
+
* its own numbering, so requiring the two to agree would refuse every commit
|
|
865
|
+
* on the path this exists for. Content is the thing both sides can be held to.
|
|
866
|
+
*
|
|
867
|
+
* A changed text is a new version authored by {@link INTEGRITY_CLIENT_ID}.
|
|
868
|
+
* Kept at the old version, the repair is invisible to every reader keyed on
|
|
869
|
+
* versions: a write based on the unrepaired version passes the base-version gate
|
|
870
|
+
* and replaces the repair, and an echo filter credits the repair to the
|
|
871
|
+
* client whose edit it corrected. No change event is fired: the repair
|
|
872
|
+
* rides the build already under way, and an event would re-enter it.
|
|
873
|
+
*/
|
|
874
|
+
commitRepair(uri, parsedFrom, repaired) {
|
|
875
|
+
const key = this.documentKey(uri);
|
|
876
|
+
const document = this.__syncedDocuments.get(key);
|
|
877
|
+
if (document === undefined) {
|
|
878
|
+
return { status: 'not-open' };
|
|
603
879
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
880
|
+
if (document.getText() !== parsedFrom) {
|
|
881
|
+
this.logUri(key, 'Refuse repair commit: the open document moved on from the text the AST was parsed from', 'debug');
|
|
882
|
+
return { status: 'stale' };
|
|
883
|
+
}
|
|
884
|
+
if (repaired === parsedFrom) {
|
|
885
|
+
return { status: 'committed', document };
|
|
886
|
+
}
|
|
887
|
+
// Reassigned rather than mutated in place: the default configuration
|
|
888
|
+
// updates and returns the SAME instance, but an adopter-supplied one may
|
|
889
|
+
// return a new object, and the store must end up holding whichever it is.
|
|
890
|
+
const updated = this.commitChange(key, document, [{ text: repaired }], INTEGRITY_CLIENT_ID).document;
|
|
891
|
+
this.log(updated.uri, `Update to version ${updated.version} by ${this.formatClientId(INTEGRITY_CLIENT_ID)} (repair)`);
|
|
892
|
+
return { status: 'committed', document: updated };
|
|
608
893
|
}
|
|
609
894
|
getAuthor(uri, version) {
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (!clientId && history) {
|
|
895
|
+
const key = this.documentKey(uri);
|
|
896
|
+
const clientId = this.textLedger.authorOf(key, version);
|
|
897
|
+
if (!clientId && this.textLedger.authorOf(key) !== undefined) {
|
|
614
898
|
// Only warn when there IS a history but the specific version is missing; no history at all
|
|
615
899
|
// means the document was rebuilt internally (e.g. by a project manager), not an error.
|
|
616
900
|
this.log(uri, `Could not detect author of version ${version}.`);
|
|
@@ -624,16 +908,18 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
624
908
|
* True iff any client still holds `uri` open. Distinct from {@link isOpen},
|
|
625
909
|
* which reads `__syncedDocuments` — that map is cleared only AFTER the
|
|
626
910
|
* `onDidClose` event fires for the last client, so `isOpen` returns `true`
|
|
627
|
-
* during the close event itself. `isOpenInAnyClient` reads the
|
|
628
|
-
*
|
|
629
|
-
* subscriber that finds this `false` knows the last client just closed
|
|
630
|
-
*
|
|
911
|
+
* during the close event itself. `isOpenInAnyClient` reads the open table
|
|
912
|
+
* ({@link __sessions}), which is updated BEFORE the fire, so an `onDidClose`
|
|
913
|
+
* subscriber that finds this `false` knows the last client just closed.
|
|
914
|
+
* What the build keeps is the release's: a subscriber that re-read or
|
|
915
|
+
* rebuilt the document would race the `DocumentReleaseHandler`, and after a
|
|
916
|
+
* lost connection the release waits out its grace.
|
|
631
917
|
*/
|
|
632
918
|
isOpenInAnyClient(uri) {
|
|
633
|
-
return
|
|
919
|
+
return this.__sessions.isOpen(this.documentKey(uri));
|
|
634
920
|
}
|
|
635
921
|
isOpenInClient(uri, client) {
|
|
636
|
-
return
|
|
922
|
+
return this.__sessions.isOpenIn(this.documentKey(uri), client);
|
|
637
923
|
}
|
|
638
924
|
isOpenInLanguageClient(uri) {
|
|
639
925
|
// Takes any URI form (canonicalizes internally via `documentKey`, like
|
|
@@ -641,14 +927,18 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
641
927
|
// client-facing URI both resolve to the same registration.
|
|
642
928
|
return this.isOpenInClient(uri, LANGUAGE_CLIENT_ID);
|
|
643
929
|
}
|
|
930
|
+
/** The clients that have `uri` open, in the order they opened it. */
|
|
931
|
+
clientsOf(uri) {
|
|
932
|
+
return this.__sessions.clientsOf(this.documentKey(uri));
|
|
933
|
+
}
|
|
644
934
|
isOnlyOpenInClient(uri, client) {
|
|
645
|
-
const
|
|
646
|
-
return
|
|
935
|
+
const clients = this.__sessions.clientsOf(this.documentKey(uri));
|
|
936
|
+
return clients.length === 1 && clients[0] === client;
|
|
647
937
|
}
|
|
648
938
|
/**
|
|
649
939
|
* Every document currently held open by at least one client, with the client
|
|
650
|
-
* ids holding it. Reads the same
|
|
651
|
-
*
|
|
940
|
+
* ids holding it. Reads the same open table as {@link isOpenInAnyClient}, so
|
|
941
|
+
* it reflects a last-client close immediately.
|
|
652
942
|
*
|
|
653
943
|
* Diagnostics-oriented — the server-state snapshot lists these so an operator
|
|
654
944
|
* can see WHY a document is pinned: a document that lingers here after its
|
|
@@ -656,30 +946,252 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
656
946
|
* under a shedding policy, explains why its CST is never shed).
|
|
657
947
|
*/
|
|
658
948
|
openDocuments() {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
949
|
+
return this.__sessions.openDocuments();
|
|
950
|
+
}
|
|
951
|
+
/** Fires once a client session has ended, after every document it had open was closed. */
|
|
952
|
+
get onDidCloseSession() {
|
|
953
|
+
return this.__sessions.onDidCloseSession;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Fires once a document no client has open is released: at its last close,
|
|
957
|
+
* or, for a lost client's last close, when its release grace runs out,
|
|
958
|
+
* another client opens it, or it is deleted, before the
|
|
959
|
+
* `DocumentReleaseHandler` slot is handed the document.
|
|
960
|
+
*/
|
|
961
|
+
get onDidReleaseDocument() {
|
|
962
|
+
return this.documentReleasedEmitter.event;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Fires for every save the language client reports of a document it has
|
|
966
|
+
* open: the editor has written the file. {@link onDidSave} follows only
|
|
967
|
+
* when the file holds the store's text.
|
|
968
|
+
*/
|
|
969
|
+
get onDidSaveInLanguageClient() {
|
|
970
|
+
return this.languageClientSavedEmitter.event;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Whether `uri` is waiting out the release grace: its last open closed with a
|
|
974
|
+
* lost connection, and it still holds its unsaved text. Such a document is
|
|
975
|
+
* open for no client, yet not closed either, so a caller that would persist
|
|
976
|
+
* a closed document's text to disk treats it as open.
|
|
977
|
+
*/
|
|
978
|
+
isReleaseDeferred(uri) {
|
|
979
|
+
return this.documentReleaseScheduler.isDeferred(this.documentKey(uri));
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Whether the store holds `uri` with text that differs from its disk
|
|
983
|
+
* baseline: what the server last knew the file to hold. `false` for a URI
|
|
984
|
+
* the store does not hold; a document waiting out the release grace is still
|
|
985
|
+
* held.
|
|
986
|
+
*
|
|
987
|
+
* The baseline is the text a first open brought, or what the server wrote,
|
|
988
|
+
* or read back after an editor's save or a watched-file change, so it can
|
|
989
|
+
* trail a change to the file that none of these has seen yet. A check that
|
|
990
|
+
* must know the file reads it instead.
|
|
991
|
+
*/
|
|
992
|
+
isDirty(uri) {
|
|
993
|
+
return this.dirtyStateTracker.isDirty(this.documentKey(uri));
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Fires each time the answer of {@link isDirty} changes. For a document
|
|
997
|
+
* released dirty it fires once the `DocumentReleaseHandler` slot reports the
|
|
998
|
+
* release settled: at the version of the text the build then holds, or
|
|
999
|
+
* without text when the document is gone or the handler failed, though
|
|
1000
|
+
* {@link isDirty} answers clean from the release on.
|
|
1001
|
+
*/
|
|
1002
|
+
get onDidChangeDirty() {
|
|
1003
|
+
return this.dirtyStateTracker.onDidChangeDirty;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Record that the file behind `uri` holds `text`, or no file at all for
|
|
1007
|
+
* `undefined`. A no-op for a URI the store does not hold: the next first
|
|
1008
|
+
* open sets the baseline from its own text.
|
|
1009
|
+
*/
|
|
1010
|
+
setDiskBaseline(uri, text) {
|
|
1011
|
+
const key = this.documentKey(uri);
|
|
1012
|
+
const document = this.__syncedDocuments.get(key);
|
|
1013
|
+
if (document !== undefined) {
|
|
1014
|
+
this.dirtyStateTracker.setDiskBaseline(key, document, text);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Record that `uri` was created from a session's text rather than read from
|
|
1019
|
+
* its file: it has no disk baseline, so it is dirty until its first save,
|
|
1020
|
+
* and {@link isUnsavedCreation} answers `true` until then. A no-op for a
|
|
1021
|
+
* URI the store does not hold.
|
|
1022
|
+
*/
|
|
1023
|
+
markCreated(uri) {
|
|
1024
|
+
const key = this.documentKey(uri);
|
|
1025
|
+
const document = this.__syncedDocuments.get(key);
|
|
1026
|
+
if (document !== undefined) {
|
|
1027
|
+
this.dirtyStateTracker.markCreated(key, document);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Whether `uri` was created from a session's text and the store has not
|
|
1032
|
+
* seen its file since, by a save or a reload that found it. A baseline
|
|
1033
|
+
* cleared because the file went missing does not count. `false` for a URI
|
|
1034
|
+
* the store does not hold.
|
|
1035
|
+
*/
|
|
1036
|
+
isUnsavedCreation(uri) {
|
|
1037
|
+
return this.dirtyStateTracker.isUnsavedCreation(this.documentKey(uri));
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Read the file behind `uri` through its disk queue and take it as the
|
|
1041
|
+
* baseline. A file that cannot be read counts as none, the side that
|
|
1042
|
+
* leaves the document dirty, once `existsSettled` confirms it gone: a file
|
|
1043
|
+
* it finds is read again. That wait runs outside the queue, so it holds up
|
|
1044
|
+
* no write to the file.
|
|
1045
|
+
*/
|
|
1046
|
+
async reloadDiskBaseline(uri) {
|
|
1047
|
+
const key = this.documentKey(uri);
|
|
1048
|
+
if (!this.__syncedDocuments.has(key)) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const workspace = this.services.workspace;
|
|
1052
|
+
const fileSystem = workspace.FileSystemProvider;
|
|
1053
|
+
const target = UriUtils.toUri(key);
|
|
1054
|
+
const read = async () => {
|
|
1055
|
+
try {
|
|
1056
|
+
return await workspace.FileSystemTaskQueue.enqueue(key, () => fileSystem.readFile(target));
|
|
1057
|
+
}
|
|
1058
|
+
catch (err) {
|
|
1059
|
+
this.tracer.with(key).debug(`Disk baseline: the file cannot be read. ${describeError(err)}`);
|
|
1060
|
+
return undefined;
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
let onDisk = await read();
|
|
1064
|
+
if (onDisk === undefined && (await (fileSystem.existsSettled?.(target) ?? fileSystem.exists(target)))) {
|
|
1065
|
+
onDisk = await read();
|
|
1066
|
+
}
|
|
1067
|
+
this.setDiskBaseline(key, onDisk);
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Start a client session under `clientId`. Throws where
|
|
1071
|
+
* {@link ClientSessionRegistry.register} refuses the id.
|
|
1072
|
+
*/
|
|
1073
|
+
registerSession(clientId) {
|
|
1074
|
+
this.__sessions.register(clientId);
|
|
1075
|
+
this.tracer.info(`Session started: ${this.formatClientId(clientId)}`);
|
|
1076
|
+
this.tracer.trace(`Session started: ${clientId}`);
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* End the client session `clientId`: close every document it has open, then
|
|
1080
|
+
* free the id. Immediate — each close runs the ordinary close path before
|
|
1081
|
+
* this returns, with `cause` as its cause. A no-op for an id that is not a
|
|
1082
|
+
* registered session.
|
|
1083
|
+
*/
|
|
1084
|
+
closeSession(clientId, cause = 'closed') {
|
|
1085
|
+
if (!this.__sessions.isRegistered(clientId)) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
try {
|
|
1089
|
+
for (const uri of this.__sessions.beginClose(clientId)) {
|
|
1090
|
+
this.notifyDidCloseTextDocument({ textDocument: { uri } }, clientId, cause);
|
|
663
1091
|
}
|
|
664
1092
|
}
|
|
665
|
-
|
|
1093
|
+
finally {
|
|
1094
|
+
this.__sessions.unregister(clientId, cause);
|
|
1095
|
+
this.tracer.info(`Session closed: ${this.formatClientId(clientId)}`);
|
|
1096
|
+
this.tracer.trace(`Session closed: ${clientId}`);
|
|
1097
|
+
}
|
|
666
1098
|
}
|
|
667
1099
|
/**
|
|
668
|
-
*
|
|
1100
|
+
* Close every document the language client has open, as a `didClose` for
|
|
1101
|
+
* each would, so each last close releases its document. For a host whose editor connection
|
|
1102
|
+
* can end while the process lives on, such as a worker whose port's peer
|
|
1103
|
+
* closed: the language client is no session, so nothing else closes them.
|
|
669
1104
|
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
1105
|
+
* It first waits for the workspace initialization the open handler waits
|
|
1106
|
+
* for, so an open that arrived before the close is closed too; closing at
|
|
1107
|
+
* once would leave that open to land afterwards, held by a client that is
|
|
1108
|
+
* gone.
|
|
1109
|
+
*/
|
|
1110
|
+
async closeLanguageClientDocuments() {
|
|
1111
|
+
await this.initialBuildFinished();
|
|
1112
|
+
for (const uri of this.__sessions.opensOf(LANGUAGE_CLIENT_ID)) {
|
|
1113
|
+
// A close for one URI while others remain keeps the client's hold.
|
|
1114
|
+
this.languageClientShadow.removeAllOpens(uri);
|
|
1115
|
+
this.notifyDidCloseTextDocument({ textDocument: { uri } });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Settles once the initial workspace build has finished, whether it
|
|
1120
|
+
* completed, was cancelled or failed. Awaiting `workspaceInitialized` itself
|
|
1121
|
+
* throws after a cancelled build, which any write during startup causes, and
|
|
1122
|
+
* every open and close gated on it is then dropped for the process lifetime.
|
|
1123
|
+
* The workspace manager logs a failed build.
|
|
1124
|
+
*/
|
|
1125
|
+
async initialBuildFinished() {
|
|
1126
|
+
await this.services.workspace.WorkspaceManager.workspaceInitialized.catch(() => undefined);
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* The file behind `uri` was deleted: close every open of it except the
|
|
1130
|
+
* language client's, and release now a document waiting out the grace,
|
|
1131
|
+
* whose lost client's open would otherwise bring its text back with no
|
|
1132
|
+
* file behind it.
|
|
674
1133
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
|
|
1134
|
+
* The editor's open is left alone because the editor owns it: it keeps the
|
|
1135
|
+
* buffer of a deleted file and goes on sending changes for it, and a store
|
|
1136
|
+
* that had closed the document would drop every one of them until the editor
|
|
1137
|
+
* reopened.
|
|
1138
|
+
*/
|
|
1139
|
+
notifyDocumentDeleted(uri) {
|
|
1140
|
+
const key = this.documentKey(uri);
|
|
1141
|
+
for (const clientId of this.__sessions.clientsOf(key)) {
|
|
1142
|
+
if (clientId !== LANGUAGE_CLIENT_ID) {
|
|
1143
|
+
this.notifyDidCloseTextDocument({ textDocument: { uri: key } }, clientId);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
this.releaseIfDeferred(key);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Remove the document for `uri`, closing every open of it first — the
|
|
1150
|
+
* editor's included, since the document it would keep editing is gone.
|
|
1151
|
+
*
|
|
1152
|
+
* The base removes the synced document without consulting the open table,
|
|
1153
|
+
* which leaves every open recorded against a document that no longer exists:
|
|
1154
|
+
* the store then answers that the URI is open, and the next open attaches to
|
|
1155
|
+
* nothing.
|
|
1156
|
+
*/
|
|
1157
|
+
delete(uri) {
|
|
1158
|
+
const key = this.documentKey((typeof uri === 'object' && 'uri' in uri ? uri.uri : uri).toString());
|
|
1159
|
+
this.languageClientShadow.removeAllOpens(key);
|
|
1160
|
+
for (const clientId of this.__sessions.clientsOf(key)) {
|
|
1161
|
+
this.notifyDidCloseTextDocument({ textDocument: { uri: key } }, clientId);
|
|
1162
|
+
}
|
|
1163
|
+
// A document waiting out the grace has no client left to close, and is
|
|
1164
|
+
// released now as its last close would have released it.
|
|
1165
|
+
this.releaseIfDeferred(key);
|
|
1166
|
+
super.delete(key);
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* A client id as log lines print it. An id containing `#` is cut eight
|
|
1170
|
+
* characters after its last `#`, which leaves a minted `label#uuid` as the
|
|
1171
|
+
* label and enough of the UUID to tell sessions apart; an id without `#`
|
|
1172
|
+
* prints whole. The full id goes out at trace level wherever a session
|
|
1173
|
+
* starts or ends.
|
|
1174
|
+
*/
|
|
1175
|
+
formatClientId(clientId) {
|
|
1176
|
+
const separator = clientId.lastIndexOf('#');
|
|
1177
|
+
return separator < 0 ? clientId : clientId.slice(0, separator + 9);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Stages integrity-updated content for a document no client holds.
|
|
1181
|
+
*
|
|
1182
|
+
* Nothing is pushed to a file no client holds. The next open of it reads
|
|
1183
|
+
* disk, which lacks the update; {@link notifyDidOpenTextDocument} takes this
|
|
1184
|
+
* staged content in place of that text, and the open's sync then delivers
|
|
1185
|
+
* it to an editor as an unsaved change.
|
|
1186
|
+
*
|
|
1187
|
+
* Only a FIRST open consumes it, so stage only for a URI no client holds
|
|
1188
|
+
* ({@link isOpenInAnyClient} is `false`). A URI held only through another
|
|
1189
|
+
* head is not closed: an editor attaching to it joins the existing entry and
|
|
1190
|
+
* never reads the stage, and the release discards it. An entry waits for
|
|
1191
|
+
* that first open, at one serialised string per URI.
|
|
680
1192
|
*/
|
|
681
1193
|
stagePendingContent(uri, text) {
|
|
682
|
-
this.
|
|
1194
|
+
this.__pendingContent.set(this.documentKey(uri), text);
|
|
683
1195
|
}
|
|
684
1196
|
/**
|
|
685
1197
|
* Send `newText` to the LSP textual language client (Monaco / VS Code) via
|
|
@@ -690,63 +1202,53 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
690
1202
|
* - The shared services have no LSP {@link Connection} bound (non-LSP
|
|
691
1203
|
* hosts like CLI / tests).
|
|
692
1204
|
* - The shadow already matches `newText` (no edits needed; quiet skip).
|
|
1205
|
+
* - The language client has not opened the document. A client applies an
|
|
1206
|
+
* edit to a closed file by opening it, and that open races the edit;
|
|
1207
|
+
* the document's own open delivers its text instead.
|
|
693
1208
|
*
|
|
694
1209
|
* On `applyEdit` rejection (`result.applied === false`) or RPC failure the
|
|
695
|
-
* shadow is invalidated so the next call sends a full
|
|
1210
|
+
* shadow is invalidated so the next call sends a full replace, or nothing
|
|
1211
|
+
* when the client was last heard to hold that text.
|
|
696
1212
|
* Errors are re-thrown — callers wrap with their own retry / coalescing
|
|
697
1213
|
* policy as needed.
|
|
698
1214
|
*
|
|
699
1215
|
* The diff path is apply-verify-safe: the shadow internally checks that
|
|
700
1216
|
* `TextDocument.applyEdits(old, edits) === newText` and falls back to a
|
|
701
|
-
* full replace on mismatch
|
|
702
|
-
*
|
|
1217
|
+
* full replace on mismatch, logged, so a diff regression becomes log noise,
|
|
1218
|
+
* not data loss.
|
|
703
1219
|
*
|
|
704
1220
|
* That safety net verifies the diff against the SHADOW, which is what the
|
|
705
1221
|
* client is *believed* to hold — so it cannot see the client's buffer moving
|
|
706
1222
|
* underneath a push. A line-keyed edit is position-dependent: if a genuine
|
|
707
|
-
* client keystroke lands between
|
|
708
|
-
*
|
|
709
|
-
*
|
|
1223
|
+
* client keystroke lands between computing the edits and the client
|
|
1224
|
+
* applying them, the ranges address the wrong lines and splice the buffer
|
|
1225
|
+
* (observed as a duplicated declaration, which the integrity tier then
|
|
710
1226
|
* "repairs" into a suffixed name and persists). The edit is therefore
|
|
711
|
-
* addressed at the language client's
|
|
712
|
-
* `null` ("version intentionally unknown"), which is what lets the
|
|
713
|
-
* reject a push its buffer has outrun. On rejection the shadow is invalidated,
|
|
1227
|
+
* addressed at the language client's last known version for that URI rather
|
|
1228
|
+
* than at `null` ("version intentionally unknown"), which is what lets the
|
|
1229
|
+
* client reject a push its buffer has outrun. On rejection the shadow is invalidated,
|
|
714
1230
|
* so the caller's retry is a full-range replace — position-independent, and
|
|
715
|
-
* safe to apply to whatever the client now holds
|
|
1231
|
+
* safe to apply to whatever the client now holds — or nothing when the
|
|
1232
|
+
* client was last heard to hold that text.
|
|
716
1233
|
*/
|
|
717
1234
|
async applyEditToLanguageClient(uri, newText, options) {
|
|
718
1235
|
const connection = this.services.lsp?.Connection;
|
|
719
1236
|
if (!connection) {
|
|
720
1237
|
return undefined;
|
|
721
1238
|
}
|
|
722
|
-
// The document is keyed by its canonical identity, but
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
// when the same file was opened under a symlink and its real path. Falls back
|
|
726
|
-
// to the normalized URI when nothing is tracked. The shadow is keyed by each
|
|
727
|
-
// URI, so every diff is against the right baseline.
|
|
728
|
-
const recorded = this.__documents.get(this.documentKey(uri))?.languageClientUris;
|
|
729
|
-
const targets = recorded && recorded.size > 0 ? recorded : [this.toLanguageClientUri(uri)];
|
|
1239
|
+
// The document is keyed by its canonical identity, but the client holds it
|
|
1240
|
+
// under each URI it opened, and each is diffed against its own baseline.
|
|
1241
|
+
const key = this.documentKey(uri);
|
|
730
1242
|
let lastResult;
|
|
731
|
-
for (const
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
if (edits.length === 0) {
|
|
1243
|
+
for (const clientUri of this.languageClientShadow.pushTargets(key)) {
|
|
1244
|
+
// Prepared per target, after the previous target's reply: prepared up
|
|
1245
|
+
// front, a later target is diffed against what it held before a change
|
|
1246
|
+
// that arrived meanwhile.
|
|
1247
|
+
const push = this.languageClientShadow.preparePush(key, clientUri, newText);
|
|
1248
|
+
if (push === undefined) {
|
|
738
1249
|
continue;
|
|
739
1250
|
}
|
|
740
|
-
// Record the push for echo correlation BEFORE the RPC — the client's
|
|
741
|
-
// echo can race the applyEdit response. See `__pendingPushes`.
|
|
742
|
-
this.recordPendingPush(targetUri, before, newText);
|
|
743
1251
|
try {
|
|
744
|
-
// Version the push only when it is position-DEPENDENT. A full-range
|
|
745
|
-
// replace lands correctly on any buffer, so gating it would turn a
|
|
746
|
-
// stale-by-one version into a refused update for no safety gain — and
|
|
747
|
-
// it is exactly what the caller retries with after a rejection, so
|
|
748
|
-
// gating it there would refuse the recovery too.
|
|
749
|
-
const version = isFullReplace(edits) ? UNKNOWN_CLIENT_VERSION : this.languageClientVersion(uri);
|
|
750
1252
|
// A full `ApplyWorkspaceEditParams`, `edit` and all — NOT a bare
|
|
751
1253
|
// `WorkspaceEdit` with a `label` beside it. `applyEdit` takes
|
|
752
1254
|
// `ApplyWorkspaceEditParams | WorkspaceEdit` and discriminates on
|
|
@@ -756,137 +1258,55 @@ export class HydraniumTextDocuments extends NormalizedTextDocuments {
|
|
|
756
1258
|
// The union is also what hides it at compile time: excess-property
|
|
757
1259
|
// checking admits a property present in EITHER member, so an object
|
|
758
1260
|
// matching neither type-checks against the union.
|
|
1261
|
+
const version = push.version ?? UNKNOWN_CLIENT_VERSION;
|
|
759
1262
|
const result = await connection.workspace.applyEdit({
|
|
760
1263
|
label: options?.label,
|
|
761
1264
|
edit: {
|
|
762
|
-
documentChanges: [TextDocumentEdit.create(OptionalVersionedTextDocumentIdentifier.create(
|
|
1265
|
+
documentChanges: [TextDocumentEdit.create(OptionalVersionedTextDocumentIdentifier.create(clientUri, version), push.edits)]
|
|
763
1266
|
}
|
|
764
1267
|
});
|
|
765
1268
|
if (result && result.applied === false) {
|
|
766
|
-
|
|
767
|
-
|
|
1269
|
+
push.notifyOutcome('refused');
|
|
1270
|
+
}
|
|
1271
|
+
else if (result?.applied) {
|
|
1272
|
+
push.notifyOutcome('applied');
|
|
768
1273
|
}
|
|
769
1274
|
lastResult = result;
|
|
770
1275
|
}
|
|
771
1276
|
catch (err) {
|
|
772
|
-
|
|
773
|
-
this.__pendingPushes.delete(targetUri);
|
|
1277
|
+
push.notifyOutcome('failed');
|
|
774
1278
|
throw err;
|
|
775
1279
|
}
|
|
776
1280
|
}
|
|
777
1281
|
return lastResult;
|
|
778
1282
|
}
|
|
779
1283
|
/**
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
* Falls back to {@link UNKNOWN_CLIENT_VERSION} when the client has never
|
|
789
|
-
* declared one, which is the honest answer for a document it has not opened.
|
|
790
|
-
* That is also the case in which there is no shadow, so the push is already a
|
|
791
|
-
* position-independent full replace and has nothing to gain from a gate.
|
|
1284
|
+
* Explicitly baseline the language-client shadow for a URI the client has
|
|
1285
|
+
* open, for an adopter that knows what the client holds without a
|
|
1286
|
+
* `didChange` saying so (e.g. after a sideband save). Normal didOpen /
|
|
1287
|
+
* didChange paths from the LSP language client already auto-track the
|
|
1288
|
+
* shadow. A seed for a URI the client has not opened is kept but changes no
|
|
1289
|
+
* push, since nothing is pushed there; its open replaces it with the text it
|
|
1290
|
+
* declares. `text` other than the synced text refuses the editor's saves
|
|
1291
|
+
* until the editor holds the synced text again.
|
|
792
1292
|
*/
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
* Append a push to the in-flight queue for `targetUri`
|
|
798
|
-
* (see {@link __pendingPushes}). Bounded: beyond
|
|
799
|
-
* {@link PENDING_ECHO_CAP} the oldest entry drops with a debug log — an
|
|
800
|
-
* echo that far outstanding means the client is not echoing at all, and
|
|
801
|
-
* an unbounded queue must not become the leak.
|
|
802
|
-
*/
|
|
803
|
-
recordPendingPush(targetUri, before, newText) {
|
|
804
|
-
let pending = this.__pendingPushes.get(targetUri);
|
|
805
|
-
if (!pending) {
|
|
806
|
-
pending = [];
|
|
807
|
-
this.__pendingPushes.set(targetUri, pending);
|
|
808
|
-
}
|
|
809
|
-
pending.push({ before, afterHash: contentHash(newText) });
|
|
810
|
-
if (pending.length > PENDING_ECHO_CAP) {
|
|
811
|
-
pending.shift();
|
|
812
|
-
this.logUri(targetUri, `Pending-echo queue exceeded ${PENDING_ECHO_CAP} entries; dropped the oldest`, 'debug');
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Decide what an incoming language-client change actually is, by
|
|
817
|
-
* reconstructing the client's resulting buffer against the OLDEST push still
|
|
818
|
-
* in flight for `clientFacing`.
|
|
819
|
-
*
|
|
820
|
-
* `undefined` when nothing of ours is in flight, which is the ordinary path:
|
|
821
|
-
* the client's ranges then address the synced text and the caller applies
|
|
822
|
-
* them to it directly.
|
|
823
|
-
*
|
|
824
|
-
* **Why the oldest, and why reconstruct at all.** The client applies our
|
|
825
|
-
* pushes in order and echoes each against the buffer it held before that
|
|
826
|
-
* push, so the first echo to arrive belongs to the oldest entry — a FIFO
|
|
827
|
-
* correspondence the queue preserves by consuming from the front. Comparing
|
|
828
|
-
* the reconstruction against every pending hash, not only the oldest, is
|
|
829
|
-
* what recognises an echo that a newer write already superseded: a rapid
|
|
830
|
-
* write sequence can deliver the echo of push N after the store applied
|
|
831
|
-
* push N+1, and everything older is then accounted for too.
|
|
832
|
-
*
|
|
833
|
-
* A reconstruction matching NO pending push means the client's buffer went
|
|
834
|
-
* somewhere we did not send it — it coalesced a keystroke into the echo, or
|
|
835
|
-
* typed before the push landed. That text is authoritative, and the queue
|
|
836
|
-
* drops: the client has stopped being a pure mirror, so no outstanding echo
|
|
837
|
-
* can match again. (A push still in flight at that point will be refused by
|
|
838
|
-
* the client's own version gate, which invalidates the shadow and makes the
|
|
839
|
-
* next sync a position-independent full replace.)
|
|
840
|
-
*
|
|
841
|
-
* Content equality is a sound echo proof because entries live only between a
|
|
842
|
-
* push and its echo — a milliseconds window, never history — and the
|
|
843
|
-
* client's `didChange` stream is ordered, so every buffer state arrives in
|
|
844
|
-
* mutation order. An UNDO returning the buffer to previously-pushed text is
|
|
845
|
-
* therefore never swallowed: the edit that preceded it already emptied the
|
|
846
|
-
* queue, so undo revisits PAST states while the queue holds IN-FLIGHT ones.
|
|
847
|
-
*/
|
|
848
|
-
classifyLanguageClientChange(clientFacing, document, changes) {
|
|
849
|
-
const pending = this.__pendingPushes.get(clientFacing);
|
|
850
|
-
if (pending === undefined || pending.length === 0) {
|
|
851
|
-
return undefined;
|
|
852
|
-
}
|
|
853
|
-
// Through the configured factories, not `TextDocument` directly, so an
|
|
854
|
-
// adopter's custom text-document type governs how the ranges are applied
|
|
855
|
-
// here exactly as it does on the synced document.
|
|
856
|
-
const probe = this.create(clientFacing, document.languageId, 0, pending[0].before ?? document.getText());
|
|
857
|
-
const clientText = this.update(probe, changes, 0).getText();
|
|
858
|
-
const matchIndex = pending.findIndex(push => push.afterHash === contentHash(clientText));
|
|
859
|
-
if (matchIndex >= 0) {
|
|
860
|
-
pending.splice(0, matchIndex + 1);
|
|
861
|
-
return { kind: 'echo' };
|
|
862
|
-
}
|
|
863
|
-
pending.length = 0;
|
|
864
|
-
return { kind: 'divergent', text: clientText };
|
|
1293
|
+
setLanguageClientText(uri, text) {
|
|
1294
|
+
const document = this.get(uri);
|
|
1295
|
+
const held = document?.getText() === text ? document.version : undefined;
|
|
1296
|
+
this.languageClientShadow.setLanguageClientText(this.toLanguageClientUri(uri), text, held);
|
|
865
1297
|
}
|
|
866
1298
|
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
* paths from the LSP language client already auto-track the shadow.
|
|
1299
|
+
* Drop the shadow baseline for a URI; the next applyEditToLanguageClient
|
|
1300
|
+
* sends a full replace, or nothing when the client was last heard to hold
|
|
1301
|
+
* that text.
|
|
871
1302
|
*/
|
|
872
|
-
setLanguageClientText(uri, text) {
|
|
873
|
-
const clientFacing = this.toLanguageClientUri(uri);
|
|
874
|
-
this.__shadow.set(clientFacing, text);
|
|
875
|
-
// An explicit rebaseline supersedes whatever was in flight.
|
|
876
|
-
this.__pendingPushes.delete(clientFacing);
|
|
877
|
-
}
|
|
878
|
-
/** Drop the shadow baseline for a URI; the next applyEditToLanguageClient sends a full replace. */
|
|
879
1303
|
invalidateLanguageClientText(uri) {
|
|
880
|
-
|
|
881
|
-
this.__shadow.invalidate(clientFacing);
|
|
882
|
-
this.__pendingPushes.delete(clientFacing);
|
|
1304
|
+
this.languageClientShadow.invalidateLanguageClientText(this.toLanguageClientUri(uri));
|
|
883
1305
|
}
|
|
884
1306
|
consumePendingContent(uri) {
|
|
885
|
-
const
|
|
886
|
-
const content =
|
|
887
|
-
|
|
888
|
-
record.pendingContent = undefined;
|
|
889
|
-
}
|
|
1307
|
+
const key = this.documentKey(uri);
|
|
1308
|
+
const content = this.__pendingContent.get(key);
|
|
1309
|
+
this.__pendingContent.delete(key);
|
|
890
1310
|
return content;
|
|
891
1311
|
}
|
|
892
1312
|
log(uri, message) {
|