diff --git a/src/solidlsp/initialize_params.py b/src/solidlsp/initialize_params.py new file mode 100644 index 00000000..b8d81c0e --- /dev/null +++ b/src/solidlsp/initialize_params.py @@ -0,0 +1,58 @@ +import logging +import os +import pathlib +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Self, cast + +from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, WorkspaceFolder + +if TYPE_CHECKING: + from solidlsp import SolidLanguageServer + + +log = logging.getLogger(__name__) + + +class InitializeParamsBuilder(ABC): + def __init__(self): + self._params = {} + + def with_base_options(self, options: dict | InitializeParams) -> Self: + self._params.update(options) + return self + + def _set(self, key: str, value: Any) -> None: + if key in self._params: + log.debug("Overriding existing option '%s' with new value: %s (old value: %s)", key, value, self._params[key]) + self._params[key] = value + + @abstractmethod + def _apply_updates(self) -> None: + """ + Applies implementation-specific updates to the options. + """ + + def build(self) -> InitializeParams: + self._apply_updates() + return cast(InitializeParams, self._params) + + +class DefaultInitializeParamsBuilder(InitializeParamsBuilder): + def __init__(self, ls: "SolidLanguageServer", set_workspace_folders: bool = True): + super().__init__() + self._ls = ls + self._set_workspace_folders = set_workspace_folders + + @staticmethod + def _create_workspace_folder_entry(path: str) -> WorkspaceFolder: + abs_path = os.path.abspath(path) + return {"uri": pathlib.Path(abs_path).as_uri(), "name": os.path.basename(abs_path)} + + def _apply_updates(self): + root_abs_path = self._ls.repository_root_path + self._set("processId", os.getpid()) + self._set("rootPath", root_abs_path) + self._set("rootUri", pathlib.Path(root_abs_path).as_uri()) + self._set("clientInfo", {"name": "Serena"}) + if self._set_workspace_folders: + self._set("workspaceFolders", [self._create_workspace_folder_entry(root_abs_path)]) diff --git a/src/solidlsp/language_servers/pyright_server.py b/src/solidlsp/language_servers/pyright_server.py index f369706c..b808d8f7 100644 --- a/src/solidlsp/language_servers/pyright_server.py +++ b/src/solidlsp/language_servers/pyright_server.py @@ -3,8 +3,6 @@ Provides Python specific instantiation of the LanguageServer class. Contains var """ import logging -import os -import pathlib import re import threading from typing import cast @@ -13,7 +11,6 @@ from overrides import override from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderUvx, SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig -from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.settings import SolidLSPSettings log = logging.getLogger(__name__) @@ -59,16 +56,8 @@ class PyrightServer(SolidLanguageServer): def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"] - @staticmethod - def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: - """ - Returns the initialize params for the Pyright Language Server. - """ - # Create basic initialization parameters + def _create_base_initialize_params(self) -> dict: initialize_params = { - "processId": os.getpid(), - "rootPath": repository_absolute_path, - "rootUri": pathlib.Path(repository_absolute_path).as_uri(), "initializationOptions": { "exclude": [ "**/__pycache__", @@ -111,12 +100,8 @@ class PyrightServer(SolidLanguageServer): "publishDiagnostics": {"relatedInformation": True}, }, }, - "workspaceFolders": [ - {"uri": pathlib.Path(repository_absolute_path).as_uri(), "name": os.path.basename(repository_absolute_path)} - ], } - - return cast(InitializeParams, initialize_params) + return initialize_params def _start_server(self) -> None: """ @@ -230,7 +215,7 @@ class PyrightServer(SolidLanguageServer): self.server.start() # Send proper initialization parameters - initialize_params = self._get_initialize_params(self.repository_root_path) + initialize_params = self._create_initialize_params() log.info("Sending initialize request from LSP client to pyright server and awaiting response") init_response = self.server.send.initialize(initialize_params) diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index a769a3b3..ab130be5 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -22,6 +22,7 @@ from sensai.util.string import ToStringMixin from serena.util.file_system import match_path from serena.util.text_utils import MatchedConsecutiveLines from solidlsp import ls_types +from solidlsp.initialize_params import DefaultInitializeParamsBuilder, InitializeParamsBuilder from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_process import LanguageServerInterface, StdioLanguageServer @@ -35,6 +36,7 @@ from solidlsp.lsp_protocol_handler.lsp_types import ( DefinitionParams, DocumentSymbol, ImplementationParams, + InitializeParams, LocationLink, RenameParams, SymbolInformation, @@ -3281,3 +3283,33 @@ class SolidLanguageServer(ABC): def is_running(self) -> bool: return self.server.is_running() + + def _create_initialize_params_builder(self) -> InitializeParamsBuilder: + return DefaultInitializeParamsBuilder(self) + + def _create_base_initialize_params(self) -> dict | InitializeParams: + """ + Subclasses should override this method to provide server-specific InitializeParams settings, + which provide the basis for the InitializeParams object sent to the language server during initialization. + + The returned dictionary is passed to the builder constructed by _create_initialize_params_builder() + in order to create the final InitializeParams object. + + The default builder implementation already sets the following keys, so implementations of this method + should not set them: + + - processId + - rootPath + - rootUri + - clientInfo + - workspaceFolders + + :return: the base InitializeParams settings + """ + raise NotImplementedError + + def _create_initialize_params(self) -> InitializeParams: + """ + Create the InitializeParams object to send to the language server during initialization. + """ + return self._create_initialize_params_builder().with_base_options(self._create_base_initialize_params()).build()