|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import shutil |
| 4 | +import sys |
| 5 | +from functools import partial |
| 6 | +from subprocess import CalledProcessError |
| 7 | +from typing import Any, Literal, override |
| 8 | + |
| 9 | +import anyio |
| 10 | +from attrs import define |
| 11 | +from loguru import logger |
| 12 | + |
| 13 | +from lsp_client.capability.notification import ( |
| 14 | + WithNotifyDidChangeConfiguration, |
| 15 | +) |
| 16 | +from lsp_client.capability.request import ( |
| 17 | + WithRequestDefinition, |
| 18 | + WithRequestHover, |
| 19 | + WithRequestReferences, |
| 20 | +) |
| 21 | +from lsp_client.capability.server_notification import ( |
| 22 | + WithReceiveLogMessage, |
| 23 | + WithReceivePublishDiagnostics, |
| 24 | +) |
| 25 | +from lsp_client.capability.server_request import ( |
| 26 | + WithRespondConfigurationRequest, |
| 27 | + WithRespondWorkspaceFoldersRequest, |
| 28 | +) |
| 29 | +from lsp_client.client.abc import LSPClient |
| 30 | +from lsp_client.server.abc import LSPServer |
| 31 | +from lsp_client.server.container import ContainerServer |
| 32 | +from lsp_client.server.local import LocalServer |
| 33 | +from lsp_client.utils.types import lsp_type |
| 34 | + |
| 35 | +TyContainerServer = partial(ContainerServer, image="ghcr.io/astral-sh/ty:latest") |
| 36 | + |
| 37 | + |
| 38 | +@define |
| 39 | +class TyClient( |
| 40 | + LSPClient, |
| 41 | + WithNotifyDidChangeConfiguration, |
| 42 | + WithRequestDefinition, |
| 43 | + WithRequestHover, |
| 44 | + WithRequestReferences, |
| 45 | + WithReceiveLogMessage, |
| 46 | + WithReceivePublishDiagnostics, |
| 47 | + WithRespondConfigurationRequest, |
| 48 | + WithRespondWorkspaceFoldersRequest, |
| 49 | +): |
| 50 | + """ |
| 51 | + - Language: Python |
| 52 | + - Homepage: https://docs.astral.sh/ty/ |
| 53 | + - Github: https://github.com/astral-sh/ty |
| 54 | + """ |
| 55 | + |
| 56 | + log_level: Literal["trace", "debug", "info", "warn", "error"] = "info" |
| 57 | + diagnostic_mode: Literal["openFilesOnly", "workspace"] = "openFilesOnly" |
| 58 | + inlay_hints_variable_types: bool = True |
| 59 | + inlay_hints_call_argument_names: bool = True |
| 60 | + |
| 61 | + @override |
| 62 | + def get_language_id(self) -> lsp_type.LanguageKind: |
| 63 | + return lsp_type.LanguageKind.Python |
| 64 | + |
| 65 | + @override |
| 66 | + def create_default_server(self) -> LSPServer: |
| 67 | + return LocalServer(command=["ty", "server"]) |
| 68 | + |
| 69 | + @override |
| 70 | + def create_initialization_options(self) -> dict[str, Any]: |
| 71 | + return { |
| 72 | + "logLevel": self.log_level, |
| 73 | + "diagnosticMode": self.diagnostic_mode, |
| 74 | + "inlayHints": { |
| 75 | + "variableTypes": self.inlay_hints_variable_types, |
| 76 | + "callArgumentNames": self.inlay_hints_call_argument_names, |
| 77 | + }, |
| 78 | + } |
| 79 | + |
| 80 | + @override |
| 81 | + def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None: |
| 82 | + return |
| 83 | + |
| 84 | + @override |
| 85 | + async def ensure_installed(self) -> None: |
| 86 | + if shutil.which("ty"): |
| 87 | + return |
| 88 | + |
| 89 | + logger.warning("ty not found, attempting to install via pip...") |
| 90 | + |
| 91 | + try: |
| 92 | + await anyio.run_process([sys.executable, "-m", "pip", "install", "ty"]) |
| 93 | + logger.info("Successfully installed ty via pip") |
| 94 | + return |
| 95 | + except CalledProcessError as e: |
| 96 | + raise RuntimeError( |
| 97 | + "Could not install ty. Please install it manually with 'pip install ty' or 'uv tool install ty'. " |
| 98 | + "See https://docs.astral.sh/ty/installation/ for more information." |
| 99 | + ) from e |
0 commit comments