From f182db59e3479f84c17273815a89ea12ec94d65b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 23 Jun 2025 17:40:29 +0200 Subject: [PATCH] Rename language server modules and classes --- src/serena/agent.py | 10 +- src/serena/symbol.py | 2 +- src/serena/util/inspection.py | 2 +- .../clangd_language_server.py | 10 +- .../dart_language_server.py | 6 +- .../eclipse_jdtls/eclipse_jdtls.py | 16 +- src/solidlsp/language_servers/gopls/gopls.py | 6 +- .../intelephense/intelephense.py | 10 +- .../jedi_language_server/jedi_server.py | 6 +- .../kotlin_language_server.py | 10 +- .../language_servers/omnisharp/omnisharp.py | 14 +- .../pyright_language_server/pyright_server.py | 6 +- .../rust_analyzer/rust_analyzer.py | 10 +- .../language_servers/solargraph/solargraph.py | 8 +- .../typescript_language_server.py | 10 +- src/solidlsp/ls.py | 172 +++--- .../{multilspy_config.py => ls_config.py} | 2 +- ...ltilspy_exceptions.py => ls_exceptions.py} | 2 +- src/solidlsp/ls_handler.py | 8 +- .../{multilspy_logger.py => ls_logger.py} | 2 +- .../{lsp_request.py => ls_request.py} | 2 +- .../{multilspy_types.py => ls_types.py} | 0 .../{multilspy_utils.py => ls_utils.py} | 32 +- src/solidlsp/lsp_protocol_handler/server.py | 517 +----------------- .../{multilspy_settings.py => settings.py} | 2 +- src/solidlsp/type_helpers.py | 28 - test/conftest.py | 8 +- test/multilspy/go/test_go_basic.py | 4 +- test/multilspy/java/test_java_basic.py | 4 +- test/multilspy/php/test_php_basic.py | 2 +- test/multilspy/python/test_python_basic.py | 2 +- .../test_retrieval_with_ignored_dirs.py | 2 +- .../multilspy/python/test_symbol_retrieval.py | 4 +- test/multilspy/rust/test_rust_basic.py | 4 +- .../typescript/test_typescript_basic.py | 4 +- test/serena/test_serena_agent.py | 2 +- test/serena/test_symbol_editing.py | 2 +- 37 files changed, 194 insertions(+), 737 deletions(-) rename src/solidlsp/{multilspy_config.py => ls_config.py} (98%) rename src/solidlsp/{multilspy_exceptions.py => ls_exceptions.py} (87%) rename src/solidlsp/{multilspy_logger.py => ls_logger.py} (98%) rename src/solidlsp/{lsp_request.py => ls_request.py} (99%) rename src/solidlsp/{multilspy_types.py => ls_types.py} (100%) rename src/solidlsp/{multilspy_utils.py => ls_utils.py} (91%) rename src/solidlsp/{multilspy_settings.py => settings.py} (96%) delete mode 100644 src/solidlsp/type_helpers.py diff --git a/src/serena/agent.py b/src/serena/agent.py index 965dbdf..9fea8ec 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -52,9 +52,9 @@ from serena.util.inspection import determine_programming_language_composition, i from serena.util.shell import execute_shell_command from serena.util.thread import ExecutionResult, execute_with_timeout from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language, MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_types import SymbolKind +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_types import SymbolKind if TYPE_CHECKING: from serena.gui_log_viewer import GuiLogViewerHandler @@ -669,12 +669,12 @@ def create_ls_for_project( log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") ignored_paths.extend(spec.patterns) log.debug(f"Using {len(ignored_paths)} ignored paths in total.") - multilspy_config = MultilspyConfig( + multilspy_config = LanguageServerConfig( code_language=project_instance.language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication, ) - ls_logger = MultilspyLogger(log_level=log_level) + ls_logger = LanguageServerLogger(log_level=log_level) log.info(f"Creating language server instance for {project_instance.project_root}.") return SolidLanguageServer.create( multilspy_config, diff --git a/src/serena/symbol.py b/src/serena/symbol.py index e4da274..e039440 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -11,7 +11,7 @@ from sensai.util.string import ToStringMixin from solidlsp import SolidLanguageServer from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol -from solidlsp.multilspy_types import Position, SymbolKind, UnifiedSymbolInformation +from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation if TYPE_CHECKING: from .agent import SerenaAgent diff --git a/src/serena/util/inspection.py b/src/serena/util/inspection.py index 4847fd1..24c2171 100644 --- a/src/serena/util/inspection.py +++ b/src/serena/util/inspection.py @@ -4,7 +4,7 @@ from collections.abc import Generator from typing import TypeVar from serena.util.file_system import find_all_non_ignored_files -from solidlsp.multilspy_config import Language +from solidlsp.ls_config import Language T = TypeVar("T") diff --git a/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py index a61ba13..4572de9 100644 --- a/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server/clangd_language_server.py @@ -12,9 +12,9 @@ import threading from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import FileUtils, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils class ClangdLanguageServer(SolidLanguageServer): @@ -24,7 +24,7 @@ class ClangdLanguageServer(SolidLanguageServer): Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -41,7 +41,7 @@ class ClangdLanguageServer(SolidLanguageServer): self.initialize_searcher_command_available = threading.Event() self.resolve_main_method_available = threading.Event() - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for ClangdLanguageServer. """ diff --git a/src/solidlsp/language_servers/dart_language_server/dart_language_server.py b/src/solidlsp/language_servers/dart_language_server/dart_language_server.py index bf40699..5957558 100644 --- a/src/solidlsp/language_servers/dart_language_server/dart_language_server.py +++ b/src/solidlsp/language_servers/dart_language_server/dart_language_server.py @@ -6,8 +6,8 @@ import stat from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import FileUtils, PlatformUtils +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils class DartLanguageServer(SolidLanguageServer): @@ -28,7 +28,7 @@ class DartLanguageServer(SolidLanguageServer): "dart", ) - def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str: + def setup_runtime_dependencies(self, logger: "LanguageServerLogger") -> str: platform_id = PlatformUtils.get_platform_id() with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json")) as f: diff --git a/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py index bd63594..a2453fa 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls/eclipse_jdtls.py @@ -18,10 +18,10 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_settings import MultilspySettings -from solidlsp.multilspy_utils import FileUtils, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.settings import SolidLSPSettings +from solidlsp.ls_utils import FileUtils, PlatformUtils @dataclasses.dataclass @@ -45,7 +45,7 @@ class EclipseJDTLS(SolidLanguageServer): The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a new EclipseJDTLS instance initializing the language server settings appropriately. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. @@ -56,7 +56,7 @@ class EclipseJDTLS(SolidLanguageServer): # ws_dir is the workspace directory for the EclipseJDTLS server ws_dir = str( PurePath( - MultilspySettings.get_language_server_directory(), + SolidLSPSettings.get_language_server_directory(), "EclipseJDTLS", "workspaces", uuid.uuid4().hex, @@ -64,7 +64,7 @@ class EclipseJDTLS(SolidLanguageServer): ) # shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces - shared_cache_location = str(PurePath(MultilspySettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex")) + shared_cache_location = str(PurePath(SolidLSPSettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex")) jre_path = self.runtime_dependency_paths.jre_path lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path @@ -153,7 +153,7 @@ class EclipseJDTLS(SolidLanguageServer): "lib", # General ] - def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> RuntimeDependencyPaths: + def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> RuntimeDependencyPaths: """ Setup runtime dependencies for EclipseJDTLS. """ diff --git a/src/solidlsp/language_servers/gopls/gopls.py b/src/solidlsp/language_servers/gopls/gopls.py index cd7f6e0..32ae5e5 100644 --- a/src/solidlsp/language_servers/gopls/gopls.py +++ b/src/solidlsp/language_servers/gopls/gopls.py @@ -10,8 +10,8 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class Gopls(SolidLanguageServer): @@ -71,7 +71,7 @@ class Gopls(SolidLanguageServer): return True - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): self.setup_runtime_dependency() super().__init__( diff --git a/src/solidlsp/language_servers/intelephense/intelephense.py b/src/solidlsp/language_servers/intelephense/intelephense.py index 7e7e8db..a02f9d1 100644 --- a/src/solidlsp/language_servers/intelephense/intelephense.py +++ b/src/solidlsp/language_servers/intelephense/intelephense.py @@ -15,9 +15,9 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import PlatformId, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import PlatformId, PlatformUtils class Intelephense(SolidLanguageServer): @@ -33,7 +33,7 @@ class Intelephense(SolidLanguageServer): # - cache: commonly used for caching return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for Intelephense. """ @@ -97,7 +97,7 @@ class Intelephense(SolidLanguageServer): return f"{intelephense_executable_path} --stdio" - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): # Setup runtime dependencies before initializing intelephense_cmd = self.setup_runtime_dependencies(logger, config) diff --git a/src/solidlsp/language_servers/jedi_language_server/jedi_server.py b/src/solidlsp/language_servers/jedi_language_server/jedi_server.py index 9ec7554..a0ea203 100644 --- a/src/solidlsp/language_servers/jedi_language_server/jedi_server.py +++ b/src/solidlsp/language_servers/jedi_language_server/jedi_server.py @@ -12,8 +12,8 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class JediServer(SolidLanguageServer): @@ -21,7 +21,7 @@ class JediServer(SolidLanguageServer): Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ diff --git a/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py b/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py index cf4022c..3031535 100644 --- a/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py +++ b/src/solidlsp/language_servers/kotlin_language_server/kotlin_language_server.py @@ -12,9 +12,9 @@ import stat from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import FileUtils, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils @dataclasses.dataclass @@ -33,7 +33,7 @@ class KotlinLanguageServer(SolidLanguageServer): Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a Kotlin Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -54,7 +54,7 @@ class KotlinLanguageServer(SolidLanguageServer): "kotlin", ) - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> KotlinRuntimeDependencyPaths: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> KotlinRuntimeDependencyPaths: """ Setup runtime dependencies for Kotlin Language Server. """ diff --git a/src/solidlsp/language_servers/omnisharp/omnisharp.py b/src/solidlsp/language_servers/omnisharp/omnisharp.py index b8b33ea..0a0216c 100644 --- a/src/solidlsp/language_servers/omnisharp/omnisharp.py +++ b/src/solidlsp/language_servers/omnisharp/omnisharp.py @@ -15,10 +15,10 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_exceptions import MultilspyException -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_exceptions import LanguageServerException +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils def breadth_first_file_scan(root) -> Iterable[str]: @@ -59,7 +59,7 @@ class OmniSharp(SolidLanguageServer): Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -68,7 +68,7 @@ class OmniSharp(SolidLanguageServer): slnfilename = find_least_depth_sln_file(repository_root_path) if slnfilename is None: logger.log("No *.sln file found in repository", logging.ERROR) - raise MultilspyException("No SLN file found in repository") + raise LanguageServerException("No SLN file found in repository") cmd = " ".join( [ @@ -136,7 +136,7 @@ class OmniSharp(SolidLanguageServer): return d - def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> tuple[str, str]: + def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> tuple[str, str]: """ Setup runtime dependencies for OmniSharp. """ diff --git a/src/solidlsp/language_servers/pyright_language_server/pyright_server.py b/src/solidlsp/language_servers/pyright_language_server/pyright_server.py index b7ddd07..64dbd57 100644 --- a/src/solidlsp/language_servers/pyright_language_server/pyright_server.py +++ b/src/solidlsp/language_servers/pyright_language_server/pyright_server.py @@ -13,8 +13,8 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class PyrightServer(SolidLanguageServer): @@ -23,7 +23,7 @@ class PyrightServer(SolidLanguageServer): Contains various configurations and settings specific to Python. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a PyrightServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. diff --git a/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py index d88cc59..a21768e 100644 --- a/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py +++ b/src/solidlsp/language_servers/rust_analyzer/rust_analyzer.py @@ -14,9 +14,9 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import FileUtils, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils class RustAnalyzer(SolidLanguageServer): @@ -24,7 +24,7 @@ class RustAnalyzer(SolidLanguageServer): Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -45,7 +45,7 @@ class RustAnalyzer(SolidLanguageServer): def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["target"] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for rust_analyzer. """ diff --git a/src/solidlsp/language_servers/solargraph/solargraph.py b/src/solidlsp/language_servers/solargraph/solargraph.py index f733e30..07cc1b3 100644 --- a/src/solidlsp/language_servers/solargraph/solargraph.py +++ b/src/solidlsp/language_servers/solargraph/solargraph.py @@ -15,8 +15,8 @@ from typing import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger class Solargraph(SolidLanguageServer): @@ -25,7 +25,7 @@ class Solargraph(SolidLanguageServer): Contains various configurations and settings specific to Ruby. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a Solargraph instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. @@ -47,7 +47,7 @@ class Solargraph(SolidLanguageServer): def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["vendor"] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig, repository_root_path: str) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig, repository_root_path: str) -> str: """ Setup runtime dependencies for Solargraph. """ diff --git a/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py index 3fe2d86..dd1d230 100644 --- a/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py +++ b/src/solidlsp/language_servers/typescript_language_server/typescript_language_server.py @@ -16,9 +16,9 @@ from overrides import override from solidlsp.ls import SolidLanguageServer from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -from solidlsp.multilspy_config import MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import PlatformId, PlatformUtils +from solidlsp.ls_config import LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import PlatformId, PlatformUtils # Platform-specific imports if os.name != "nt": # Unix-like systems @@ -41,7 +41,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript. """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ @@ -65,7 +65,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): "coverage", ] - def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str: + def setup_runtime_dependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """ Setup runtime dependencies for TypeScript Language Server. """ diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 610c60e..426d471 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -19,7 +19,7 @@ import pathspec import tqdm from serena.text_utils import MatchedConsecutiveLines, search_files -from solidlsp import multilspy_types +from solidlsp import ls_types from solidlsp.ls_handler import SolidLanguageServerHandler from solidlsp.lsp_protocol_handler import lsp_types from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes @@ -30,19 +30,19 @@ from solidlsp.lsp_protocol_handler.server import ( ProcessLaunchInfo, StringDict, ) -from solidlsp.multilspy_config import Language, MultilspyConfig -from solidlsp.multilspy_exceptions import MultilspyException -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_utils import FileUtils, PathUtils, TextUtils +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_exceptions import LanguageServerException +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils -GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, multilspy_types.UnifiedSymbolInformation] +GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, ls_types.UnifiedSymbolInformation] @dataclasses.dataclass(kw_only=True) class ReferenceInSymbol: """A symbol retrieved when requesting reference to a symbol, together with the location of the reference""" - symbol: multilspy_types.UnifiedSymbolInformation + symbol: ls_types.UnifiedSymbolInformation line: int character: int @@ -90,7 +90,7 @@ class SolidLanguageServer(ABC): @classmethod def create( - cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, timeout: float | None = None + cls, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, timeout: float | None = None ) -> "SolidLanguageServer": """ Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language. @@ -178,12 +178,12 @@ class SolidLanguageServer(ABC): else: logger.log(f"Language {config.code_language} is not supported", logging.ERROR) - raise MultilspyException(f"Language {config.code_language} is not supported") + raise LanguageServerException(f"Language {config.code_language} is not supported") def __init__( self, - config: MultilspyConfig, - logger: MultilspyLogger, + config: LanguageServerConfig, + logger: LanguageServerLogger, repository_root_path: str, process_launch_info: ProcessLaunchInfo, language_id: str, @@ -214,7 +214,7 @@ class SolidLanguageServer(ABC): # load cache first to prevent any racing conditions due to asyncio stuff self._document_symbols_cache: dict[ - str, tuple[str, tuple[list[multilspy_types.UnifiedSymbolInformation], list[multilspy_types.UnifiedSymbolInformation]]] + str, tuple[str, tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]] ] = {} """Maps file paths to a tuple of (file_content_hash, result_of_request_document_symbols)""" self._cache_lock = threading.Lock() @@ -368,7 +368,7 @@ class SolidLanguageServer(ABC): "open_file called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) uri = pathlib.Path(absolute_file_path).as_uri() @@ -411,7 +411,7 @@ class SolidLanguageServer(ABC): def insert_text_at_position( self, relative_file_path: str, line: int, column: int, text_to_be_inserted: str - ) -> multilspy_types.Position: + ) -> ls_types.Position: """ Insert text at the given line and column in the given file and return the updated cursor position after inserting the text. @@ -426,7 +426,7 @@ class SolidLanguageServer(ABC): "insert_text_at_position called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) uri = pathlib.Path(absolute_file_path).as_uri() @@ -456,13 +456,13 @@ class SolidLanguageServer(ABC): ], } ) - return multilspy_types.Position(line=new_l, character=new_c) + return ls_types.Position(line=new_l, character=new_c) def delete_text_between_positions( self, relative_file_path: str, - start: multilspy_types.Position, - end: multilspy_types.Position, + start: ls_types.Position, + end: ls_types.Position, ) -> str: """ Delete text between the given start and end positions in the given file and return the deleted text. @@ -472,7 +472,7 @@ class SolidLanguageServer(ABC): "insert_text_at_position called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path)) uri = pathlib.Path(absolute_file_path).as_uri() @@ -500,7 +500,7 @@ class SolidLanguageServer(ABC): def _send_definition_request(self, definition_params: DefinitionParams) -> Definition | list[LocationLink] | None: return self.server.send.definition(definition_params) - def request_definition(self, relative_file_path: str, line: int, column: int) -> list[multilspy_types.Location]: + def request_definition(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: """ Raise a [textDocument/definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) request to the Language Server for the symbol at the given line and column in the given file. Wait for the response and return the result. @@ -516,7 +516,7 @@ class SolidLanguageServer(ABC): "find_function_definition called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") with self.open_file(relative_file_path): # sending request to the language server and waiting for response @@ -534,29 +534,29 @@ class SolidLanguageServer(ABC): ) response = self._send_definition_request(definition_params) - ret: list[multilspy_types.Location] = [] + ret: list[ls_types.Location] = [] if isinstance(response, list): # response is either of type Location[] or LocationLink[] for item in response: assert isinstance(item, dict) if LSPConstants.URI in item and LSPConstants.RANGE in item: - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} 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(multilspy_types.Location(new_item)) + ret.append(ls_types.Location(new_item)) 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: multilspy_types.Location = {} + new_item: ls_types.Location = {} new_item["uri"] = item[LSPConstants.TARGET_URI] 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(multilspy_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) else: assert False, f"Unexpected response from Language Server: {item}" elif isinstance(response, dict): @@ -564,11 +564,11 @@ class SolidLanguageServer(ABC): assert LSPConstants.URI in response assert LSPConstants.RANGE in response - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} 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(multilspy_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) 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 @@ -591,7 +591,7 @@ class SolidLanguageServer(ABC): } ) - def request_references(self, relative_file_path: str, line: int, column: int) -> list[multilspy_types.Location]: + def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: """ Raise a [textDocument/references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) request to the Language Server to find references to the symbol at the given line and column in the given file. Wait for the response and return the result. @@ -608,7 +608,7 @@ class SolidLanguageServer(ABC): "request_references called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") with self.open_file(relative_file_path): try: @@ -624,7 +624,7 @@ class SolidLanguageServer(ABC): if response is None: return [] - ret: list[multilspy_types.Location] = [] + ret: list[ls_types.Location] = [] assert isinstance(response, list), f"Unexpected response from Language Server (expected list, got {type(response)}): {response}" for item in response: assert isinstance(item, dict), f"Unexpected response from Language Server (expected dict, got {type(item)}): {item}" @@ -637,11 +637,11 @@ class SolidLanguageServer(ABC): self.logger.log(f"Ignoring reference in {rel_path} since it should be ignored", logging.DEBUG) continue - new_item: multilspy_types.Location = {} + new_item: ls_types.Location = {} new_item.update(item) new_item["absolutePath"] = str(abs_path) new_item["relativePath"] = str(rel_path) - ret.append(multilspy_types.Location(**new_item)) + ret.append(ls_types.Location(**new_item)) return ret @@ -697,7 +697,7 @@ class SolidLanguageServer(ABC): def request_completions( self, relative_file_path: str, line: int, column: int, allow_incomplete: bool = False - ) -> list[multilspy_types.CompletionItem]: + ) -> list[ls_types.CompletionItem]: """ Raise a [textDocument/completion](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion) request to the Language Server to find completions at the given line and column in the given file. Wait for the response and return the result. @@ -737,7 +737,7 @@ class SolidLanguageServer(ABC): # TODO: Handle the case when the completion is a keyword items = [item for item in response if item["kind"] != LSPTypes.CompletionItemKind.Keyword] - completions_list: list[multilspy_types.CompletionItem] = [] + completions_list: list[ls_types.CompletionItem] = [] for item in items: assert "insertText" in item or "textEdit" in item @@ -776,14 +776,14 @@ class SolidLanguageServer(ABC): else: assert False - completion_item = multilspy_types.CompletionItem(**completion_item) + completion_item = ls_types.CompletionItem(**completion_item) 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)] def request_document_symbols( self, relative_file_path: str, include_body: bool = False - ) -> tuple[list[multilspy_types.UnifiedSymbolInformation], list[multilspy_types.UnifiedSymbolInformation]]: + ) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]: """ Raise a [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol) request to the Language Server to find symbols in the given file. Wait for the response and return the result. @@ -823,14 +823,14 @@ class SolidLanguageServer(ABC): ) def turn_item_into_symbol_with_children(item: GenericDocumentSymbol): - item = cast(multilspy_types.UnifiedSymbolInformation, item) + item = cast(ls_types.UnifiedSymbolInformation, item) absolute_path = os.path.join(self.repository_root_path, relative_file_path) # handle missing entries in location if "location" not in item: uri = pathlib.Path(absolute_path).as_uri() assert "range" in item - tree_location = multilspy_types.Location( + tree_location = ls_types.Location( uri=uri, range=item["range"], absolutePath=absolute_path, @@ -855,9 +855,9 @@ class SolidLanguageServer(ABC): child["parent"] = item item[LSPConstants.CHILDREN] = children - flat_all_symbol_list: list[multilspy_types.UnifiedSymbolInformation] = [] + flat_all_symbol_list: list[ls_types.UnifiedSymbolInformation] = [] assert isinstance(response, list), f"Unexpected response from Language Server: {response}" - root_nodes: list[multilspy_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]: @@ -867,7 +867,7 @@ class SolidLanguageServer(ABC): # so we cast and rename the var after the mutating call to turn_item_into_symbol_with_children # which turned and item into a "symbol" turn_item_into_symbol_with_children(root_item) - root_symbol = cast(multilspy_types.UnifiedSymbolInformation, root_item) + root_symbol = cast(ls_types.UnifiedSymbolInformation, root_item) root_symbol["parent"] = None root_nodes.append(root_symbol) @@ -878,9 +878,9 @@ class SolidLanguageServer(ABC): if LSPConstants.CHILDREN in root_symbol: # TODO: l_tree should be a list of TreeRepr. Define the following function to return TreeRepr as well - def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> list[multilspy_types.UnifiedSymbolInformation]: - node = cast(multilspy_types.UnifiedSymbolInformation, node) - l: list[multilspy_types.UnifiedSymbolInformation] = [] + def visit_tree_nodes_and_build_tree_repr(node: GenericDocumentSymbol) -> list[ls_types.UnifiedSymbolInformation]: + node = cast(ls_types.UnifiedSymbolInformation, node) + l: list[ls_types.UnifiedSymbolInformation] = [] turn_item_into_symbol_with_children(node) assert LSPConstants.CHILDREN in node children = node[LSPConstants.CHILDREN] @@ -891,7 +891,7 @@ class SolidLanguageServer(ABC): flat_all_symbol_list.extend(visit_tree_nodes_and_build_tree_repr(root_symbol)) else: - flat_all_symbol_list.append(multilspy_types.UnifiedSymbolInformation(**root_symbol)) + flat_all_symbol_list.append(ls_types.UnifiedSymbolInformation(**root_symbol)) result = flat_all_symbol_list, root_nodes self.logger.log(f"Caching document symbols for {relative_file_path}", logging.DEBUG) @@ -902,7 +902,7 @@ class SolidLanguageServer(ABC): def request_full_symbol_tree( self, within_relative_path: str | None = None, include_body: bool = False - ) -> list[multilspy_types.UnifiedSymbolInformation]: + ) -> list[ls_types.UnifiedSymbolInformation]: """ Will go through all files in the project or within a relative path and build a tree of symbols. Note: this may be slow the first time it is called, especially if `within_relative_path` is not used to restrict the search. @@ -937,7 +937,7 @@ class SolidLanguageServer(ABC): return root_nodes # Helper function to recursively process directories - def process_directory(rel_dir_path: str) -> list[multilspy_types.UnifiedSymbolInformation]: + def process_directory(rel_dir_path: str) -> list[ls_types.UnifiedSymbolInformation]: abs_dir_path = self.repository_root_path if rel_dir_path == "." else os.path.join(self.repository_root_path, rel_dir_path) abs_dir_path = os.path.realpath(abs_dir_path) @@ -952,10 +952,10 @@ class SolidLanguageServer(ABC): return [] # Create package symbol for directory - package_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore + package_symbol = ls_types.UnifiedSymbolInformation( # type: ignore name=os.path.basename(abs_dir_path), - kind=multilspy_types.SymbolKind.Package, - location=multilspy_types.Location( + kind=ls_types.SymbolKind.Package, + location=ls_types.Location( uri=str(pathlib.Path(abs_dir_path).as_uri()), range={"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}, absolutePath=str(abs_dir_path), @@ -985,12 +985,12 @@ class SolidLanguageServer(ABC): file_rel_path = str(Path(contained_dir_or_file_abs_path).resolve().relative_to(self.repository_root_path)) with self.open_file(file_rel_path) as file_data: fileRange = self._get_range_from_file_content(file_data.contents) - file_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore + file_symbol = ls_types.UnifiedSymbolInformation( # type: ignore name=os.path.splitext(contained_dir_or_file_name)[0], - kind=multilspy_types.SymbolKind.File, + kind=ls_types.SymbolKind.File, range=fileRange, selectionRange=fileRange, - location=multilspy_types.Location( + location=ls_types.Location( uri=str(pathlib.Path(contained_dir_or_file_abs_path).as_uri()), range=fileRange, absolutePath=str(contained_dir_or_file_abs_path), @@ -1006,7 +1006,7 @@ 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[multilspy_types.UnifiedSymbolInformation]): + def fix_relative_path(nodes: list[ls_types.UnifiedSymbolInformation]): for node in nodes: if "location" in node and "relativePath" in node["location"]: path = Path(node["location"]["relativePath"]) @@ -1028,18 +1028,18 @@ class SolidLanguageServer(ABC): return process_directory(start_rel_path) @staticmethod - def _get_range_from_file_content(file_content: str) -> multilspy_types.Range: + def _get_range_from_file_content(file_content: str) -> ls_types.Range: """ Get the range for the given file. """ lines = file_content.split("\n") end_line = len(lines) end_column = len(lines[-1]) - return multilspy_types.Range( - start=multilspy_types.Position(line=0, character=0), end=multilspy_types.Position(line=end_line, character=end_column) + return ls_types.Range( + start=ls_types.Position(line=0, character=0), end=ls_types.Position(line=end_line, character=end_column) ) - def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]: """ An overview of the given directory. @@ -1048,11 +1048,11 @@ class SolidLanguageServer(ABC): """ symbol_tree = self.request_full_symbol_tree(relative_dir_path) # Initialize result dictionary - result: dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]] = defaultdict(list) + 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: multilspy_types.UnifiedSymbolInformation): - if symbol["kind"] == multilspy_types.SymbolKind.File: + def process_symbol(symbol: ls_types.UnifiedSymbolInformation): + if symbol["kind"] == ls_types.SymbolKind.File: # For file symbols, process their children (top-level symbols) for child in symbol["children"]: assert "location" in child @@ -1075,7 +1075,7 @@ class SolidLanguageServer(ABC): process_symbol(root) return result - def request_document_overview(self, relative_file_path: str) -> list[tuple[str, multilspy_types.SymbolKind, int, int]]: + def request_document_overview(self, relative_file_path: str) -> list[tuple[str, ls_types.SymbolKind, int, int]]: """ An overview of the given file. Returns the list of tuples (name, kind, line, column) of all top-level symbols in the file. @@ -1091,7 +1091,7 @@ class SolidLanguageServer(ABC): raise KeyError(f"Could not process symbol of name {root.get('name', 'unknown')} in {relative_file_path=}") from e return result - def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]: """ An overview of all symbols in the given file or directory. @@ -1108,7 +1108,7 @@ class SolidLanguageServer(ABC): else: return self.request_dir_overview(within_relative_path) - def request_hover(self, relative_file_path: str, line: int, column: int) -> multilspy_types.Hover | None: + def request_hover(self, relative_file_path: str, line: int, column: int) -> ls_types.Hover | None: """ Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server to find the hover information at the given line and column in the given file. Wait for the response and return the result. @@ -1135,12 +1135,12 @@ class SolidLanguageServer(ABC): assert isinstance(response, dict) - return multilspy_types.Hover(**response) + return ls_types.Hover(**response) # ----------------------------- FROM HERE ON MODIFICATIONS BY MISCHA -------------------- def retrieve_symbol_body( - self, symbol: multilspy_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation + self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation ) -> str: """ Load the body of the given symbol. If the body is already contained in the symbol, just return it. @@ -1169,7 +1169,7 @@ class SolidLanguageServer(ABC): "request_parsed_files called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") rel_file_paths = [] for root, dirs, files in os.walk(self.repository_root_path): dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))] @@ -1244,7 +1244,7 @@ class SolidLanguageServer(ABC): "request_referencing_symbols called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") # First, get all references to the symbol references = self.request_references(relative_file_path, line, column) @@ -1281,7 +1281,7 @@ class SolidLanguageServer(ABC): containing_symbol_name = ref_text.split(".")[0] all_symbols, _ = self.request_document_symbols(ref_path) for symbol in all_symbols: - if symbol["name"] == containing_symbol_name and symbol["kind"] == multilspy_types.SymbolKind.Variable: + if symbol["name"] == containing_symbol_name and symbol["kind"] == ls_types.SymbolKind.Variable: containing_symbol = copy(symbol) containing_symbol["location"] = ref containing_symbol["range"] = ref["range"] @@ -1294,7 +1294,7 @@ class SolidLanguageServer(ABC): logging.WARNING, ) fileRange = self._get_range_from_file_content(file_data.contents) - location = multilspy_types.Location( + location = ls_types.Location( uri=str(pathlib.Path(os.path.join(self.repository_root_path, ref_path)).as_uri()), range=fileRange, absolutePath=str(os.path.join(self.repository_root_path, ref_path)), @@ -1307,8 +1307,8 @@ class SolidLanguageServer(ABC): else: body = "" - containing_symbol = multilspy_types.UnifiedSymbolInformation( - kind=multilspy_types.SymbolKind.File, + containing_symbol = ls_types.UnifiedSymbolInformation( + kind=ls_types.SymbolKind.File, range=fileRange, selectionRange=fileRange, location=location, @@ -1316,7 +1316,7 @@ class SolidLanguageServer(ABC): children=[], body=body, ) - if containing_symbol is None or (not include_file_symbols and containing_symbol["kind"] == multilspy_types.SymbolKind.File): + if containing_symbol is None or (not include_file_symbols and containing_symbol["kind"] == ls_types.SymbolKind.File): continue assert "location" in containing_symbol @@ -1363,7 +1363,7 @@ class SolidLanguageServer(ABC): column: int | None = None, strict: bool = False, include_body: bool = False, - ) -> multilspy_types.UnifiedSymbolInformation | None: + ) -> ls_types.UnifiedSymbolInformation | None: """ Finds the first symbol containing the position for the given file. For Python, container symbols are considered to be those with kinds corresponding to @@ -1409,7 +1409,7 @@ class SolidLanguageServer(ABC): for symbol in symbols: if "location" not in symbol: range = symbol["range"] - location = multilspy_types.Location( + location = ls_types.Location( uri=f"file:/{absolute_file_path}", range=range, absolutePath=absolute_file_path, @@ -1424,9 +1424,9 @@ class SolidLanguageServer(ABC): location["uri"] = Path(absolute_file_path).as_uri() # Allowed container kinds, currently only for Python - container_symbol_kinds = {multilspy_types.SymbolKind.Method, multilspy_types.SymbolKind.Function, multilspy_types.SymbolKind.Class} + container_symbol_kinds = {ls_types.SymbolKind.Method, ls_types.SymbolKind.Function, ls_types.SymbolKind.Class} - def is_position_in_range(line: int, range_d: multilspy_types.Range) -> bool: + def is_position_in_range(line: int, range_d: ls_types.Range) -> bool: start = range_d["start"] end = range_d["end"] @@ -1447,7 +1447,7 @@ class SolidLanguageServer(ABC): for s in symbols if s["kind"] in container_symbol_kinds and s["location"]["range"]["start"]["line"] != s["location"]["range"]["end"]["line"] ] - var_containers = [s for s in symbols if s["kind"] == multilspy_types.SymbolKind.Variable] + var_containers = [s for s in symbols if s["kind"] == ls_types.SymbolKind.Variable] candidate_containers.extend(var_containers) if not candidate_containers: @@ -1471,8 +1471,8 @@ class SolidLanguageServer(ABC): return None def request_container_of_symbol( - self, symbol: multilspy_types.UnifiedSymbolInformation, include_body: bool = False - ) -> multilspy_types.UnifiedSymbolInformation | None: + self, symbol: ls_types.UnifiedSymbolInformation, include_body: bool = False + ) -> ls_types.UnifiedSymbolInformation | None: """ Finds the container of the given symbol if there is one. If the parent attribute is present, the parent is returned without further searching. @@ -1498,7 +1498,7 @@ class SolidLanguageServer(ABC): line: int, column: int, include_body: bool = False, - ) -> multilspy_types.UnifiedSymbolInformation | None: + ) -> ls_types.UnifiedSymbolInformation | None: """ Finds the symbol that defines the symbol at the given location. @@ -1516,7 +1516,7 @@ class SolidLanguageServer(ABC): "request_defining_symbol called before Language Server started", logging.ERROR, ) - raise MultilspyException("Language Server not started") + raise LanguageServerException("Language Server not started") # Get the definition location(s) definitions = self.request_definition(relative_file_path, line, column) @@ -1596,7 +1596,7 @@ class SolidLanguageServer(ABC): logging.ERROR, ) - def request_workspace_symbol(self, query: str) -> list[multilspy_types.UnifiedSymbolInformation] | None: + def request_workspace_symbol(self, query: str) -> list[ls_types.UnifiedSymbolInformation] | None: """ Raise a [workspace/symbol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) request to the Language Server to find symbols across the whole workspace. Wait for the response and return the result. @@ -1611,7 +1611,7 @@ class SolidLanguageServer(ABC): assert isinstance(response, list) - ret: list[multilspy_types.UnifiedSymbolInformation] = [] + ret: list[ls_types.UnifiedSymbolInformation] = [] for item in response: assert isinstance(item, dict) @@ -1619,7 +1619,7 @@ class SolidLanguageServer(ABC): assert LSPConstants.KIND in item assert LSPConstants.LOCATION in item - ret.append(multilspy_types.UnifiedSymbolInformation(**item)) + ret.append(ls_types.UnifiedSymbolInformation(**item)) return ret diff --git a/src/solidlsp/multilspy_config.py b/src/solidlsp/ls_config.py similarity index 98% rename from src/solidlsp/multilspy_config.py rename to src/solidlsp/ls_config.py index f676e52..27deaf1 100644 --- a/src/solidlsp/multilspy_config.py +++ b/src/solidlsp/ls_config.py @@ -73,7 +73,7 @@ class Language(str, Enum): @dataclass -class MultilspyConfig: +class LanguageServerConfig: """ Configuration parameters """ diff --git a/src/solidlsp/multilspy_exceptions.py b/src/solidlsp/ls_exceptions.py similarity index 87% rename from src/solidlsp/multilspy_exceptions.py rename to src/solidlsp/ls_exceptions.py index 1dc12ed..0484da6 100644 --- a/src/solidlsp/multilspy_exceptions.py +++ b/src/solidlsp/ls_exceptions.py @@ -3,7 +3,7 @@ This module contains the exceptions raised by the Multilspy framework. """ -class MultilspyException(Exception): +class LanguageServerException(Exception): """ Exceptions raised by the Multilspy framework. """ diff --git a/src/solidlsp/ls_handler.py b/src/solidlsp/ls_handler.py index e4c8b68..6de9ef7 100644 --- a/src/solidlsp/ls_handler.py +++ b/src/solidlsp/ls_handler.py @@ -28,8 +28,8 @@ from solidlsp.lsp_protocol_handler.server import ( make_request, make_response, ) -from solidlsp.lsp_request import SolidLspRequest -from solidlsp.multilspy_exceptions import MultilspyException +from solidlsp.ls_request import LanguageServerRequest +from solidlsp.ls_exceptions import LanguageServerException log = logging.getLogger(__name__) @@ -109,7 +109,7 @@ class SolidLanguageServerHandler: logger: An optional function that takes two strings (source and destination) and a payload dictionary, and logs the communication between the client and the server. """ - self.send = SolidLspRequest(self.send_request) + self.send = LanguageServerRequest(self.send_request) self.notify = LspNotification(self.send_notification) self.process_launch_info = process_launch_info @@ -422,7 +422,7 @@ class SolidLanguageServerHandler: self._log("Processing result") if result.is_error(): - raise MultilspyException( + raise LanguageServerException( f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}" ) from result.error diff --git a/src/solidlsp/multilspy_logger.py b/src/solidlsp/ls_logger.py similarity index 98% rename from src/solidlsp/multilspy_logger.py rename to src/solidlsp/ls_logger.py index 993aa3b..6bdf12c 100644 --- a/src/solidlsp/multilspy_logger.py +++ b/src/solidlsp/ls_logger.py @@ -22,7 +22,7 @@ class LogLine(BaseModel): message: str -class MultilspyLogger: +class LanguageServerLogger: """ Logger class """ diff --git a/src/solidlsp/lsp_request.py b/src/solidlsp/ls_request.py similarity index 99% rename from src/solidlsp/lsp_request.py rename to src/solidlsp/ls_request.py index 11fc67b..209a3f0 100644 --- a/src/solidlsp/lsp_request.py +++ b/src/solidlsp/ls_request.py @@ -3,7 +3,7 @@ from typing import Union from solidlsp.lsp_protocol_handler import lsp_types -class SolidLspRequest: +class LanguageServerRequest: def __init__(self, send_request): self.send_request = send_request diff --git a/src/solidlsp/multilspy_types.py b/src/solidlsp/ls_types.py similarity index 100% rename from src/solidlsp/multilspy_types.py rename to src/solidlsp/ls_types.py diff --git a/src/solidlsp/multilspy_utils.py b/src/solidlsp/ls_utils.py similarity index 91% rename from src/solidlsp/multilspy_utils.py rename to src/solidlsp/ls_utils.py index e1b71c2..535d342 100644 --- a/src/solidlsp/multilspy_utils.py +++ b/src/solidlsp/ls_utils.py @@ -14,9 +14,9 @@ from pathlib import Path, PurePath import requests -from solidlsp.multilspy_exceptions import MultilspyException -from solidlsp.multilspy_logger import MultilspyLogger -from solidlsp.multilspy_types import UnifiedSymbolInformation +from solidlsp.ls_exceptions import LanguageServerException +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_types import UnifiedSymbolInformation class TextUtils: @@ -147,22 +147,22 @@ class FileUtils: """ @staticmethod - def read_file(logger: MultilspyLogger, file_path: str) -> str: + def read_file(logger: LanguageServerLogger, file_path: str) -> str: """ Reads the file at the given path and returns the contents as a string. """ if not os.path.exists(file_path): logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR) - raise MultilspyException(f"File read '{file_path}' failed: File does not exist.") + raise LanguageServerException(f"File read '{file_path}' failed: File does not exist.") try: with open(file_path, encoding="utf-8") as inp_file: return inp_file.read() except Exception as exc: logger.log(f"File read '{file_path}' failed to read with encoding 'utf-8': {exc}", logging.ERROR) - raise MultilspyException("File read failed.") from None + raise LanguageServerException("File read failed.") from None @staticmethod - def download_file(logger: MultilspyLogger, url: str, target_path: str) -> None: + def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None: """ Downloads the file from the given URL to the given {target_path} """ @@ -170,15 +170,15 @@ class FileUtils: response = requests.get(url, stream=True, timeout=60) if response.status_code != 200: logger.log(f"Error downloading file '{url}': {response.status_code} {response.text}", logging.ERROR) - raise MultilspyException("Error downloading file.") + raise LanguageServerException("Error downloading file.") with open(target_path, "wb") as f: shutil.copyfileobj(response.raw, f) except Exception as exc: logger.log(f"Error downloading file '{url}': {exc}", logging.ERROR) - raise MultilspyException("Error downloading file.") from None + raise LanguageServerException("Error downloading file.") from None @staticmethod - def download_and_extract_archive(logger: MultilspyLogger, url: str, target_path: str, archive_type: str) -> None: + def download_and_extract_archive(logger: LanguageServerLogger, url: str, target_path: str, archive_type: str) -> None: """ Downloads the archive from the given URL having format {archive_type} and extracts it to the given {target_path} """ @@ -203,10 +203,10 @@ class FileUtils: shutil.copyfileobj(f_in, f_out) else: logger.log(f"Unknown archive type '{archive_type}' for extraction", logging.ERROR) - raise MultilspyException(f"Unknown archive type '{archive_type}'") + raise LanguageServerException(f"Unknown archive type '{archive_type}'") except Exception as exc: logger.log(f"Error extracting archive '{tmp_file_name}' obtained from '{url}': {exc}", logging.ERROR) - raise MultilspyException("Error extracting archive.") from exc + raise LanguageServerException("Error extracting archive.") from exc finally: for tmp_file_name in tmp_files: if os.path.exists(tmp_file_name): @@ -268,7 +268,7 @@ class PlatformUtils: platform_id += "-" + libc return PlatformId(platform_id) else: - raise MultilspyException(f"Unknown platform: {system=}, {machine=}, {bitness=}") + raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}") @staticmethod def _determine_windows_machine_type(): @@ -324,7 +324,7 @@ class PlatformUtils: available_version_cmd_output.append(version_cmd_output) if not available_version_cmd_output: - raise MultilspyException("dotnet not found on the system") + raise LanguageServerException("dotnet not found on the system") # Check for supported versions in order of preference (latest first) for version_cmd_output in available_version_cmd_output: @@ -341,7 +341,7 @@ class PlatformUtils: return DotnetVersion.V4 # If no supported version found, raise exception with all available versions - raise MultilspyException( + raise LanguageServerException( f"No supported dotnet version found. Available versions: {', '.join(available_version_cmd_output)}. Supported versions: 4, 6, 7, 8" ) except (FileNotFoundError, subprocess.CalledProcessError): @@ -349,7 +349,7 @@ class PlatformUtils: result = subprocess.run(["mono", "--version"], capture_output=True, check=True) return DotnetVersion.VMONO except (FileNotFoundError, subprocess.CalledProcessError): - raise MultilspyException("dotnet or mono not found on the system") + raise LanguageServerException("dotnet or mono not found on the system") class SymbolUtils: diff --git a/src/solidlsp/lsp_protocol_handler/server.py b/src/solidlsp/lsp_protocol_handler/server.py index c628d43..0a001f4 100644 --- a/src/solidlsp/lsp_protocol_handler/server.py +++ b/src/solidlsp/lsp_protocol_handler/server.py @@ -39,7 +39,7 @@ from typing import Any, Union import psutil -from ..multilspy_exceptions import MultilspyException +from ..ls_exceptions import LanguageServerException from .lsp_requests import LspNotification, LspRequest from .lsp_types import ErrorCodes @@ -118,23 +118,6 @@ class MessageType: log = 4 -class Request: - def __init__(self) -> None: - self.cv = asyncio.Condition() - self.result: PayloadLike | None = None - self.error: Error | None = None - - async def on_result(self, params: PayloadLike) -> None: - self.result = params - async with self.cv: - self.cv.notify() - - async def on_error(self, err: Error) -> None: - self.error = err - async with self.cv: - self.cv.notify() - - def content_length(line: bytes) -> int | None: if line.startswith(b"Content-Length: "): _, value = line.split(b"Content-Length: ") @@ -145,501 +128,3 @@ def content_length(line: bytes) -> int | None: raise ValueError(f"Invalid Content-Length header: {value}") return None - -class LanguageServerHandler: - """ - This class provides the implementation of Python client for the Language Server Protocol. - A class that launches the language server and communicates with it - using the Language Server Protocol (LSP). - - It provides methods for sending requests, responses, and notifications to the server - and for registering handlers for requests and notifications from the server. - - Uses JSON-RPC 2.0 for communication with the server over stdin/stdout. - - Attributes: - send: A LspRequest object that can be used to send requests to the server and - await for the responses. - notify: A LspNotification object that can be used to send notifications to the server. - cmd: A string that represents the command to launch the language server process. - process: A subprocess.Popen object that represents the language server process. - _received_shutdown: A boolean flag that indicates whether the client has received - a shutdown request from the server. - request_id: An integer that represents the next available request id for the client. - _response_handlers: A dictionary that maps request ids to Request objects that - store the results or errors of the requests. - on_request_handlers: A dictionary that maps method names to callback functions - that handle requests from the server. - on_notification_handlers: A dictionary that maps method names to callback functions - that handle notifications from the server. - logger: An optional function that takes two strings (source and destination) and - a payload dictionary, and logs the communication between the client and the server. - tasks: A dictionary that maps task ids to asyncio.Task objects that represent - the asynchronous tasks created by the handler. - task_counter: An integer that represents the next available task id for the handler. - loop: An asyncio.AbstractEventLoop object that represents the event loop used by the handler. - start_independent_lsp_process: An optional boolean flag that indicates whether to start the - language server process in an independent process group. Default is `True`. Setting it to - `False` means that the language server process will be in the same process group as the - the current process, and any SIGINT and SIGTERM signals will be sent to both processes. - - """ - - def __init__( - self, - process_launch_info: ProcessLaunchInfo, - logger: Callable[[str, str, StringDict | str], None] | None = None, - start_independent_lsp_process=True, - ) -> None: - """ - Params: - cmd: A string that represents the command to launch the language server process. - logger: An optional function that takes two strings (source and destination) and - a payload dictionary, and logs the communication between the client and the server. - """ - self.send = LspRequest(self.send_request) - self.notify = LspNotification(self.send_notification) - - self.process_launch_info = process_launch_info - self.process = None - self._received_shutdown = False - - self.request_id = 1 - self._response_handlers: dict[Any, Request] = {} - self.on_request_handlers = {} - self.on_notification_handlers = {} - self.logger = logger - self.tasks = {} - self.task_counter = 0 - self.loop = None - self.start_independent_lsp_process = start_independent_lsp_process - - # Add thread locks for shared resources to prevent race conditions - self._stdin_lock = threading.Lock() - self._request_id_lock = threading.Lock() - self._response_handlers_lock = threading.Lock() - self._tasks_lock = threading.Lock() - - def is_running(self) -> bool: - """ - Checks if the language server process is currently running. - """ - return self.process is not None and self.process.returncode is None - - async def start(self) -> None: - """ - Starts the language server process and creates a task to continuously read from its stdout to handle communications - from the server to the client - """ - child_proc_env = os.environ.copy() - child_proc_env.update(self.process_launch_info.env) - - log.info("Starting language server process via command: %s", self.process_launch_info.cmd) - self.process = await asyncio.create_subprocess_shell( - self.process_launch_info.cmd, - stdout=asyncio.subprocess.PIPE, - stdin=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=child_proc_env, - cwd=self.process_launch_info.cwd, - start_new_session=self.start_independent_lsp_process, - ) - - # Check if process terminated immediately - if self.process.returncode is not None: - log.error("Language server has already terminated/could not be started") - # Process has already terminated - stderr_data = await self.process.stderr.read() - error_message = stderr_data.decode("utf-8", errors="replace") - raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}") - - self.loop = asyncio.get_event_loop() - - # Use lock to prevent race conditions on tasks and task_counter during startup - with self._tasks_lock: - self.tasks[self.task_counter] = self.loop.create_task(self.run_forever()) - self.task_counter += 1 - self.tasks[self.task_counter] = self.loop.create_task(self.run_forever_stderr()) - self.task_counter += 1 - - async def stop(self) -> None: - """ - Sends the terminate signal to the language server process and waits for it to exit, with a timeout, killing it if necessary - """ - # First cancel all tasks - await self._cancel_pending_tasks() - - process = self.process - self.process = None - - if not process: - return - - # Clean up the process - await self._cleanup_process(process) - - async def _cancel_pending_tasks(self): - """Cancel all pending tasks and wait for them to complete or timeout.""" - pending_tasks = [] - - # Use lock to safely access tasks dictionary - with self._tasks_lock: - for task in self.tasks.values(): - if not task.done(): - task.cancel() - pending_tasks.append(task) - - if pending_tasks: - try: - await asyncio.wait_for(asyncio.gather(*pending_tasks, return_exceptions=True), timeout=5.0) - except (TimeoutError, Exception): - pass - - # Clear tasks dictionary under lock - with self._tasks_lock: - self.tasks = {} - - async def _cleanup_process(self, process): - """Clean up a process: close stdin, terminate/kill process, close stdout/stderr.""" - # Close stdin first to prevent deadlocks - # See: https://bugs.python.org/issue35539 - self._safely_close_pipe(process.stdin) - - # Terminate/kill the process if it's still running - if process.returncode is None: - await self._terminate_or_kill_process(process) - - # Close stdout and stderr pipes after process has exited - # This is essential to prevent "I/O operation on closed pipe" errors and - # "Event loop is closed" errors during garbage collection - # See: https://bugs.python.org/issue41320 and https://github.com/python/cpython/issues/88050 - self._safely_close_pipe(process.stdout) - self._safely_close_pipe(process.stderr) - - # Small delay to ensure OS has released file handles - await asyncio.sleep(0.5) - - def _safely_close_pipe(self, pipe): - """Safely close a pipe, ignoring any exceptions.""" - if pipe: - try: - pipe.close() - except Exception: - pass - - async def _terminate_or_kill_process(self, process): - """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) - - # Wait for the process to exit (with timeout) - try: - await asyncio.wait_for(process.wait(), timeout=10) - except (TimeoutError, Exception): - # If termination failed, forcefully kill the process tree - self._signal_process_tree(process, terminate=False) - try: - # Give it one more chance to exit - await asyncio.wait_for(process.wait(), timeout=2) - except Exception: - pass - - def _signal_process_tree(self, process, terminate=True): - """Send signal (terminate or kill) to the process and all its children.""" - signal_method = "terminate" if terminate else "kill" - - # Try to get the parent process - parent = None - try: - parent = psutil.Process(process.pid) - except (psutil.NoSuchProcess, psutil.AccessDenied, Exception): - pass - - # If we have the parent process and it's running, signal the entire tree - if parent and parent.is_running(): - # Signal children first - for child in parent.children(recursive=True): - try: - getattr(child, signal_method)() - except (psutil.NoSuchProcess, psutil.AccessDenied, Exception): - pass - - # Then signal the parent - try: - getattr(parent, signal_method)() - except (psutil.NoSuchProcess, psutil.AccessDenied, Exception): - pass - else: - # Fall back to direct process signaling - try: - getattr(process, signal_method)() - except Exception: - pass - - async def shutdown(self) -> None: - """ - Perform the shutdown sequence for the client, including sending the shutdown request to the server and notifying it of exit - """ - self._log("Sending shutdown request to server") - await self.send.shutdown() - self._log("Received shutdown response from server") - self._received_shutdown = True - self._log("Sending exit notification to server") - self.notify.exit() - self._log("Sent exit notification to server") - if self.process and self.process.stdout: - self.process.stdout.set_exception(StopLoopException()) - # This yields the control to the event loop to allow the exception to be handled - # in the run_forever and run_forever_stderr methods - await asyncio.sleep(0) - - def _log(self, message: str | StringDict) -> None: - """ - Create a log message - """ - if self.logger is not None: - self.logger("client", "logger", message) - - async def run_forever(self) -> bool: - """ - Continuously read from the language server process stdout and handle the messages - invoking the registered response and notification handlers - """ - try: - while self.process and self.process.stdout and not self.process.stdout.at_eof(): - line = await self.process.stdout.readline() - if not line: - continue - try: - num_bytes = content_length(line) - except ValueError: - continue - if num_bytes is None: - continue - while line and line.strip(): - line = await self.process.stdout.readline() - if not line: - continue - body = await self.process.stdout.readexactly(num_bytes) - - # Use lock to prevent race conditions on tasks and task_counter - with self._tasks_lock: - self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._handle_body(body)) - self.task_counter += 1 - except (BrokenPipeError, ConnectionResetError, StopLoopException): - pass - return self._received_shutdown - - async def run_forever_stderr(self) -> None: - """ - Continuously read from the language server process stderr and log the messages - """ - try: - while self.process and self.process.stderr and not self.process.stderr.at_eof(): - line = await self.process.stderr.readline() - if not line: - continue - self._log("LSP stderr: " + line.decode(ENCODING, errors="replace")) - except (BrokenPipeError, ConnectionResetError, StopLoopException): - pass - - async def _handle_body(self, body: bytes) -> None: - """ - Parse the body text received from the language server process and invoke the appropriate handler - """ - try: - await self._receive_payload(json.loads(body)) - except OSError as ex: - self._log(f"malformed {ENCODING}: {ex}") - except UnicodeDecodeError as ex: - self._log(f"malformed {ENCODING}: {ex}") - except json.JSONDecodeError as ex: - self._log(f"malformed JSON: {ex}") - - async def _receive_payload(self, payload: StringDict) -> None: - """ - Determine if the payload received from server is for a request, response, or notification and invoke the appropriate handler - """ - if self.logger: - self.logger("server", "client", payload) - try: - if "method" in payload: - if "id" in payload: - await self._request_handler(payload) - else: - await self._notification_handler(payload) - elif "id" in payload: - await self._response_handler(payload) - else: - self._log(f"Unknown payload type: {payload}") - except Exception as err: - self._log(f"Error handling server payload: {err}") - - def send_notification(self, method: str, params: dict | None = None) -> None: - """ - Send notification pertaining to the given method to the server with the given parameters - """ - self._send_payload_sync(make_notification(method, params)) - - def send_response(self, request_id: Any, params: PayloadLike) -> None: - """ - Send response to the given request id to the server with the given parameters - """ - # Use lock to prevent race conditions on tasks and task_counter - with self._tasks_lock: - self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._send_payload(make_response(request_id, params))) - self.task_counter += 1 - - def send_error_response(self, request_id: Any, err: Error) -> None: - """ - Send error response to the given request id to the server with the given error - """ - # Use lock to prevent race conditions on tasks and task_counter - with self._tasks_lock: - self.tasks[self.task_counter] = asyncio.get_event_loop().create_task(self._send_payload(make_error_response(request_id, err))) - self.task_counter += 1 - - async def send_request(self, method: str, params: dict | None = None) -> PayloadLike: - """ - Send request to the server, register the request id, and wait for the response - """ - request = Request() - - # Use lock to prevent race conditions on request_id and _response_handlers - with self._request_id_lock: - request_id = self.request_id - self.request_id += 1 - - with self._response_handlers_lock: - self._response_handlers[request_id] = request - - async with request.cv: - await self._send_payload(make_request(method, request_id, params)) - self._log(f"Waiting for asyncio condition for request {method} with params:\n{params}") - await request.cv.wait() - self._log("Finished waiting, processing result") - if isinstance(request.error, Error): - raise MultilspyException( - f"Could not process request {method} with params:\n{params}.\n Language server error: {request.error}" - ) from request.error - self._log(f"Returning non-error result, which is:\n{request.result}") - return request.result - - def _send_payload_sync(self, payload: StringDict) -> None: - """ - Send the payload to the server by writing to its stdin synchronously - """ - if not self.process or not self.process.stdin: - return - msg = create_message(payload) - if self.logger: - self.logger("client", "server", payload) - - # Use lock to prevent concurrent writes to stdin that cause buffer corruption - with self._stdin_lock: - try: - self.process.stdin.writelines(msg) - except (BrokenPipeError, ConnectionResetError, OSError) as e: - # Log the error but don't raise to prevent cascading failures - if self.logger: - self.logger("client", "logger", f"Failed to write to stdin: {e}") - return - - async def _send_payload(self, payload: StringDict) -> None: - """ - Send the payload to the server by writing to its stdin asynchronously. - """ - if not self.process or not self.process.stdin: - return - self._log(payload) - msg = create_message(payload) - - # Use lock to prevent concurrent writes to stdin that cause buffer corruption - with self._stdin_lock: - try: - self.process.stdin.writelines(msg) - await self.process.stdin.drain() - except (BrokenPipeError, ConnectionResetError, OSError) as e: - # Log the error but don't raise to prevent cascading failures - if self.logger: - self.logger("client", "logger", f"Failed to write to stdin: {e}") - return - - def on_request(self, method: str, cb) -> 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: - """ - Register the callback function to handle notifications from the server to the client for the given method - """ - self.on_notification_handlers[method] = cb - - async def _response_handler(self, response: StringDict) -> None: - """ - Handle the response received from the server for a request, using the id to determine the request - """ - with self._response_handlers_lock: - request = self._response_handlers.pop(response["id"]) - - if "result" in response and "error" not in response: - await request.on_result(response["result"]) - elif "result" not in response and "error" in response: - await request.on_error(Error.from_lsp(response["error"])) - else: - await request.on_error(Error(ErrorCodes.InvalidRequest, "")) - - async def _request_handler(self, response: StringDict) -> None: - """ - Handle the request received from the server: call the appropriate callback function and return the result - """ - method = response.get("method", "") - params = response.get("params") - request_id = response.get("id") - handler = self.on_request_handlers.get(method) - if not handler: - self.send_error_response( - request_id, - Error( - ErrorCodes.MethodNotFound, - f"method '{method}' not handled on client.", - ), - ) - return - try: - self.send_response(request_id, await handler(params)) - except Error as ex: - self.send_error_response(request_id, ex) - except Exception as ex: - self.send_error_response(request_id, Error(ErrorCodes.InternalError, str(ex))) - - async def _notification_handler(self, response: StringDict) -> None: - """ - Handle the notification received from the server: call the appropriate callback function - """ - method = response.get("method", "") - params = response.get("params") - handler = self.on_notification_handlers.get(method) - if not handler: - self._log(f"unhandled {method}") - return - try: - await handler(params) - except asyncio.CancelledError: - return - except Exception as ex: - if (not self._received_shutdown) and self.logger: - self.logger( - "client", - "logger", - str( - { - "type": MessageType.error, - "message": str(ex), - "method": method, - "params": params, - } - ), - ) diff --git a/src/solidlsp/multilspy_settings.py b/src/solidlsp/settings.py similarity index 96% rename from src/solidlsp/multilspy_settings.py rename to src/solidlsp/settings.py index b404030..166be83 100644 --- a/src/solidlsp/multilspy_settings.py +++ b/src/solidlsp/settings.py @@ -6,7 +6,7 @@ import os import pathlib -class MultilspySettings: +class SolidLSPSettings: """ Provides the various settings for multilspy. """ diff --git a/src/solidlsp/type_helpers.py b/src/solidlsp/type_helpers.py deleted file mode 100644 index 53a10de..0000000 --- a/src/solidlsp/type_helpers.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -This module provides type-helpers used across multilspy implementation -""" - -import inspect -from collections.abc import Callable -from typing import TypeVar - -R = TypeVar("R", bound=object) - - -def ensure_all_methods_implemented( - source_cls: type[object], -) -> Callable[[type[R]], type[R]]: - """ - A decorator to ensure that all methods of source_cls class are implemented in the decorated class. - """ - - def check_all_methods_implemented(target_cls: R) -> R: - for name, _ in inspect.getmembers(source_cls, inspect.isfunction): - if name.startswith("_"): - continue - if name not in target_cls.__dict__ or not callable(target_cls.__dict__[name]): - raise NotImplementedError(f"{name} is not implemented in {target_cls}") - - return target_cls - - return check_all_methods_implemented diff --git a/test/conftest.py b/test/conftest.py index 1cb81ea..6632f3a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -6,8 +6,8 @@ from sensai.util.logging import configure from serena.util.file_system import GitignoreParser from solidlsp.ls import SolidLanguageServer -from solidlsp.multilspy_config import Language, MultilspyConfig -from solidlsp.multilspy_logger import MultilspyLogger +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger configure(level=logging.DEBUG) @@ -40,8 +40,8 @@ def create_ls( gitignore_parser = GitignoreParser(str(repo_path)) for spec in gitignore_parser.get_ignore_specs(): ignored_paths.extend(spec.patterns) - config = MultilspyConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication) - logger = MultilspyLogger(log_level=log_level) + config = LanguageServerConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication) + logger = LanguageServerLogger(log_level=log_level) return SolidLanguageServer.create(config, logger, repo_path) diff --git a/test/multilspy/go/test_go_basic.py b/test/multilspy/go/test_go_basic.py index 82b6960..5632315 100644 --- a/test/multilspy/go/test_go_basic.py +++ b/test/multilspy/go/test_go_basic.py @@ -3,8 +3,8 @@ import os import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language -from solidlsp.multilspy_utils import SymbolUtils +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.go diff --git a/test/multilspy/java/test_java_basic.py b/test/multilspy/java/test_java_basic.py index f592373..d74a9fe 100644 --- a/test/multilspy/java/test_java_basic.py +++ b/test/multilspy/java/test_java_basic.py @@ -3,8 +3,8 @@ import os import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language -from solidlsp.multilspy_utils import SymbolUtils +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.java diff --git a/test/multilspy/php/test_php_basic.py b/test/multilspy/php/test_php_basic.py index 3ae2797..391c565 100644 --- a/test/multilspy/php/test_php_basic.py +++ b/test/multilspy/php/test_php_basic.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language +from solidlsp.ls_config import Language @pytest.mark.php diff --git a/test/multilspy/python/test_python_basic.py b/test/multilspy/python/test_python_basic.py index 541f7f5..74737c4 100644 --- a/test/multilspy/python/test_python_basic.py +++ b/test/multilspy/python/test_python_basic.py @@ -11,7 +11,7 @@ import pytest from serena.text_utils import LineType from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language +from solidlsp.ls_config import Language @pytest.mark.python diff --git a/test/multilspy/python/test_retrieval_with_ignored_dirs.py b/test/multilspy/python/test_retrieval_with_ignored_dirs.py index f100d41..a3d7d6a 100644 --- a/test/multilspy/python/test_retrieval_with_ignored_dirs.py +++ b/test/multilspy/python/test_retrieval_with_ignored_dirs.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language +from solidlsp.ls_config import Language from test.conftest import create_ls # This mark will be applied to all tests in this module diff --git a/test/multilspy/python/test_symbol_retrieval.py b/test/multilspy/python/test_symbol_retrieval.py index 97f5d44..c19bf8b 100644 --- a/test/multilspy/python/test_symbol_retrieval.py +++ b/test/multilspy/python/test_symbol_retrieval.py @@ -11,8 +11,8 @@ import os import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language -from solidlsp.multilspy_types import SymbolKind +from solidlsp.ls_config import Language +from solidlsp.ls_types import SymbolKind pytestmark = pytest.mark.python diff --git a/test/multilspy/rust/test_rust_basic.py b/test/multilspy/rust/test_rust_basic.py index e6cd6f4..d0576de 100644 --- a/test/multilspy/rust/test_rust_basic.py +++ b/test/multilspy/rust/test_rust_basic.py @@ -3,8 +3,8 @@ import os import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language -from solidlsp.multilspy_utils import SymbolUtils +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.rust diff --git a/test/multilspy/typescript/test_typescript_basic.py b/test/multilspy/typescript/test_typescript_basic.py index 1c6cb5f..1410c5c 100644 --- a/test/multilspy/typescript/test_typescript_basic.py +++ b/test/multilspy/typescript/test_typescript_basic.py @@ -3,8 +3,8 @@ import os import pytest from solidlsp import SolidLanguageServer -from solidlsp.multilspy_config import Language -from solidlsp.multilspy_utils import SymbolUtils +from solidlsp.ls_config import Language +from solidlsp.ls_utils import SymbolUtils @pytest.mark.typescript diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py index a31ccd4..4e29785 100644 --- a/test/serena/test_serena_agent.py +++ b/test/serena/test_serena_agent.py @@ -7,7 +7,7 @@ import pytest from serena.agent import FindReferencingSymbolsTool, FindSymbolTool, Project, ProjectConfig, SerenaAgent, SerenaConfigBase from serena.process_isolated_agent import ProcessIsolatedSerenaAgent -from solidlsp.multilspy_config import Language +from solidlsp.ls_config import Language from test.conftest import get_repo_path diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index bf6e2a3..86fdc7c 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -12,7 +12,7 @@ from typing import Literal import pytest from serena.symbol import CodeDiff -from solidlsp.multilspy_config import Language +from solidlsp.ls_config import Language from src.serena.symbol import SymbolManager from test.conftest import create_ls, get_repo_path