Add TY support for python, mark LS that support implementation request

This commit is contained in:
Michael Panchenko committed 2026-03-27 10:03:05 +01:00
1 parent b5545b82b9
commit 34ed5523c8
2 files changed
+168 -1

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
"""
Python language server integration using Astral's ``ty``.
You can pass the following entries in ``ls_specific_settings["python_ty"]``:
- ls_path: Override the executable used to start ``ty``.
- ty_version: Override the pinned ``ty`` version used with ``uvx`` / ``uv x``
(default: the bundled Serena version).
"""
import logging
import os
import pathlib
import shutil
from typing import cast
from typing_extensions import override
from solidlsp.ls import LanguageServerDependencyProvider, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
TY_VERSION = "0.0.25"
class TyLanguageServer(SolidLanguageServer):
"""
Provides Python specific instantiation of the LanguageServer class using ``ty``.
"""
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Creates a TyLanguageServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
super().__init__(
config,
repository_root_path,
None,
str(config.code_language),
solidlsp_settings,
)
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
class DependencyProvider(LanguageServerDependencyProvider):
def create_launch_command(self) -> list[str]:
# respecting an explicit override
ls_path = self._custom_settings.get("ls_path")
if ls_path is not None:
return [ls_path, "server"]
ty_version = self._custom_settings.get("ty_version", TY_VERSION)
# preferring uvx for on-demand execution
uvx_path = os.environ.get("UVX") or shutil.which("uvx")
if uvx_path is not None:
return [uvx_path, "--from", f"ty=={ty_version}", "ty", "server"]
# falling back to uv's uvx-compatible subcommand when only `uv` is available
uv_path = shutil.which("uv")
if uv_path is not None:
return [uv_path, "x", "--from", f"ty=={ty_version}", "ty", "server"]
raise RuntimeError("Could not find 'uvx' or 'uv' in PATH.\nInstall uv or provide ls_specific_settings.python_ty.ls_path.")
@override
def is_ignored_dirname(self, dirname: str) -> bool:
return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"]
@override
def _get_language_id_for_file(self, relative_file_path: str) -> str:
return "python"
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
Returns the initialize params for the Ty language server.
"""
root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params = {
"processId": os.getpid(),
"clientInfo": {"name": "Serena", "version": "0.1.0"},
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"capabilities": {
"workspace": {
"workspaceEdit": {"documentChanges": True},
"didChangeConfiguration": {"dynamicRegistration": True},
"didChangeWatchedFiles": {"dynamicRegistration": True},
"symbol": {
"dynamicRegistration": True,
"symbolKind": {"valueSet": list(range(1, 27))},
},
},
"textDocument": {
"synchronization": {
"dynamicRegistration": True,
"willSave": True,
"willSaveWaitUntil": True,
"didSave": True,
},
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
"definition": {"dynamicRegistration": True, "linkSupport": True},
"references": {"dynamicRegistration": True},
"documentSymbol": {
"dynamicRegistration": True,
"symbolKind": {"valueSet": list(range(1, 27))},
"hierarchicalDocumentSymbolSupport": True,
},
"implementation": {"dynamicRegistration": True, "linkSupport": True},
"publishDiagnostics": {"relatedInformation": True},
},
},
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
}
return cast(InitializeParams, initialize_params)
def _start_server(self) -> None:
"""
Starts the Ty language server.
"""
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params: dict) -> None:
return
def window_log_message(msg: dict) -> None:
log.info("LSP: window/logMessage: %s", msg.get("message", ""))
# setting up lightweight handlers
self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("language/status", do_nothing)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
# starting and initializing the server
log.info("Starting ty language server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
log.info("Sending initialize request from LSP client to ty server and awaiting response")
init_response = self.server.send.initialize(initialize_params)
log.info("Received initialize response from ty server: %s", init_response)
capabilities = init_response["capabilities"]
assert "textDocumentSync" in capabilities
assert "definitionProvider" in capabilities
assert "referencesProvider" in capabilities
assert "documentSymbolProvider" in capabilities
# completing the initialization handshake
self.server.notify.initialized({})
+8 -1
View File
@@ -87,6 +87,8 @@ class Language(str, Enum):
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
PYTHON_JEDI = "python_jedi"
"""Jedi language server for Python (instead of pyright, which is the default)"""
PYTHON_TY = "python_ty"
"""Ty language server for Python (instead of pyright, which is the default)."""
CSHARP_OMNISHARP = "csharp_omnisharp"
"""OmniSharp language server for C# (instead of the default csharp-ls by microsoft).
Currently has problems with finding references, and generally seems less stable and performant.
@@ -154,6 +156,7 @@ class Language(str, Enum):
self.ANSIBLE,
self.TYPESCRIPT_VTS,
self.PYTHON_JEDI,
self.PYTHON_TY,
self.CSHARP_OMNISHARP,
self.RUBY_SOLARGRAPH,
self.PHP_PHPACTOR,
@@ -187,7 +190,7 @@ class Language(str, Enum):
def get_source_fn_matcher(self) -> FilenameMatcher:
match self:
case self.PYTHON | self.PYTHON_JEDI:
case self.PYTHON | self.PYTHON_JEDI | self.PYTHON_TY:
return FilenameMatcher("*.py", "*.pyi")
case self.JAVA:
return FilenameMatcher("*.java")
@@ -321,6 +324,10 @@ class Language(str, Enum):
from solidlsp.language_servers.jedi_server import JediServer
return JediServer
case self.PYTHON_TY:
from solidlsp.language_servers.ty_server import TyLanguageServer
return TyLanguageServer
case self.JAVA:
from solidlsp.language_servers.eclipse_jdtls import EclipseJDTLS