diff --git a/src/lsp_client/capability/build.py b/src/lsp_client/capability/build.py index 7a32b4e..9772cb8 100644 --- a/src/lsp_client/capability/build.py +++ b/src/lsp_client/capability/build.py @@ -3,6 +3,7 @@ from typing import Any from lsp_client.protocol import ( + ExperimentalCapabilityProtocol, GeneralCapabilityProtocol, NotebookCapabilityProtocol, ServerRequestHookProtocol, @@ -22,6 +23,7 @@ def build_client_capabilities(cls: type) -> lsp_type.ClientCapabilities: ) window = lsp_type.WindowClientCapabilities() general = lsp_type.GeneralClientCapabilities() + experimental: dict[str, Any] = {} if issubclass(cls, WorkspaceCapabilityProtocol): cls.register_workspace_capability(workspace) @@ -33,6 +35,8 @@ def build_client_capabilities(cls: type) -> lsp_type.ClientCapabilities: cls.register_window_capability(window) if issubclass(cls, GeneralCapabilityProtocol): cls.register_general_capability(general) + if issubclass(cls, ExperimentalCapabilityProtocol): + cls.register_experimental_capability(experimental) return lsp_type.ClientCapabilities( workspace=workspace, @@ -40,6 +44,7 @@ def build_client_capabilities(cls: type) -> lsp_type.ClientCapabilities: notebook_document=notebook_document, window=window, general=general, + experimental=experimental or None, ) diff --git a/src/lsp_client/clients/deno/README.md b/src/lsp_client/clients/deno/README.md new file mode 100644 index 0000000..4438c24 --- /dev/null +++ b/src/lsp_client/clients/deno/README.md @@ -0,0 +1,56 @@ +# Deno LSP Custom Capabilities + +This document summarizes the custom LSP capabilities and protocol extensions provided by the Deno Language Server. + +## Custom Requests (Client → Server) + +| Request | Python Method | Purpose | +| ----------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `deno/cache` | `request_deno_cache` | Instructs Deno to cache a module and its dependencies. Usually sent as a response to a code action for un-cached modules. | +| `deno/performance` | `request_deno_performance` | Requests timing averages for Deno's internal instrumentation for monitoring and debugging. | +| `deno/reloadImportRegistries` | `request_deno_reload_import_registries` | Reloads cached responses from import registries. | +| `deno/virtualTextDocument` | `request_deno_virtual_text_document` | Requests read-only virtual text documents (e.g., cached remote modules or Deno library files) using the `deno:` schema. | +| `deno/task` | `request_deno_task` | Retrieves a list of available Deno tasks defined in `deno.json` or `deno.jsonc`. | + +## Custom Notifications (Server → Client) + +| Notification | Python Method | Default Behavior | +| -------------------- | ----------------------------- | ---------------------------------------------------------------------------------- | +| `deno/registryState` | `receive_deno_registry_state` | Logs the registry discovery status and configuration suggestions at `DEBUG` level. | + +## Testing API (Experimental) + +Requires `experimental.testingApi` capability to be enabled by both client and server. + +### Requests (Client → Server) + +| Request | Python Method | Purpose | +| -------------------- | ------------------------------ | ---------------------------------------------------------- | +| `deno/testRun` | `request_deno_test_run` | Initiates a test execution for specified modules or tests. | +| `deno/testRunCancel` | `request_deno_test_run_cancel` | Cancels an ongoing test run by ID. | + +### Notifications (Server → Client) + +| Notification | Python Method | Default Behavior | +| ----------------------- | --------------------------------- | ------------------------------------------------------------------------------------------ | +| `deno/testModule` | `receive_deno_test_module` | Logs discovered test module information at `DEBUG` level. | +| `deno/testModuleDelete` | `receive_deno_test_module_delete` | Logs deleted test module information at `DEBUG` level. | +| `deno/testRunProgress` | `receive_deno_test_run_progress` | Logs test run state (`enqueued`, `started`, `passed`, etc.) and progress at `DEBUG` level. | + +## Other Experimental Capabilities + +- `denoConfigTasks`: Support for tasks defined in Deno configuration files. +- `didRefreshDenoConfigurationTreeNotifications`: Notifications for when the configuration tree is refreshed. + +## Deno LSP Documentation + +### Official Documentation + +- [Language Server Integration](https://docs.deno.com/runtime/reference/lsp_integration/) - Comprehensive guide for integrating with Deno's LSP, including custom capabilities and testing API +- [deno lsp CLI Reference](https://docs.deno.com/runtime/reference/cli/lsp/) - Basic information about the `deno lsp` subcommand +- [Deno & Visual Studio Code](https://docs.deno.com/runtime/reference/vscode/) - Complete integration guide and setup + +### Source Code & Development + +- [Deno CLI LSP Source](https://github.com/denoland/deno/tree/main/cli/lsp) - The actual LSP implementation in Rust +- [VS Code Extension](https://github.com/denoland/vscode_deno) - Official VS Code extension source code diff --git a/src/lsp_client/clients/deno/__init__.py b/src/lsp_client/clients/deno/__init__.py new file mode 100644 index 0000000..4273636 --- /dev/null +++ b/src/lsp_client/clients/deno/__init__.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from .client import DenoClient, DenoContainerServer + +__all__ = [ + "DenoClient", + "DenoContainerServer", +] diff --git a/src/lsp_client/clients/deno/client.py b/src/lsp_client/clients/deno/client.py new file mode 100644 index 0000000..913b8cf --- /dev/null +++ b/src/lsp_client/clients/deno/client.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +import shutil +from functools import partial +from subprocess import CalledProcessError +from typing import Any, override + +import anyio +from attrs import Factory, define +from loguru import logger + +from lsp_client.capability.request import ( + WithRequestCallHierarchy, + WithRequestDefinition, + WithRequestDocumentSymbol, + WithRequestHover, + WithRequestImplementation, + WithRequestReferences, + WithRequestTypeDefinition, + WithRequestWorkspaceSymbol, +) +from lsp_client.capability.server_notification import ( + WithReceivePublishDiagnostics, +) +from lsp_client.capability.server_notification.log_message import WithReceiveLogMessage +from lsp_client.client.abc import LSPClient +from lsp_client.server.abc import LSPServer +from lsp_client.server.container import ContainerServer +from lsp_client.server.local import LocalServer +from lsp_client.utils.types import lsp_type + +from .extension import ( + WithReceiveDenoRegistryStatus, + WithReceiveDenoTestModule, + WithReceiveDenoTestModuleDelete, + WithReceiveDenoTestRunProgress, + WithRequestDenoCache, + WithRequestDenoPerformance, + WithRequestDenoReloadImportRegistries, + WithRequestDenoTask, + WithRequestDenoTestRun, + WithRequestDenoTestRunCancel, + WithRequestDenoVirtualTextDocument, +) + +DenoContainerServer = partial( + ContainerServer, image="ghcr.io/observerw/lsp-client/deno:latest" +) + + +@define +class DenoClient( + LSPClient, + WithRequestHover, + WithRequestDefinition, + WithRequestReferences, + WithRequestImplementation, + WithRequestTypeDefinition, + WithRequestCallHierarchy, + WithRequestDocumentSymbol, + WithRequestWorkspaceSymbol, + WithReceiveLogMessage, + WithReceivePublishDiagnostics, + WithRequestDenoCache, + WithRequestDenoPerformance, + WithRequestDenoReloadImportRegistries, + WithRequestDenoVirtualTextDocument, + WithRequestDenoTask, + WithRequestDenoTestRun, + WithRequestDenoTestRunCancel, + WithReceiveDenoRegistryStatus, + WithReceiveDenoTestModule, + WithReceiveDenoTestModuleDelete, + WithReceiveDenoTestRunProgress, +): + """ + - Language: TypeScript, JavaScript + - Homepage: https://deno.land/ + - Doc: https://docs.deno.com/runtime/reference/lsp_integration/ + - Github: https://github.com/denoland/deno + - VSCode Extension: https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno + """ + + enable: bool = True + unstable: bool = False + lint: bool = True + + config: str | None = None + import_map: str | None = None + + code_lens_implementations: bool = True + code_lens_references: bool = True + code_lens_references_all_functions: bool = True + code_lens_test: bool = True + + suggest_complete_function_calls: bool = True + suggest_names: bool = True + suggest_paths: bool = True + suggest_auto_imports: bool = True + suggest_imports_auto_discover: bool = True + suggest_imports_hosts: list[str] = Factory(list) + + testing_enable: bool = False + testing_args: list[str] = Factory(list) + + @override + def get_language_id(self) -> lsp_type.LanguageKind: + return lsp_type.LanguageKind.TypeScript + + @override + def create_default_server(self) -> LSPServer: + return LocalServer(command=["deno", "lsp"]) + + @override + def create_initialization_options(self) -> dict[str, Any]: + options: dict[str, Any] = { + "enable": self.enable, + "unstable": self.unstable, + "lint": self.lint, + "codeLens": { + "implementations": self.code_lens_implementations, + "references": self.code_lens_references, + "referencesAllFunctions": self.code_lens_references_all_functions, + "test": self.code_lens_test, + }, + "suggest": { + "completeFunctionCalls": self.suggest_complete_function_calls, + "names": self.suggest_names, + "paths": self.suggest_paths, + "autoImports": self.suggest_auto_imports, + "imports": { + "autoDiscover": self.suggest_imports_auto_discover, + "hosts": list(self.suggest_imports_hosts), + }, + }, + } + + if self.config: + options["config"] = self.config + + if self.import_map: + options["importMap"] = self.import_map + + if self.testing_enable: + options["testing"] = { + "enable": self.testing_enable, + "args": list(self.testing_args), + } + + return options + + @override + def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None: + return + + @override + async def ensure_installed(self) -> None: + if shutil.which("deno"): + return + + logger.warning("deno not found, attempting to install...") + + try: + # Use shell to execute the piped command + await anyio.run_process( + ["sh", "-c", "curl -fsSL https://deno.land/install.sh | sh"] + ) + logger.info("Successfully installed deno via shell script") + return + except CalledProcessError as e: + raise RuntimeError( + "Could not install deno. Please install it manually with:\n" + "curl -fsSL https://deno.land/install.sh | sh\n\n" + "See https://deno.land/ for more information." + ) from e diff --git a/src/lsp_client/clients/deno/extension.py b/src/lsp_client/clients/deno/extension.py new file mode 100644 index 0000000..2ee39a4 --- /dev/null +++ b/src/lsp_client/clients/deno/extension.py @@ -0,0 +1,337 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import Any, Protocol, override, runtime_checkable + +from loguru import logger + +from lsp_client.jsonrpc.id import jsonrpc_uuid +from lsp_client.protocol import ( + CapabilityClientProtocol, + CapabilityProtocol, + ExperimentalCapabilityProtocol, + ServerNotificationHook, + ServerRequestHookProtocol, + ServerRequestHookRegistry, +) +from lsp_client.utils.types import AnyPath, lsp_type + +from .models import ( + DENO_CACHE, + DENO_PERFORMANCE, + DENO_REGISTRY_STATE, + DENO_RELOAD_IMPORT_REGISTRIES, + DENO_TASK, + DENO_TEST_MODULE, + DENO_TEST_MODULE_DELETE, + DENO_TEST_RUN, + DENO_TEST_RUN_CANCEL, + DENO_TEST_RUN_PROGRESS, + DENO_VIRTUAL_TEXT_DOCUMENT, + DenoCacheParams, + DenoCacheRequest, + DenoCacheResponse, + DenoPerformanceRequest, + DenoPerformanceResponse, + DenoRegistryStatusNotification, + DenoReloadImportRegistriesRequest, + DenoReloadImportRegistriesResponse, + DenoTaskRequest, + DenoTaskResponse, + DenoTestModuleDeleteNotification, + DenoTestModuleNotification, + DenoTestRunCancelParams, + DenoTestRunCancelRequest, + DenoTestRunCancelResponse, + DenoTestRunProgressNotification, + DenoTestRunRequest, + DenoTestRunRequestParams, + DenoTestRunResponse, + DenoTestRunResponseParams, + DenoVirtualTextDocumentParams, + DenoVirtualTextDocumentRequest, + DenoVirtualTextDocumentResponse, +) + + +@runtime_checkable +class WithRequestDenoCache( + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_CACHE,) + + async def request_deno_cache( + self, + referrer: AnyPath, + uris: Sequence[AnyPath] = (), + ) -> None: + return await self.request( + DenoCacheRequest( + id=jsonrpc_uuid(), + params=DenoCacheParams( + referrer=lsp_type.TextDocumentIdentifier(uri=self.as_uri(referrer)), + uris=[ + lsp_type.TextDocumentIdentifier(uri=self.as_uri(uri)) + for uri in uris + ], + ), + ), + schema=DenoCacheResponse, + ) + + +@runtime_checkable +class WithRequestDenoPerformance( + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_PERFORMANCE,) + + async def request_deno_performance(self) -> Any: + return await self.request( + DenoPerformanceRequest(id=jsonrpc_uuid()), + schema=DenoPerformanceResponse, + ) + + +@runtime_checkable +class WithRequestDenoReloadImportRegistries( + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_RELOAD_IMPORT_REGISTRIES,) + + async def request_deno_reload_import_registries(self) -> None: + return await self.request( + DenoReloadImportRegistriesRequest(id=jsonrpc_uuid()), + schema=DenoReloadImportRegistriesResponse, + ) + + +@runtime_checkable +class WithRequestDenoVirtualTextDocument( + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_VIRTUAL_TEXT_DOCUMENT,) + + async def request_deno_virtual_text_document( + self, + uri: str, + ) -> str: + return await self.request( + DenoVirtualTextDocumentRequest( + id=jsonrpc_uuid(), + params=DenoVirtualTextDocumentParams( + text_document=lsp_type.TextDocumentIdentifier(uri=uri) + ), + ), + schema=DenoVirtualTextDocumentResponse, + ) + + +@runtime_checkable +class WithRequestDenoTask( + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_TASK,) + + async def request_deno_task(self) -> list[Any]: + return await self.request( + DenoTaskRequest(id=jsonrpc_uuid()), + schema=DenoTaskResponse, + ) + + +@runtime_checkable +class WithRequestDenoTestRun( + ExperimentalCapabilityProtocol, + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_TEST_RUN,) + + @override + @classmethod + def register_experimental_capability(cls, cap: dict[str, Any]) -> None: + cap["testingApi"] = True + + async def request_deno_test_run( + self, + params: DenoTestRunRequestParams, + ) -> DenoTestRunResponseParams: + return await self.request( + DenoTestRunRequest( + id=jsonrpc_uuid(), + params=params, + ), + schema=DenoTestRunResponse, + ) + + +@runtime_checkable +class WithRequestDenoTestRunCancel( + CapabilityProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_TEST_RUN_CANCEL,) + + async def request_deno_test_run_cancel( + self, + test_run_id: int, + ) -> None: + return await self.request( + DenoTestRunCancelRequest( + id=jsonrpc_uuid(), + params=DenoTestRunCancelParams(id=test_run_id), + ), + schema=DenoTestRunCancelResponse, + ) + + +@runtime_checkable +class WithReceiveDenoRegistryStatus( + ServerRequestHookProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_REGISTRY_STATE,) + + async def receive_deno_registry_state( + self, noti: DenoRegistryStatusNotification + ) -> None: + logger.debug("Received Deno registry state: {}", noti.params) + + @override + def register_server_request_hooks( + self, registry: ServerRequestHookRegistry + ) -> None: + super().register_server_request_hooks(registry) + registry.register( + DENO_REGISTRY_STATE, + ServerNotificationHook( + cls=DenoRegistryStatusNotification, + execute=self.receive_deno_registry_state, + ), + ) + + +@runtime_checkable +class WithReceiveDenoTestModule( + ServerRequestHookProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_TEST_MODULE,) + + async def receive_deno_test_module(self, noti: DenoTestModuleNotification) -> None: + logger.debug("Received Deno test module: {}", noti.params) + + @override + def register_server_request_hooks( + self, registry: ServerRequestHookRegistry + ) -> None: + super().register_server_request_hooks(registry) + registry.register( + DENO_TEST_MODULE, + ServerNotificationHook( + cls=DenoTestModuleNotification, + execute=self.receive_deno_test_module, + ), + ) + + +@runtime_checkable +class WithReceiveDenoTestModuleDelete( + ServerRequestHookProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_TEST_MODULE_DELETE,) + + async def receive_deno_test_module_delete( + self, noti: DenoTestModuleDeleteNotification + ) -> None: + logger.debug("Received Deno test module delete: {}", noti.params) + + @override + def register_server_request_hooks( + self, registry: ServerRequestHookRegistry + ) -> None: + super().register_server_request_hooks(registry) + registry.register( + DENO_TEST_MODULE_DELETE, + ServerNotificationHook( + cls=DenoTestModuleDeleteNotification, + execute=self.receive_deno_test_module_delete, + ), + ) + + +@runtime_checkable +class WithReceiveDenoTestRunProgress( + ServerRequestHookProtocol, + CapabilityClientProtocol, + Protocol, +): + @override + @classmethod + def methods(cls) -> Sequence[str]: + return (DENO_TEST_RUN_PROGRESS,) + + async def receive_deno_test_run_progress( + self, noti: DenoTestRunProgressNotification + ) -> None: + logger.debug("Received Deno test run progress: {}", noti.params) + + @override + def register_server_request_hooks( + self, registry: ServerRequestHookRegistry + ) -> None: + super().register_server_request_hooks(registry) + registry.register( + DENO_TEST_RUN_PROGRESS, + ServerNotificationHook( + cls=DenoTestRunProgressNotification, + execute=self.receive_deno_test_run_progress, + ), + ) diff --git a/src/lsp_client/clients/deno/models.py b/src/lsp_client/clients/deno/models.py new file mode 100644 index 0000000..4941990 --- /dev/null +++ b/src/lsp_client/clients/deno/models.py @@ -0,0 +1,368 @@ +from __future__ import annotations + +from typing import Any, Literal + +import cattrs +from attrs import define, field, resolve_types +from lsprotocol import converters + +from lsp_client.jsonrpc.id import ID +from lsp_client.utils.types import lsp_type + +# ---------------------------------- Constants --------------------------------- # + +DENO_CACHE: Literal["deno/cache"] = "deno/cache" +DENO_PERFORMANCE: Literal["deno/performance"] = "deno/performance" +DENO_RELOAD_IMPORT_REGISTRIES: Literal["deno/reloadImportRegistries"] = ( + "deno/reloadImportRegistries" +) +DENO_VIRTUAL_TEXT_DOCUMENT: Literal["deno/virtualTextDocument"] = ( + "deno/virtualTextDocument" +) +DENO_TASK: Literal["deno/task"] = "deno/task" +DENO_REGISTRY_STATE: Literal["deno/registryState"] = "deno/registryState" +DENO_TEST_RUN: Literal["deno/testRun"] = "deno/testRun" +DENO_TEST_RUN_CANCEL: Literal["deno/testRunCancel"] = "deno/testRunCancel" +DENO_TEST_MODULE: Literal["deno/testModule"] = "deno/testModule" +DENO_TEST_MODULE_DELETE: Literal["deno/testModuleDelete"] = "deno/testModuleDelete" +DENO_TEST_RUN_PROGRESS: Literal["deno/testRunProgress"] = "deno/testRunProgress" + + +# --------------------------------- Base Types -------------------------------- # + + +@define +class DenoTestData: + id: str + label: str + steps: list[DenoTestData] | None = None + range: lsp_type.Range | None = None + + +@define +class DenoTestIdentifier: + text_document: lsp_type.TextDocumentIdentifier + id: str | None = None + step_id: str | None = None + + +@define +class DenoTestMessage: + message: lsp_type.MarkupContent + expected_output: str | None = None + actual_output: str | None = None + location: lsp_type.Location | None = None + + +@define +class DenoTestEnqueuedStartedSkipped: + type: Literal["enqueued", "started", "skipped"] + test: DenoTestIdentifier + + +@define +class DenoTestFailedErrored: + type: Literal["failed", "errored"] + test: DenoTestIdentifier + messages: list[DenoTestMessage] + duration: float | None = None + + +@define +class DenoTestPassed: + type: Literal["passed"] + test: DenoTestIdentifier + duration: float | None = None + + +@define +class DenoTestOutput: + type: Literal["output"] + value: str + test: DenoTestIdentifier | None = None + location: lsp_type.Location | None = None + + +@define +class DenoTestEnd: + type: Literal["end"] + + +type DenoTestRunProgressMessage = ( + DenoTestEnqueuedStartedSkipped + | DenoTestFailedErrored + | DenoTestPassed + | DenoTestOutput + | DenoTestEnd +) + + +@define +class DenoEnqueuedTestModule: + text_document: lsp_type.TextDocumentIdentifier + ids: list[str] + + +# ---------------------------------- Requests --------------------------------- # + + +@define +class DenoCacheParams: + referrer: lsp_type.TextDocumentIdentifier + uris: list[lsp_type.TextDocumentIdentifier] = field(factory=list) + + +@define +class DenoCacheRequest: + id: ID + params: DenoCacheParams + method: Literal["deno/cache"] = DENO_CACHE + jsonrpc: str = "2.0" + + +@define +class DenoCacheResponse: + id: ID | None + result: None + jsonrpc: str = "2.0" + + +@define +class DenoPerformanceRequest: + id: ID + method: Literal["deno/performance"] = DENO_PERFORMANCE + jsonrpc: str = "2.0" + + +@define +class DenoPerformanceResponse: + id: ID | None + result: Any + jsonrpc: str = "2.0" + + +@define +class DenoReloadImportRegistriesRequest: + id: ID + method: Literal["deno/reloadImportRegistries"] = DENO_RELOAD_IMPORT_REGISTRIES + jsonrpc: str = "2.0" + + +@define +class DenoReloadImportRegistriesResponse: + id: ID | None + result: None + jsonrpc: str = "2.0" + + +@define +class DenoVirtualTextDocumentParams: + text_document: lsp_type.TextDocumentIdentifier + + +@define +class DenoVirtualTextDocumentRequest: + id: ID + params: DenoVirtualTextDocumentParams + method: Literal["deno/virtualTextDocument"] = DENO_VIRTUAL_TEXT_DOCUMENT + jsonrpc: str = "2.0" + + +@define +class DenoVirtualTextDocumentResponse: + id: ID | None + result: str + jsonrpc: str = "2.0" + + +@define +class DenoTaskParams: + pass + + +@define +class DenoTaskRequest: + id: ID + params: DenoTaskParams | None = None + method: Literal["deno/task"] = DENO_TASK + jsonrpc: str = "2.0" + + +@define +class DenoTaskResponse: + id: ID | None + result: list[Any] + jsonrpc: str = "2.0" + + +@define +class DenoTestRunRequestParams: + id: int + kind: Literal["run", "coverage", "debug"] + exclude: list[DenoTestIdentifier] | None = None + include: list[DenoTestIdentifier] | None = None + + +@define +class DenoTestRunRequest: + id: ID + params: DenoTestRunRequestParams + method: Literal["deno/testRun"] = DENO_TEST_RUN + jsonrpc: str = "2.0" + + +@define +class DenoTestRunResponseParams: + enqueued: list[DenoEnqueuedTestModule] + + +@define +class DenoTestRunResponse: + id: ID | None + result: DenoTestRunResponseParams + jsonrpc: str = "2.0" + + +@define +class DenoTestRunCancelParams: + id: int + + +@define +class DenoTestRunCancelRequest: + id: ID + params: DenoTestRunCancelParams + method: Literal["deno/testRunCancel"] = DENO_TEST_RUN_CANCEL + jsonrpc: str = "2.0" + + +@define +class DenoTestRunCancelResponse: + id: ID | None + result: None + jsonrpc: str = "2.0" + + +# -------------------------------- Notifications ------------------------------- # + + +@define +class DenoRegistryStatusNotificationParams: + origin: str + suggestions: bool + + +@define +class DenoRegistryStatusNotification: + params: DenoRegistryStatusNotificationParams + method: Literal["deno/registryState"] = DENO_REGISTRY_STATE + jsonrpc: str = "2.0" + + +@define +class DenoTestModuleParams: + text_document: lsp_type.TextDocumentIdentifier + kind: Literal["insert", "replace"] + label: str + tests: list[DenoTestData] + + +@define +class DenoTestModuleNotification: + params: DenoTestModuleParams + method: Literal["deno/testModule"] = DENO_TEST_MODULE + jsonrpc: str = "2.0" + + +@define +class DenoTestModuleDeleteParams: + text_document: lsp_type.TextDocumentIdentifier + + +@define +class DenoTestModuleDeleteNotification: + params: DenoTestModuleDeleteParams + method: Literal["deno/testModuleDelete"] = DENO_TEST_MODULE_DELETE + jsonrpc: str = "2.0" + + +@define +class DenoTestRunProgressParams: + id: int + message: DenoTestRunProgressMessage + + +@define +class DenoTestRunProgressNotification: + params: DenoTestRunProgressParams + method: Literal["deno/testRunProgress"] = DENO_TEST_RUN_PROGRESS + jsonrpc: str = "2.0" + + +def register_hooks(converter: cattrs.Converter) -> None: + resolve_types(DenoTestData) + resolve_types(DenoTestIdentifier) + resolve_types(DenoTestMessage) + resolve_types(DenoTestEnqueuedStartedSkipped) + resolve_types(DenoTestFailedErrored) + resolve_types(DenoTestPassed) + resolve_types(DenoTestOutput) + resolve_types(DenoTestEnd) + resolve_types(DenoEnqueuedTestModule) + resolve_types(DenoCacheParams) + resolve_types(DenoCacheRequest) + resolve_types(DenoCacheResponse) + resolve_types(DenoPerformanceRequest) + resolve_types(DenoPerformanceResponse) + resolve_types(DenoReloadImportRegistriesRequest) + resolve_types(DenoReloadImportRegistriesResponse) + resolve_types(DenoVirtualTextDocumentParams) + resolve_types(DenoVirtualTextDocumentRequest) + resolve_types(DenoVirtualTextDocumentResponse) + resolve_types(DenoTaskParams) + resolve_types(DenoTaskRequest) + resolve_types(DenoTaskResponse) + resolve_types(DenoTestRunRequestParams) + resolve_types(DenoTestRunRequest) + resolve_types(DenoTestRunResponseParams) + resolve_types(DenoTestRunResponse) + resolve_types(DenoTestRunCancelParams) + resolve_types(DenoTestRunCancelRequest) + resolve_types(DenoTestRunCancelResponse) + resolve_types(DenoRegistryStatusNotificationParams) + resolve_types(DenoRegistryStatusNotification) + resolve_types(DenoTestModuleParams) + resolve_types(DenoTestModuleNotification) + resolve_types(DenoTestModuleDeleteParams) + resolve_types(DenoTestModuleDeleteNotification) + resolve_types(DenoTestRunProgressParams) + resolve_types(DenoTestRunProgressNotification) + + def _test_run_progress_message_hook( + obj: Any, _: type + ) -> DenoTestRunProgressMessage: + if not isinstance(obj, dict): + return obj + + match obj.get("type"): + case "enqueued" | "started" | "skipped": + return converter.structure(obj, DenoTestEnqueuedStartedSkipped) + case "failed" | "errored": + return converter.structure(obj, DenoTestFailedErrored) + case "passed": + return converter.structure(obj, DenoTestPassed) + case "output": + return converter.structure(obj, DenoTestOutput) + case "end": + return converter.structure(obj, DenoTestEnd) + case _: + raise ValueError( + f"Unknown DenoTestRunProgressMessage type: {obj.get('type')}" + ) + + converter.register_structure_hook( + DenoTestRunProgressMessage, _test_run_progress_message_hook + ) + + +register_hooks(converters.get_converter()) diff --git a/src/lsp_client/protocol/__init__.py b/src/lsp_client/protocol/__init__.py index ef3fedc..af4406e 100644 --- a/src/lsp_client/protocol/__init__.py +++ b/src/lsp_client/protocol/__init__.py @@ -2,6 +2,7 @@ from .capability import ( CapabilityProtocol, + ExperimentalCapabilityProtocol, GeneralCapabilityProtocol, NotebookCapabilityProtocol, TextDocumentCapabilityProtocol, @@ -21,6 +22,7 @@ __all__ = [ "CapabilityClientProtocol", "CapabilityProtocol", + "ExperimentalCapabilityProtocol", "GeneralCapabilityProtocol", "NotebookCapabilityProtocol", "ServerNotificationHook", diff --git a/src/lsp_client/protocol/capability.py b/src/lsp_client/protocol/capability.py index adde4da..be57513 100644 --- a/src/lsp_client/protocol/capability.py +++ b/src/lsp_client/protocol/capability.py @@ -2,7 +2,7 @@ from abc import abstractmethod from collections.abc import Sequence -from typing import Protocol, runtime_checkable +from typing import Any, Protocol, runtime_checkable from lsp_client.utils.types import lsp_type @@ -113,3 +113,17 @@ def register_general_capability( cls, cap: lsp_type.GeneralClientCapabilities ) -> None: return + + +@runtime_checkable +class ExperimentalCapabilityProtocol( + CapabilityProtocol, + Protocol, +): + """ + LSP `experimental` capability. + """ + + @classmethod + def register_experimental_capability(cls, cap: dict[str, Any]) -> None: + return