Make mypy run through on solidlsp

This commit is contained in:
MischaPanch authored and Michael Panchenko committed 2025-07-26 13:49:20 +02:00
1 parent 4b69a0ddab
commit 3a05cac93e
27 files changed
+291 -244

No files matched your search

+1 -1
View File
@@ -119,7 +119,7 @@ _black_format = "black src scripts test"
_ruff_format = "ruff check --fix src scripts test"
lint = ["_black_check", "_ruff_check"]
format = ["_ruff_format", "_black_format"]
_mypy = "mypy src/serena"
_mypy = "mypy src/serena src/solidlsp"
type-check = ["_mypy"]
[tool.ruff]
@@ -7,6 +7,7 @@ import os
import pathlib
import stat
import threading
from typing import Any, cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
@@ -126,9 +127,9 @@ class ClangdLanguageServer(SolidLanguageServer):
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Clangd Language Server, waits for the server to be ready and yields the LanguageServer instance.
@@ -142,7 +143,7 @@ class ClangdLanguageServer(SolidLanguageServer):
# LanguageServer has been shutdown
"""
def register_capability_handler(params):
def register_capability_handler(params: Any) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "workspace/executeCommand":
@@ -150,24 +151,24 @@ class ClangdLanguageServer(SolidLanguageServer):
self.resolve_main_method_available.set()
return
def lang_status_handler(params):
def lang_status_handler(params: Any) -> None:
# TODO: Should we wait for
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
# Before proceeding?
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
self.service_ready_event.set()
def execute_client_command_handler(params):
def execute_client_command_handler(params: Any) -> list:
return []
def do_nothing(params):
def do_nothing(params: Any) -> None:
return
def check_experimental_status(params):
def check_experimental_status(params: Any) -> None:
if params["quiescent"] == True:
self.server_ready.set()
def window_log_message(msg):
def window_log_message(msg: Any) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -188,7 +189,7 @@ class ClangdLanguageServer(SolidLanguageServer):
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
assert init_response["capabilities"]["completionProvider"] == {
"triggerCharacters": [".", "<", ">", ":", '"', "/", "*"],
+14 -12
View File
@@ -9,6 +9,7 @@ import shutil
import stat
import subprocess
import threading
from typing import cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
@@ -26,7 +27,7 @@ def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedP
)
def verify_clojure_cli():
def verify_clojure_cli() -> None:
install_msg = "Please install the official Clojure CLI from:\n https://clojure.org/guides/getting_started"
if shutil.which("clojure") is None:
raise FileNotFoundError("`clojure` not found.\n" + install_msg)
@@ -125,7 +126,7 @@ class ClojureLSP(SolidLanguageServer):
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""Returns the init params for clojure-lsp."""
root_uri = pathlib.Path(repository_absolute_path).as_uri()
return { # type: ignore
result = { # type: ignore
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
@@ -153,9 +154,10 @@ class ClojureLSP(SolidLanguageServer):
"trace": "off",
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
}
return cast(InitializeParams, result)
def _start_server(self):
def register_capability_handler(params):
def _start_server(self) -> None:
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "workspace/executeCommand":
@@ -163,24 +165,24 @@ class ClojureLSP(SolidLanguageServer):
self.resolve_main_method_available.set()
return
def lang_status_handler(params):
def lang_status_handler(params: dict) -> None:
# TODO: Should we wait for
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
# Before proceeding?
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
self.service_ready_event.set()
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def check_experimental_status(params):
if params["quiescent"] == True:
def check_experimental_status(params: dict) -> None:
if params["quiescent"] is True:
self.server_ready.set()
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -202,11 +204,11 @@ class ClojureLSP(SolidLanguageServer):
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
# Clojure-lsp completion provider capabilities are more flexible than other servers'
completion_provider = init_response["capabilities"]["completionProvider"]
assert completion_provider["resolveProvider"] == True
assert completion_provider["resolveProvider"] is True
assert "triggerCharacters" in completion_provider
self.server.notify.initialized({})
# after initialize, Clojure-lsp is ready to serve
+3
View File
@@ -94,6 +94,9 @@ class RuntimeDependencyCollection:
@staticmethod
def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> None:
if not dep.url:
raise ValueError(f"Dependency {dep.id} has no URL")
if dep.archive_type == "gz" and dep.binary_name:
dest = os.path.join(target_dir, dep.binary_name)
FileUtils.download_and_extract_archive(logger, dep.url, dest, dep.archive_type)
@@ -13,8 +13,9 @@ import tarfile
import threading
import urllib.request
import zipfile
from collections.abc import Iterable
from pathlib import Path
from typing import cast
from typing import Any, cast
from overrides import override
@@ -23,7 +24,7 @@ from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import LanguageServerException
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
@@ -126,7 +127,7 @@ RUNTIME_DEPENDENCIES = [
]
def breadth_first_file_scan(root_dir):
def breadth_first_file_scan(root_dir: str) -> Iterable[str]:
"""
Perform a breadth-first scan of files in the given directory.
Yields file paths in breadth-first order.
@@ -148,7 +149,7 @@ def breadth_first_file_scan(root_dir):
pass
def find_solution_or_project_file(root_dir) -> str | None:
def find_solution_or_project_file(root_dir: str) -> str | None:
"""
Find the first .sln file in breadth-first order.
If no .sln file is found, look for a .csproj file.
@@ -296,6 +297,7 @@ class CSharpLanguageServer(SolidLanguageServer):
package_version = lang_server_dep.package_version
server_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / f"{package_name}.{package_version}"
assert lang_server_dep.binary_name is not None
server_dll = server_dir / lang_server_dep.binary_name
if server_dll.exists():
@@ -304,6 +306,8 @@ class CSharpLanguageServer(SolidLanguageServer):
# Download and install the language server
logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO)
assert package_version is not None
assert package_name is not None
package_path = cls._download_nuget_package_direct(logger, package_name, package_version, solidlsp_settings)
# Extract and install
@@ -423,6 +427,7 @@ class CSharpLanguageServer(SolidLanguageServer):
# Download .NET 9 runtime using config
dotnet_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "dotnet-runtime-9.0"
assert runtime_dep.binary_name is not None
dotnet_exe = dotnet_dir / runtime_dep.binary_name
if dotnet_exe.exists():
@@ -434,6 +439,7 @@ class CSharpLanguageServer(SolidLanguageServer):
dotnet_dir.mkdir(parents=True, exist_ok=True)
url = runtime_dep.url
assert url is not None
archive_type = runtime_dep.archive_type
# Download the runtime
@@ -519,11 +525,11 @@ class CSharpLanguageServer(SolidLanguageServer):
},
)
def _start_server(self):
def do_nothing(params):
def _start_server(self) -> None:
def do_nothing(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
"""Log messages from the language server."""
message_text = msg.get("message", "")
level = msg.get("type", 4) # Default to Log level
@@ -533,7 +539,7 @@ class CSharpLanguageServer(SolidLanguageServer):
self.logger.log(f"LSP: {message_text}", level_map.get(level, logging.DEBUG))
def handle_progress(params):
def handle_progress(params: dict) -> None:
"""Handle progress notifications from the language server."""
token = params.get("token", "")
value = params.get("value", {})
@@ -567,10 +573,10 @@ class CSharpLanguageServer(SolidLanguageServer):
message = value.get("message", "Operation completed")
self.logger.log(f"Progress [{token}]: {message}", logging.INFO)
def handle_workspace_configuration(params):
def handle_workspace_configuration(params: dict) -> list:
"""Handle workspace/configuration requests from the server."""
items = params.get("items", [])
result = []
result: list[Any] = []
for item in items:
section = item.get("section", "")
@@ -613,12 +619,12 @@ class CSharpLanguageServer(SolidLanguageServer):
return result
def handle_work_done_progress_create(params):
def handle_work_done_progress_create(params: dict) -> None:
"""Handle work done progress create requests."""
# Just acknowledge the request
return
def handle_register_capability(params):
def handle_register_capability(params: dict) -> None:
"""Handle client/registerCapability requests."""
# Just acknowledge the request - we don't need to track these for now
return
@@ -683,13 +689,13 @@ class CSharpLanguageServer(SolidLanguageServer):
logging.INFO,
)
def _force_pull_diagnostics(self, init_response: dict) -> None:
def _force_pull_diagnostics(self, init_response: dict | InitializeResult) -> None:
"""
Apply the diagnostic capabilities hack.
Forces the server to support pull diagnostics.
"""
capabilities = init_response.get("capabilities", {})
diagnostic_provider = capabilities.get("diagnosticProvider", {})
diagnostic_provider: Any = capabilities.get("diagnosticProvider", {})
# Add the diagnostic capabilities hack
if isinstance(diagnostic_provider, dict):
@@ -2,12 +2,15 @@ import logging
import os
import pathlib
import stat
from typing import cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from ..ls_config import LanguageServerConfig
from ..lsp_protocol_handler.lsp_types import InitializeParams
from .common import RuntimeDependency, RuntimeDependencyCollection
@@ -16,7 +19,9 @@ class DartLanguageServer(SolidLanguageServer):
Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
"""
def __init__(self, config, logger, repository_root_path, solidlsp_settings: SolidLSPSettings):
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
) -> None:
"""
Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
"""
@@ -89,7 +94,7 @@ class DartLanguageServer(SolidLanguageServer):
return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2"
@staticmethod
def _get_initialize_params(repository_absolute_path: str):
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
Returns the initialize params for the Dart Language Server.
"""
@@ -115,23 +120,23 @@ class DartLanguageServer(SolidLanguageServer):
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Start the language server and yield when the server is ready.
"""
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
pass
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", do_nothing)
@@ -150,7 +155,7 @@ class DartLanguageServer(SolidLanguageServer):
"Sending initialize request to dart-language-server",
logging.DEBUG,
)
init_response = self.server.send_request("initialize", initialize_params)
init_response = self.server.send_request("initialize", initialize_params) # type: ignore
self.logger.log(
f"Received initialize response from dart-language-server: {init_response}",
logging.INFO,
+15 -14
View File
@@ -11,6 +11,7 @@ import stat
import threading
import uuid
from pathlib import PurePath
from typing import cast
from overrides import override
@@ -625,29 +626,29 @@ class EclipseJDTLS(SolidLanguageServer):
],
}
initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri]
initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri] # type: ignore
bundles = [self.runtime_dependency_paths.intellicode_jar_path]
initialize_params["initializationOptions"]["bundles"] = bundles
initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [
initialize_params["initializationOptions"]["bundles"] = bundles # type: ignore
initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [ # type: ignore
{"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True}
]
for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]:
for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: # type: ignore
assert "name" in runtime
assert "path" in runtime
assert os.path.exists(runtime["path"]), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist"
gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"]
gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"] # type: ignore
gradle_settings["home"] = self.runtime_dependency_paths.gradle_path
gradle_settings["java"]["home"] = self.runtime_dependency_paths.jre_path
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Eclipse JDTLS Language Server
"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "textDocument/completion":
@@ -665,22 +666,22 @@ class EclipseJDTLS(SolidLanguageServer):
self.intellicode_enable_command_available.set()
return
def lang_status_handler(params):
def lang_status_handler(params: dict) -> None:
# TODO: Should we wait for
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
# Before proceeding?
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
self.service_ready_event.set()
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
assert params["command"] == "_java.reloadBundles.command"
assert params["arguments"] == []
return []
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -700,13 +701,13 @@ class EclipseJDTLS(SolidLanguageServer):
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" not in init_response["capabilities"]
assert "executeCommandProvider" not in init_response["capabilities"]
self.server.notify.initialized({})
self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]})
self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]}) # type: ignore
self.intellicode_enable_command_available.wait()
@@ -5,6 +5,7 @@ import stat
import subprocess
import threading
import time
from typing import Any, cast
from overrides import override
@@ -47,7 +48,7 @@ class ElixirTools(SolidLanguageServer):
)
@override
def _send_references_request(self, relative_file_path: str, line: int, column: int):
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list | None:
"""Override to filter out Next LS internal files from references."""
from solidlsp.ls_utils import PathUtils
@@ -70,7 +71,7 @@ class ElixirTools(SolidLanguageServer):
return filtered_response
@classmethod
def _get_elixir_version(cls):
def _get_elixir_version(cls) -> str | None:
"""Get the installed Elixir version or None if not found."""
try:
result = subprocess.run(["elixir", "--version"], capture_output=True, text=True, check=False)
@@ -145,10 +146,12 @@ class ElixirTools(SolidLanguageServer):
dependency = runtime_deps[platform_id]
executable_path = os.path.join(next_ls_dir, "nextls")
assert dependency.binary_name is not None
binary_path = os.path.join(next_ls_dir, dependency.binary_name)
if not os.path.exists(executable_path):
logger.log(f"Downloading Next LS binary from {dependency.url}", logging.INFO)
assert dependency.url is not None
FileUtils.download_file(logger, dependency.url, binary_path)
# Make the binary executable on Unix-like systems
@@ -243,15 +246,15 @@ class ElixirTools(SolidLanguageServer):
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""Start Next LS server process"""
def register_capability_handler(params):
def register_capability_handler(params: Any) -> None:
return
def window_log_message(msg):
def window_log_message(msg: Any) -> None:
"""Handle window/logMessage notifications from Next LS"""
message_text = msg.get("message", "")
self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO)
@@ -262,10 +265,10 @@ class ElixirTools(SolidLanguageServer):
self.logger.log("Next LS runtime is ready based on official log message", logging.INFO)
self.server_ready.set()
def do_nothing(params):
def do_nothing(params: Any) -> None:
return
def check_server_ready(params):
def check_server_ready(params: Any) -> None:
"""
Handle $/progress notifications from Next LS.
Keep as fallback for error detection, but primary readiness detection
@@ -280,7 +283,7 @@ class ElixirTools(SolidLanguageServer):
self.logger.log("Next LS initialization progress completed", logging.INFO)
# Note: We don't set server_ready here - we wait for the log message
def work_done_progress(params):
def work_done_progress(params: Any) -> None:
"""
Handle $/workDoneProgress notifications from Next LS.
Keep for completeness but primary readiness detection is via window/logMessage.
+9 -8
View File
@@ -3,6 +3,7 @@ import os
import pathlib
import subprocess
import threading
from typing import cast
from overrides import override
@@ -28,7 +29,7 @@ class Gopls(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in ["vendor", "node_modules", "dist", "build"]
@staticmethod
def _get_go_version():
def _get_go_version() -> str | None:
"""Get the installed Go version or None if not found."""
try:
result = subprocess.run(["go", "version"], capture_output=True, text=True, check=False)
@@ -39,7 +40,7 @@ class Gopls(SolidLanguageServer):
return None
@staticmethod
def _get_gopls_version():
def _get_gopls_version() -> str | None:
"""Get the installed gopls version or None if not found."""
try:
result = subprocess.run(["gopls", "version"], capture_output=True, text=True, check=False)
@@ -50,7 +51,7 @@ class Gopls(SolidLanguageServer):
return None
@staticmethod
def _setup_runtime_dependency():
def _setup_runtime_dependency() -> bool:
"""
Check if required Go runtime dependencies are available.
Raises RuntimeError with helpful message if dependencies are missing.
@@ -117,18 +118,18 @@ class Gopls(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""Start gopls server process"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
self.server.on_request("client/registerCapability", register_capability_handler)
+10 -8
View File
@@ -7,6 +7,7 @@ import os
import pathlib
import shutil
from time import sleep
from typing import cast
from overrides import override
@@ -14,10 +15,11 @@ from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.ls_utils import PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, InitializeParams, LocationLink
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from ..lsp_protocol_handler import lsp_types
from .common import RuntimeDependency, RuntimeDependencyCollection
@@ -124,18 +126,18 @@ class Intelephense(SolidLanguageServer):
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""Start Intelephense server process"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -170,7 +172,7 @@ class Intelephense(SolidLanguageServer):
@override
# For some reason, the LS may need longer to process this, so we just retry
def _send_references_request(self, relative_file_path: str, line: int, column: int):
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None:
# TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is
# despite the LS having processed requests already. I don't know what causes this, but sleeping
# one second helps. It may be that sleeping only once is enough but that's hard to reliably test.
@@ -180,7 +182,7 @@ class Intelephense(SolidLanguageServer):
return super()._send_references_request(relative_file_path, line, column)
@override
def _send_definition_request(self, definition_params: DefinitionParams):
def _send_definition_request(self, definition_params: DefinitionParams) -> Definition | list[LocationLink] | None:
# TODO: same as above, also only a problem if the definition is in another file
sleep(1)
return super()._send_definition_request(definition_params)
+8 -7
View File
@@ -5,6 +5,7 @@ Provides Python specific instantiation of the LanguageServer class. Contains var
import logging
import os
import pathlib
from typing import cast
from overrides import override
@@ -150,24 +151,24 @@ class JediServer(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the JEDI Language Server
"""
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
if params["quiescent"] == True:
self.completions_available.set()
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", do_nothing)
@@ -188,7 +189,7 @@ class JediServer(SolidLanguageServer):
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
assert init_response["capabilities"]["completionProvider"] == {
"triggerCharacters": [".", "'", '"'],
@@ -7,6 +7,7 @@ import logging
import os
import pathlib
import stat
from typing import cast
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
@@ -114,7 +115,7 @@ class KotlinLanguageServer(SolidLanguageServer):
}
kotlin_dependency = runtime_dependencies["runtimeDependency"]
java_dependency = runtime_dependencies["java"][platform_id.value]
java_dependency = runtime_dependencies["java"][platform_id.value] # type: ignore
# Setup paths for dependencies
static_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "kotlin_language_server")
@@ -149,7 +150,7 @@ class KotlinLanguageServer(SolidLanguageServer):
# Download and extract Kotlin Language Server if script doesn't exist
if not os.path.exists(kotlin_script):
logger.log("Downloading Kotlin Language Server...", logging.INFO)
FileUtils.download_and_extract_archive(logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"])
FileUtils.download_and_extract_archive(logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]) # type: ignore
# Make script executable on Unix platforms
if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"):
@@ -420,20 +421,20 @@ class KotlinLanguageServer(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Kotlin Language Server
"""
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", do_nothing)
+9 -9
View File
@@ -22,7 +22,7 @@ from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
def breadth_first_file_scan(root) -> Iterable[str]:
def breadth_first_file_scan(root: str) -> Iterable[str]:
"""
This function was obtained from https://stackoverflow.com/questions/49654234/is-there-a-breadth-first-search-option-available-in-os-walk-or-equivalent-py
It traverses the directory tree in breadth first order.
@@ -48,7 +48,7 @@ def breadth_first_file_scan(root) -> Iterable[str]:
dirs = next_dirs
def find_least_depth_sln_file(root_dir) -> str | None:
def find_least_depth_sln_file(root_dir: str) -> str | None:
for filename in breadth_first_file_scan(root_dir):
if filename.endswith(".sln"):
return filename
@@ -205,12 +205,12 @@ class OmniSharp(SolidLanguageServer):
return omnisharp_executable_path, razor_omnisharp_dll_path
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Omnisharp Language Server
"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "textDocument/definition":
@@ -220,7 +220,7 @@ class OmniSharp(SolidLanguageServer):
if registration["method"] == "textDocument/completion":
self.completions_available.set()
def lang_status_handler(params):
def lang_status_handler(params: dict) -> None:
# TODO: Should we wait for
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
# Before proceeding?
@@ -228,17 +228,17 @@ class OmniSharp(SolidLanguageServer):
# self.service_ready_event.set()
pass
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
if params["quiescent"] is True:
self.server_ready.set()
def workspace_configuration_handler(params):
def workspace_configuration_handler(params: dict) -> list[dict]:
# TODO: We do not know the appropriate way to handle this request. Should ideally contact the OmniSharp dev team
return [
{
@@ -7,6 +7,7 @@ import os
import pathlib
import re
import threading
from typing import cast
from overrides import override
@@ -56,7 +57,7 @@ class PyrightServer(SolidLanguageServer):
Returns the initialize params for the Pyright Language Server.
"""
# Create basic initialization parameters
initialize_params: InitializeParams = { # type: ignore
initialize_params = { # type: ignore
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": pathlib.Path(repository_absolute_path).as_uri(),
@@ -107,9 +108,9 @@ class PyrightServer(SolidLanguageServer):
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Pyright Language Server and waits for initial workspace analysis to complete.
@@ -127,13 +128,13 @@ class PyrightServer(SolidLanguageServer):
```
"""
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
"""
Monitor Pyright's log messages to detect when initial analysis is complete.
Pyright logs "Found X source files" when it finishes scanning the workspace.
@@ -149,7 +150,7 @@ class PyrightServer(SolidLanguageServer):
self.analysis_complete.set()
self.completions_available.set()
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
"""
Also listen for experimental/serverStatus as a backup signal
"""
+10 -9
View File
@@ -7,6 +7,7 @@ import os
import pathlib
import stat
import threading
from typing import cast
from overrides import override
@@ -580,14 +581,14 @@ class RustAnalyzer(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Rust Analyzer Language Server
"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "workspace/executeCommand":
@@ -595,24 +596,24 @@ class RustAnalyzer(SolidLanguageServer):
self.resolve_main_method_available.set()
return
def lang_status_handler(params):
def lang_status_handler(params: dict) -> None:
# TODO: Should we wait for
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
# Before proceeding?
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
self.service_ready_event.set()
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
if params["quiescent"] == True:
self.server_ready.set()
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -633,7 +634,7 @@ class RustAnalyzer(SolidLanguageServer):
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
assert "completionProvider" in init_response["capabilities"]
assert init_response["capabilities"]["completionProvider"] == {
"resolveProvider": True,
+13 -12
View File
@@ -10,6 +10,7 @@ import pathlib
import stat
import subprocess
import threading
from typing import cast
from overrides import override
@@ -71,7 +72,7 @@ class Solargraph(SolidLanguageServer):
# Check if Ruby is installed
try:
result = subprocess.run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path)
ruby_version = result.stdout.strip()
ruby_version = str(result.stdout).strip()
logger.log(f"Ruby version: {ruby_version}", logging.INFO)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Error checking for Ruby installation: {e.stderr}") from e
@@ -81,16 +82,16 @@ class Solargraph(SolidLanguageServer):
# Check if solargraph is installed
try:
result = subprocess.run(
["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path
["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path # type: ignore
)
if result.stdout.strip() == "false":
if str(result.stdout).strip() == "false":
logger.log("Installing Solargraph...", logging.INFO)
subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path)
# Get the gem executable path directly
result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path)
result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path) # type: ignore
gem_path = result.stdout.strip()
bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin")
bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin") # type: ignore
executable_path = os.path.join(bin_dir, "solargraph")
if not os.path.exists(executable_path):
@@ -122,14 +123,14 @@ class Solargraph(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the Solargraph Language Server for Ruby
"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "workspace/executeCommand":
@@ -137,20 +138,20 @@ class Solargraph(SolidLanguageServer):
self.resolve_main_method_available.set()
return
def lang_status_handler(params):
def lang_status_handler(params: dict) -> None:
# TODO: Should we wait for
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
# Before proceeding?
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
self.service_ready_event.set()
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -3,6 +3,7 @@ import os
import shutil
import stat
import threading
from typing import cast
from overrides import override
@@ -27,7 +28,7 @@ class TerraformLS(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in [".terraform", "terraform.tfstate.d"]
@staticmethod
def _ensure_tf_command_available(logger: LanguageServerLogger):
def _ensure_tf_command_available(logger: LanguageServerLogger) -> None:
logger.log("Starting terraform version detection...", logging.DEBUG)
# 1. Try to find terraform using shutil.which
@@ -141,7 +142,7 @@ class TerraformLS(SolidLanguageServer):
Returns the initialize params for the Terraform Language Server.
"""
root_uri = PathUtils.path_to_uri(repository_absolute_path)
return {
result = {
"processId": os.getpid(),
"locale": "en",
"rootPath": repository_absolute_path,
@@ -166,17 +167,18 @@ class TerraformLS(SolidLanguageServer):
}
],
}
return cast(InitializeParams, result)
def _start_server(self):
def _start_server(self) -> None:
"""Start terraform-ls server process"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
self.server.on_request("client/registerCapability", register_capability_handler)
@@ -8,6 +8,7 @@ import pathlib
import shutil
import threading
from time import sleep
from typing import Any, cast
from overrides import override
@@ -19,6 +20,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from ..lsp_protocol_handler import lsp_types
from .common import RuntimeDependency, RuntimeDependencyCollection
# Platform-specific imports
@@ -26,9 +28,9 @@ if os.name != "nt": # Unix-like systems
import pwd
else:
# Dummy pwd module for Windows
class pwd:
class pwd: # type: ignore
@staticmethod
def getpwuid(uid):
def getpwuid(uid: Any) -> Any:
return type("obj", (), {"pw_name": os.environ.get("USERNAME", "unknown")})()
@@ -170,9 +172,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the TypeScript Language Server, waits for the server to be ready and yields the LanguageServer instance.
@@ -186,7 +188,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
# LanguageServer has been shutdown
"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "workspace/executeCommand":
@@ -196,16 +198,16 @@ class TypeScriptLanguageServer(SolidLanguageServer):
# self.resolve_main_method_available.set()
return
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
"""
Also listen for experimental/serverStatus as a backup signal
"""
@@ -249,7 +251,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
@override
# For some reason, the LS may need longer to process this, so we just retry
def _send_references_request(self, relative_file_path: str, line: int, column: int):
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None:
# TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is
# despite the LS having processed requests already. I don't know what causes this, but sleeping
# one second helps. It may be that sleeping only once is enough but that's hard to reliably test.
@@ -10,6 +10,7 @@ import pathlib
import shutil
import threading
from time import sleep
from typing import cast
from overrides import override
@@ -21,6 +22,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from ..lsp_protocol_handler import lsp_types
from .common import RuntimeDependency, RuntimeDependencyCollection
@@ -145,9 +147,9 @@ class VtsLanguageServer(SolidLanguageServer):
}
],
}
return initialize_params
return cast(InitializeParams, initialize_params)
def _start_server(self):
def _start_server(self) -> None:
"""
Starts the VTS Language Server, waits for the server to be ready and yields the LanguageServer instance.
@@ -161,30 +163,30 @@ class VtsLanguageServer(SolidLanguageServer):
# LanguageServer has been shutdown
"""
def register_capability_handler(params):
def register_capability_handler(params: dict) -> None:
assert "registrations" in params
for registration in params["registrations"]:
if registration["method"] == "workspace/executeCommand":
self.initialize_searcher_command_available.set()
return
def execute_client_command_handler(params):
def execute_client_command_handler(params: dict) -> list:
return []
def workspace_configuration_handler(params):
def workspace_configuration_handler(params: dict) -> list[dict] | dict:
# VTS may request workspace configuration
# Return empty configuration for each requested item
if "items" in params:
return [{}] * len(params["items"])
return {}
def do_nothing(params):
def do_nothing(params: dict) -> None:
return
def window_log_message(msg):
def window_log_message(msg: dict) -> None:
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def check_experimental_status(params):
def check_experimental_status(params: dict) -> None:
"""
Also listen for experimental/serverStatus as a backup signal
"""
@@ -233,6 +235,6 @@ class VtsLanguageServer(SolidLanguageServer):
# VTS may need longer to process references, so we include a sleep similar to TypeScript LS
@override
def _send_references_request(self, relative_file_path: str, line: int, column: int):
def _send_references_request(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location] | None:
sleep(1)
return super()._send_references_request(relative_file_path, line, column)
+60 -54
View File
@@ -14,7 +14,7 @@ from collections.abc import Iterator
from contextlib import contextmanager
from copy import copy
from pathlib import Path, PurePath
from typing import Self, Union, cast
from typing import Any, Self, Union, cast
import pathspec
@@ -72,7 +72,7 @@ class LSPFileBuffer:
content_hash: str = ""
def __post_init__(self):
def __post_init__(self) -> None:
self.content_hash = hashlib.md5(self.contents.encode("utf-8")).hexdigest()
@@ -227,7 +227,7 @@ class SolidLanguageServer(ABC):
ls = TerraformLS(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
else:
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
logger.log(f"Language {config.code_language} is not supported", logging.ERROR) # type: ignore
raise LanguageServerException(f"Language {config.code_language} is not supported")
ls.set_request_timeout(timeout)
@@ -280,11 +280,11 @@ class SolidLanguageServer(ABC):
self.completions_available = threading.Event()
if config.trace_lsp_communication:
def logging_fn(source: str, target: str, msg: StringDict | str):
def logging_fn(source: str, target: str, msg: StringDict | str) -> None:
self.logger.log(f"LSP: {source} -> {target}: {str(msg)[:90]}...", self.logger.logger.level)
else:
logging_fn = None
logging_fn = None # type: ignore
# cmd is obtained from the child classes, which provide the language specific command to start the language server
# LanguageServerHandler provides the functionality to start the language server and communicate with it
@@ -366,7 +366,7 @@ class SolidLanguageServer(ABC):
return match_path(relative_path, self.get_ignore_spec(), root_path=self.repository_root_path)
def _shutdown(self, timeout: float = 5.0):
def _shutdown(self, timeout: float = 5.0) -> None:
"""
A robust shutdown process designed to terminate cleanly on all platforms, including Windows,
by explicitly closing all I/O pipes.
@@ -377,6 +377,9 @@ class SolidLanguageServer(ABC):
self.logger.log(f"Initiating final robust shutdown with a {timeout}s timeout...", logging.INFO)
process = self.server.process
if process is None:
self.logger.log("Server process is None, cannot shutdown.", logging.DEBUG)
return
# --- Main Shutdown Logic ---
# Stage 1: Graceful Termination Request
@@ -394,7 +397,7 @@ class SolidLanguageServer(ABC):
else:
self.logger.log("LSP shutdown request completed.", logging.DEBUG)
if process.stdin and not process.stdin.is_closing():
if process.stdin and not process.stdin.closed:
process.stdin.close()
self.logger.log("Stage 1 shutdown complete.", logging.DEBUG)
except Exception as e:
@@ -428,12 +431,12 @@ class SolidLanguageServer(ABC):
yield self
self.stop()
def _start_server_process(self) -> None:
def _start_server_process(self) -> Any | None:
self.server_started = True
self._start_server()
return self._start_server()
@abstractmethod
def _start_server(self):
def _start_server(self) -> None:
pass
@contextmanager
@@ -468,7 +471,7 @@ class SolidLanguageServer(ABC):
self.server.notify.did_open_text_document(
{
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.URI: uri,
LSPConstants.LANGUAGE_ID: self.language_id,
LSPConstants.VERSION: 0,
@@ -482,7 +485,7 @@ class SolidLanguageServer(ABC):
if self.open_file_buffers[uri].ref_count == 0:
self.server.notify.did_close_text_document(
{
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.URI: uri,
}
}
@@ -519,7 +522,7 @@ class SolidLanguageServer(ABC):
file_buffer.contents = new_contents
self.server.notify.did_change_text_document(
{
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.VERSION: file_buffer.version,
LSPConstants.URI: file_buffer.uri,
},
@@ -566,7 +569,7 @@ class SolidLanguageServer(ABC):
file_buffer.contents = new_contents
self.server.notify.did_change_text_document(
{
LSPConstants.TEXT_DOCUMENT: {
LSPConstants.TEXT_DOCUMENT: { # type: ignore
LSPConstants.VERSION: file_buffer.version,
LSPConstants.URI: file_buffer.uri,
},
@@ -618,23 +621,23 @@ class SolidLanguageServer(ABC):
for item in response:
assert isinstance(item, dict)
if LSPConstants.URI in item and LSPConstants.RANGE in item:
new_item: ls_types.Location = {}
new_item: dict = {}
new_item.update(item)
new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"])
new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path)
ret.append(ls_types.Location(new_item))
ret.append(ls_types.Location(new_item)) # type: ignore
elif (
LSPConstants.ORIGIN_SELECTION_RANGE in item
and LSPConstants.TARGET_URI in item
and LSPConstants.TARGET_RANGE in item
and LSPConstants.TARGET_SELECTION_RANGE in item
):
new_item: ls_types.Location = {}
new_item["uri"] = item[LSPConstants.TARGET_URI]
new_item = {}
new_item["uri"] = item[LSPConstants.TARGET_URI] # type: ignore
new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"])
new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path)
new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE]
ret.append(ls_types.Location(**new_item))
new_item["range"] = item[LSPConstants.TARGET_SELECTION_RANGE] # type: ignore
ret.append(ls_types.Location(**new_item)) # type: ignore
else:
assert False, f"Unexpected response from Language Server: {item}"
elif isinstance(response, dict):
@@ -642,11 +645,11 @@ class SolidLanguageServer(ABC):
assert LSPConstants.URI in response
assert LSPConstants.RANGE in response
new_item: ls_types.Location = {}
new_item = {}
new_item.update(response)
new_item["absolutePath"] = PathUtils.uri_to_path(new_item["uri"])
new_item["relativePath"] = PathUtils.get_relative_path(new_item["absolutePath"], self.repository_root_path)
ret.append(ls_types.Location(**new_item))
ret.append(ls_types.Location(**new_item)) # type: ignore
elif response is None:
# Some language servers return None when they cannot find a definition
# This is expected for certain symbol types like generics or types with incomplete information
@@ -709,7 +712,7 @@ class SolidLanguageServer(ABC):
assert LSPConstants.URI in item
assert LSPConstants.RANGE in item
abs_path = PathUtils.uri_to_path(item[LSPConstants.URI])
abs_path = PathUtils.uri_to_path(item[LSPConstants.URI]) # type: ignore
if not Path(abs_path).is_relative_to(self.repository_root_path):
self.logger.log(
"Found a reference in a path outside the repository, probably the LS is parsing things in installed packages or in the standardlib! "
@@ -723,11 +726,11 @@ class SolidLanguageServer(ABC):
self.logger.log(f"Ignoring reference in {rel_path} since it should be ignored", logging.DEBUG)
continue
new_item: ls_types.Location = {}
new_item: dict = {}
new_item.update(item)
new_item["absolutePath"] = str(abs_path)
new_item["relativePath"] = str(rel_path)
ret.append(ls_types.Location(**new_item))
ret.append(ls_types.Location(**new_item)) # type: ignore
return ret
@@ -786,19 +789,19 @@ class SolidLanguageServer(ABC):
response: list[LSPTypes.CompletionItem] | LSPTypes.CompletionList | None = None
num_retries = 0
while response is None or (response["isIncomplete"] and num_retries < 30):
while response is None or (response["isIncomplete"] and num_retries < 30): # type: ignore
self.completions_available.wait()
response: list[LSPTypes.CompletionItem] | LSPTypes.CompletionList | None = self.server.send.completion(completion_params)
response = self.server.send.completion(completion_params)
if isinstance(response, list):
response = {"items": response, "isIncomplete": False}
num_retries += 1
# TODO: Understand how to appropriately handle `isIncomplete`
if response is None or (response["isIncomplete"] and not (allow_incomplete)):
if response is None or (response["isIncomplete"] and not allow_incomplete): # type: ignore
return []
if "items" in response:
response = response["items"]
response = response["items"] # type: ignore
response = cast(list[LSPTypes.CompletionItem], response)
@@ -816,8 +819,8 @@ class SolidLanguageServer(ABC):
if "label" in item:
completion_item["completionText"] = item["label"]
completion_item["kind"] = item["kind"]
elif "insertText" in item:
completion_item["kind"] = item["kind"] # type: ignore
elif "insertText" in item: # type: ignore
completion_item["completionText"] = item["insertText"]
completion_item["kind"] = item["kind"]
elif "textEdit" in item and "newText" in item["textEdit"]:
@@ -844,7 +847,7 @@ class SolidLanguageServer(ABC):
else:
assert False
completion_item = ls_types.CompletionItem(**completion_item)
completion_item = ls_types.CompletionItem(**completion_item) # type: ignore
completions_list.append(completion_item)
return [json.loads(json_repr) for json_repr in set(json.dumps(item, sort_keys=True) for item in completions_list)]
@@ -899,7 +902,7 @@ class SolidLanguageServer(ABC):
logging.DEBUG,
)
def turn_item_into_symbol_with_children(item: GenericDocumentSymbol):
def turn_item_into_symbol_with_children(item: GenericDocumentSymbol) -> None:
item = cast(ls_types.UnifiedSymbolInformation, item)
absolute_path = os.path.join(self.repository_root_path, relative_file_path)
@@ -916,9 +919,9 @@ class SolidLanguageServer(ABC):
item["location"] = tree_location
location = item["location"]
if "absolutePath" not in location:
location["absolutePath"] = absolute_path
location["absolutePath"] = absolute_path # type: ignore
if "relativePath" not in location:
location["relativePath"] = relative_file_path
location["relativePath"] = relative_file_path # type: ignore
if include_body:
item["body"] = self.retrieve_symbol_body(item)
# handle missing selectionRange
@@ -927,16 +930,16 @@ class SolidLanguageServer(ABC):
item["selectionRange"] = item["range"]
else:
item["selectionRange"] = item["location"]["range"]
children = item.get(LSPConstants.CHILDREN, [])
for child in children:
children = item.get(LSPConstants.CHILDREN, []) # type: ignore
for child in children: # type: ignore
child["parent"] = item
item[LSPConstants.CHILDREN] = children
item[LSPConstants.CHILDREN] = children # type: ignore
flat_all_symbol_list: list[ls_types.UnifiedSymbolInformation] = []
root_nodes: list[ls_types.UnifiedSymbolInformation] = []
for root_item in response:
if "range" not in root_item and "location" not in root_item:
if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]:
if root_item["kind"] in [SymbolKind.File, SymbolKind.Module]: # type: ignore
...
# mutation is more convenient than creating a new dict,
@@ -959,7 +962,7 @@ class SolidLanguageServer(ABC):
l: list[ls_types.UnifiedSymbolInformation] = []
turn_item_into_symbol_with_children(node)
assert LSPConstants.CHILDREN in node
children = node[LSPConstants.CHILDREN]
children = node[LSPConstants.CHILDREN] # type: ignore
l.append(node)
for child in children:
l.extend(visit_tree_nodes_and_build_tree_repr(child))
@@ -1082,10 +1085,10 @@ class SolidLanguageServer(ABC):
package_symbol["children"].append(file_symbol)
# TODO: Not sure if this is actually still needed given recent changes to relative path handling
def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]):
def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]) -> None:
for node in nodes:
if "location" in node and "relativePath" in node["location"]:
path = Path(node["location"]["relativePath"])
path = Path(node["location"]["relativePath"]) # type: ignore
if path.is_absolute():
try:
path = path.relative_to(self.repository_root_path)
@@ -1125,7 +1128,7 @@ class SolidLanguageServer(ABC):
result: dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]] = defaultdict(list)
# Helper function to process a symbol and its children
def process_symbol(symbol: ls_types.UnifiedSymbolInformation):
def process_symbol(symbol: ls_types.UnifiedSymbolInformation) -> None:
if symbol["kind"] == ls_types.SymbolKind.File:
# For file symbols, process their children (top-level symbols)
for child in symbol["children"]:
@@ -1209,7 +1212,7 @@ class SolidLanguageServer(ABC):
assert isinstance(response, dict)
return ls_types.Hover(**response)
return ls_types.Hover(**response) # type: ignore
def retrieve_symbol_body(self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation) -> str:
"""
@@ -1217,18 +1220,18 @@ class SolidLanguageServer(ABC):
"""
existing_body = symbol.get("body", None)
if existing_body:
return existing_body
return str(existing_body)
assert "location" in symbol
symbol_start_line = symbol["location"]["range"]["start"]["line"]
symbol_end_line = symbol["location"]["range"]["end"]["line"]
assert "relativePath" in symbol["location"]
symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"])
symbol_start_line = symbol["location"]["range"]["start"]["line"] # type: ignore
symbol_end_line = symbol["location"]["range"]["end"]["line"] # type: ignore
assert "relativePath" in symbol["location"] # type: ignore
symbol_file = self.retrieve_full_file_content(symbol["location"]["relativePath"]) # type: ignore
symbol_lines = symbol_file.split("\n")
symbol_body = "\n".join(symbol_lines[symbol_start_line : symbol_end_line + 1])
# remove leading indentation
symbol_start_column = symbol["location"]["range"]["start"]["character"]
symbol_start_column = symbol["location"]["range"]["start"]["character"] # type: ignore
symbol_body = symbol_body[symbol_start_column:]
return symbol_body
@@ -1277,6 +1280,7 @@ class SolidLanguageServer(ABC):
incoming_symbol = None
for ref in references:
ref_path = ref["relativePath"]
assert ref_path is not None
ref_line = ref["range"]["start"]["line"]
ref_col = ref["range"]["start"]["character"]
@@ -1300,6 +1304,7 @@ class SolidLanguageServer(ABC):
ref_text = file_data.contents.split("\n")[ref_line]
if "." in ref_text:
containing_symbol_name = ref_text.split(".")[0]
assert ref_path is not None
all_symbols, _ = self.request_document_symbols(ref_path)
for symbol in all_symbols:
if symbol["name"] == containing_symbol_name and symbol["kind"] == ls_types.SymbolKind.Variable:
@@ -1506,7 +1511,7 @@ class SolidLanguageServer(ABC):
return symbol["parent"]
assert "location" in symbol, f"Symbol {symbol} has no location and no parent attribute"
return self.request_containing_symbol(
symbol["location"]["relativePath"],
symbol["location"]["relativePath"], # type: ignore
symbol["location"]["range"]["start"]["line"],
symbol["location"]["range"]["start"]["character"],
strict=True,
@@ -1547,6 +1552,7 @@ class SolidLanguageServer(ABC):
# Use the first definition location
definition = definitions[0]
def_path = definition["relativePath"]
assert def_path is not None
def_line = definition["range"]["start"]["line"]
def_col = definition["range"]["start"]["character"]
@@ -1562,7 +1568,7 @@ class SolidLanguageServer(ABC):
"""
return Path(self.repository_root_path) / ".serena" / "cache" / self.language_id / "document_symbols_cache_v23-06-25.pkl"
def save_cache(self):
def save_cache(self) -> None:
with self._cache_lock:
if not self._cache_has_changed:
self.logger.log("No changes to document symbols cache, skipping save", logging.DEBUG)
@@ -1581,7 +1587,7 @@ class SolidLanguageServer(ABC):
logging.ERROR,
)
def load_cache(self):
def load_cache(self) -> None:
if not self.cache_path.exists():
return
@@ -1622,7 +1628,7 @@ class SolidLanguageServer(ABC):
assert LSPConstants.KIND in item
assert LSPConstants.LOCATION in item
ret.append(ls_types.UnifiedSymbolInformation(**item))
ret.append(ls_types.UnifiedSymbolInformation(**item)) # type: ignore
return ret
+1 -1
View File
@@ -120,7 +120,7 @@ class LanguageServerConfig:
"""Paths, dirs or glob-like patterns. The matching will follow the same logic as for .gitignore entries"""
@classmethod
def from_dict(cls, env: dict):
def from_dict(cls, env: dict) -> Self:
"""
Create a MultilspyConfig instance from a dictionary
"""
+14 -14
View File
@@ -46,7 +46,7 @@ class Request:
return self.error is not None
def __init__(self) -> None:
self._result_queue = Queue()
self._result_queue: Queue[Request.Result] = Queue()
def on_result(self, params: PayloadLike) -> None:
self._result_queue.put(Request.Result(payload=params))
@@ -101,22 +101,22 @@ class SolidLanguageServerHandler:
self,
process_launch_info: ProcessLaunchInfo,
logger: Callable[[str, str, StringDict | str], None] | None = None,
start_independent_lsp_process=True,
start_independent_lsp_process: bool = True,
request_timeout: float | None = None,
) -> None:
self.send = LanguageServerRequest(self.send_request)
self.notify = LspNotification(self.send_notification)
self.process_launch_info = process_launch_info
self.process = None
self.process: subprocess.Popen[bytes] | None = None
self._received_shutdown = False
self.request_id = 1
self._response_handlers: dict[Any, Request] = {}
self.on_request_handlers = {}
self.on_notification_handlers = {}
self.on_request_handlers: dict[str, Callable[[Any], Any]] = {}
self.on_notification_handlers: dict[str, Callable[[Any], None]] = {}
self.logger = logger
self.tasks = {}
self.tasks: dict[int, Any] = {}
self.task_counter = 0
self.loop = None
self.start_independent_lsp_process = start_independent_lsp_process
@@ -170,7 +170,7 @@ class SolidLanguageServerHandler:
if self.process.returncode is not None:
log.error("Language server has already terminated/could not be started")
# Process has already terminated
stderr_data = self.process.stderr.read()
stderr_data = self.process.stderr.read() if self.process.stderr else b""
error_message = stderr_data.decode("utf-8", errors="replace")
raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}")
@@ -195,7 +195,7 @@ class SolidLanguageServerHandler:
if process:
self._cleanup_process(process)
def _cleanup_process(self, process):
def _cleanup_process(self, process: subprocess.Popen[bytes]) -> None:
"""Clean up a process: close stdin, terminate/kill process, close stdout/stderr."""
# Close stdin first to prevent deadlocks
# See: https://bugs.python.org/issue35539
@@ -212,7 +212,7 @@ class SolidLanguageServerHandler:
self._safely_close_pipe(process.stdout)
self._safely_close_pipe(process.stderr)
def _safely_close_pipe(self, pipe):
def _safely_close_pipe(self, pipe: Any) -> None:
"""Safely close a pipe, ignoring any exceptions."""
if pipe:
try:
@@ -220,7 +220,7 @@ class SolidLanguageServerHandler:
except Exception:
pass
def _terminate_or_kill_process(self, process):
def _terminate_or_kill_process(self, process: subprocess.Popen[bytes]) -> None:
"""Try to terminate the process gracefully, then forcefully if necessary."""
# First try to terminate the process tree gracefully
self._signal_process_tree(process, terminate=True)
@@ -240,7 +240,7 @@ class SolidLanguageServerHandler:
pass
"""
def _signal_process_tree(self, process, terminate=True):
def _signal_process_tree(self, process: subprocess.Popen[bytes], terminate: bool = True) -> None:
"""Send signal (terminate or kill) to the process and all its children."""
signal_method = "terminate" if terminate else "kill"
@@ -300,7 +300,7 @@ class SolidLanguageServerHandler:
self.logger("client", "logger", message)
@staticmethod
def _read_bytes_from_process(process, stream, num_bytes):
def _read_bytes_from_process(process: subprocess.Popen[bytes], stream: Any, num_bytes: int) -> bytes:
"""Read exactly num_bytes from process stdout"""
if process.poll() is not None:
# Process has terminated, check if we can still read
@@ -457,13 +457,13 @@ class SolidLanguageServerHandler:
self.logger("client", "logger", f"Failed to write to stdin: {e}")
return
def on_request(self, method: str, cb) -> None:
def on_request(self, method: str, cb: Callable[[Any], Any]) -> None:
"""
Register the callback function to handle requests from the server to the client for the given method
"""
self.on_request_handlers[method] = cb
def on_notification(self, method: str, cb) -> None:
def on_notification(self, method: str, cb: Callable[[Any], None]) -> None:
"""
Register the callback function to handle notifications from the server to the client for the given method
"""
+4 -3
View File
@@ -1,10 +1,11 @@
from typing import Union
from collections.abc import Callable
from typing import Any, Union
from solidlsp.lsp_protocol_handler import lsp_types
class LanguageServerRequest:
def __init__(self, send_request):
def __init__(self, send_request: Callable[[str, Any], Any]) -> None:
self.send_request = send_request
def implementation(self, params: lsp_types.ImplementationParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
@@ -218,7 +219,7 @@ class LanguageServerRequest:
server. The only notification that is sent after a shutdown request
is the exit event.
"""
return self.send_request("shutdown")
return self.send_request("shutdown", None)
def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None:
"""A document will save request is sent from the client to the server before
+10 -5
View File
@@ -125,10 +125,15 @@ class PathUtils:
from urllib.parse import unquote, urlparse
from urllib.request import url2pathname
except ImportError:
# backwards compatibility
from urllib import unquote, url2pathname
# backwards compatibility (Python 2)
from urllib.parse import unquote as unquote_py2
from urllib.request import url2pathname as url2pathname_py2
from urlparse import urlparse
from urlparse import urlparse as urlparse_py2
unquote = unquote_py2
url2pathname = url2pathname_py2
urlparse = urlparse_py2
parsed = urlparse(uri)
host = f"{os.path.sep}{os.path.sep}{parsed.netloc}{os.path.sep}"
path = os.path.normpath(os.path.join(host, url2pathname(unquote(parsed.path))))
@@ -290,7 +295,7 @@ class PlatformUtils:
raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
@staticmethod
def _determine_windows_machine_type():
def _determine_windows_machine_type() -> str:
import ctypes
from ctypes import wintypes
@@ -317,7 +322,7 @@ class PlatformUtils:
_anonymous_ = ("u",)
sys_info = SYSTEM_INFO()
ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info))
ctypes.windll.kernel32.GetNativeSystemInfo(ctypes.byref(sys_info)) # type: ignore
arch_map = {
9: "AMD64",
@@ -29,13 +29,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from typing import Union
from typing import Any, Union
from solidlsp.lsp_protocol_handler import lsp_types
class LspRequest:
def __init__(self, send_request):
def __init__(self, send_request: Any) -> None:
self.send_request = send_request
async def implementation(
@@ -421,7 +421,7 @@ class LspRequest:
class LspNotification:
def __init__(self, send_notification):
def __init__(self, send_notification: Any) -> None:
self.send_notification = send_notification
def did_change_workspace_folders(self, params: lsp_types.DidChangeWorkspaceFoldersParams) -> None:
+2 -2
View File
@@ -95,7 +95,7 @@ class StopLoopException(Exception):
pass
def create_message(payload: PayloadLike):
def create_message(payload: PayloadLike) -> tuple[bytes, bytes, bytes]:
body = json.dumps(payload, check_circular=False, ensure_ascii=False, separators=(",", ":")).encode(ENCODING)
return (
f"Content-Length: {len(body)}\r\n".encode(ENCODING),
@@ -118,5 +118,5 @@ def content_length(line: bytes) -> int | None:
try:
return int(value)
except ValueError:
raise ValueError(f"Invalid Content-Length header: {value}")
raise ValueError(f"Invalid Content-Length header: {value!r}")
return None
+2 -2
View File
@@ -11,10 +11,10 @@ from dataclasses import dataclass
class SolidLSPSettings:
solidlsp_dir: str = str(pathlib.Path.home() / ".solidlsp")
def __post_init__(self):
def __post_init__(self) -> None:
os.makedirs(str(self.solidlsp_dir), exist_ok=True)
os.makedirs(str(self.ls_resources_dir), exist_ok=True)
@property
def ls_resources_dir(self):
def ls_resources_dir(self) -> str:
return os.path.join(str(self.solidlsp_dir), "language_servers", "static")