From 0a40f580dd2424a1bf36668322f86e231ca25817 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 10 Nov 2025 18:50:10 +0100 Subject: [PATCH 1/3] Improve handling of custom LS-specific settings --- .../language_servers/csharp_language_server.py | 6 +++--- src/solidlsp/language_servers/eclipse_jdtls.py | 2 +- src/solidlsp/language_servers/intelephense.py | 9 ++++----- src/solidlsp/ls.py | 1 + src/solidlsp/settings.py | 11 ++++++++++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index d5531fd2..ae044aae 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -248,8 +248,7 @@ class CSharpLanguageServer(SolidLanguageServer): Returns a tuple of (dotnet_path, language_server_dll_path). """ language_enum = cls.get_language_enum_instance() - ls_specific_settings = solidlsp_settings.ls_specific_settings or {} - language_specific_config = ls_specific_settings.get(language_enum, {}) + language_specific_config = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance()) runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", [])) logger.log("Resolving runtime dependencies", logging.DEBUG) @@ -448,7 +447,8 @@ class CSharpLanguageServer(SolidLanguageServer): logger.log("Downloading .NET 9 runtime...", logging.INFO) dotnet_dir.mkdir(parents=True, exist_ok=True) - custom_dotnet_runtime_url = solidlsp_settings.ls_specific_settings.get(cls.get_language_enum_instance(), {}).get( + custom_settings = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance()) + custom_dotnet_runtime_url = custom_settings.get( "dotnet_runtime_url" ) if custom_dotnet_runtime_url is not None: diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index e1181902..095767e0 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -343,7 +343,7 @@ class EclipseJDTLS(SolidLanguageServer): repo_uri = pathlib.Path(repository_absolute_path).as_uri() # Load user's Maven and Gradle configuration paths from ls_specific_settings["java"] - custom_java_settings = self._solidlsp_settings.ls_specific_settings.get(self.get_language_enum_instance(), {}) + custom_java_settings = self._custom_settings # Maven settings: default to ~/.m2/settings.xml default_maven_settings = os.path.join(os.path.expanduser("~"), ".m2", "settings.xml") diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py index dfac4eb5..f03262da 100644 --- a/src/solidlsp/language_servers/intelephense.py +++ b/src/solidlsp/language_servers/intelephense.py @@ -26,8 +26,8 @@ class Intelephense(SolidLanguageServer): Provides PHP specific instantiation of the LanguageServer class using Intelephense. You can pass the following entries in ls_specific_settings["php"]: - - maxMemory - - maxFileSize + - maxMemory: sets intelephense.maxMemory + - maxFileSize: sets intelephense.files.maxSize """ @override @@ -130,9 +130,8 @@ class Intelephense(SolidLanguageServer): if license_key: initialization_options["licenceKey"] = license_key - custom_intelephense_settings = self._solidlsp_settings.ls_specific_settings.get(self.get_language_enum_instance(), {}) - max_memory = custom_intelephense_settings.get("maxMemory") - max_file_size = custom_intelephense_settings.get("maxFileSize") + max_memory = self._custom_settings.get("maxMemory") + max_file_size = self._custom_settings.get("maxFileSize") if max_memory is not None: initialization_options["intelephense.maxMemory"] = max_memory if max_file_size is not None: diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 93575dbb..5da8b68d 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -176,6 +176,7 @@ class SolidLanguageServer(ABC): as opposed to HTTP, TCP modes supported by some language servers. """ self._solidlsp_settings = solidlsp_settings + self._custom_settings = solidlsp_settings.get_ls_specific_settings(self.get_language_enum_instance()) self._encoding = config.encoding self.logger = logger self.repository_root_path: str = repository_root_path diff --git a/src/solidlsp/settings.py b/src/solidlsp/settings.py index 66ecadf2..0e9e9afc 100644 --- a/src/solidlsp/settings.py +++ b/src/solidlsp/settings.py @@ -23,7 +23,7 @@ class SolidLSPSettings: For instance, if this is ".solidlsp" and the project is located at "/home/user/myproject", then Solid-LSP will store project-specific data in "/home/user/myproject/.solidlsp". """ - ls_specific_settings: dict["Language", Any] = field(default_factory=dict) + ls_specific_settings: dict["Language", dict[str, Any]] = field(default_factory=dict) """ Advanced configuration option allowing to configure language server implementation specific options. Have a look at the docstring of the constructors of the corresponding LS implementations within solidlsp to see which options are available. @@ -37,3 +37,12 @@ class SolidLSPSettings: @property def ls_resources_dir(self): return os.path.join(str(self.solidlsp_dir), "language_servers", "static") + + def get_ls_specific_settings(self, language: "Language") -> dict[str, Any]: + """ + Get the language server specific settings for the given language. + + :param language: The programming language. + :return: A dictionary of settings for the language server. + """ + return self.ls_specific_settings.get(language, {}) From 064224d6aef0908b9546f952c13b81caec188410 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 10 Nov 2025 20:08:58 +0100 Subject: [PATCH 2/3] Refactor retrieval of LS-specific settings, adding abstraction and ensuring that all relevant retrievals are logged --- .../csharp_language_server.py | 10 +---- .../language_servers/eclipse_jdtls.py | 41 +++++++++---------- src/solidlsp/ls.py | 7 +++- src/solidlsp/settings.py | 29 ++++++++++++- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index ae044aae..09b2570d 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -247,16 +247,10 @@ class CSharpLanguageServer(SolidLanguageServer): Ensure .NET runtime and Microsoft.CodeAnalysis.LanguageServer are available. Returns a tuple of (dotnet_path, language_server_dll_path). """ - language_enum = cls.get_language_enum_instance() language_specific_config = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance()) runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", [])) logger.log("Resolving runtime dependencies", logging.DEBUG) - if language_specific_config: - logger.log( - f"Language-specific config for {language_enum}: {language_specific_config}", - logging.DEBUG, - ) runtime_dependencies = RuntimeDependencyCollection( _RUNTIME_DEPENDENCIES, @@ -448,9 +442,7 @@ class CSharpLanguageServer(SolidLanguageServer): dotnet_dir.mkdir(parents=True, exist_ok=True) custom_settings = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance()) - custom_dotnet_runtime_url = custom_settings.get( - "dotnet_runtime_url" - ) + custom_dotnet_runtime_url = custom_settings.get("dotnet_runtime_url") if custom_dotnet_runtime_url is not None: logger.log(f"Using custom .NET runtime url: {custom_dotnet_runtime_url}", logging.INFO) url = custom_dotnet_runtime_url diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index 095767e0..d5566a4b 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -343,52 +343,51 @@ class EclipseJDTLS(SolidLanguageServer): repo_uri = pathlib.Path(repository_absolute_path).as_uri() # Load user's Maven and Gradle configuration paths from ls_specific_settings["java"] - custom_java_settings = self._custom_settings # Maven settings: default to ~/.m2/settings.xml - default_maven_settings = os.path.join(os.path.expanduser("~"), ".m2", "settings.xml") - maven_user_settings = None - if "maven_user_settings" in custom_java_settings: + default_maven_settings_path = os.path.join(os.path.expanduser("~"), ".m2", "settings.xml") + custom_maven_settings_path = self._custom_settings.get("maven_user_settings") + if custom_maven_settings_path is not None: # User explicitly provided a path - maven_settings_path = custom_java_settings["maven_user_settings"] - if not os.path.exists(maven_settings_path): + if not os.path.exists(custom_maven_settings_path): error_msg = ( - f"Maven settings file not found: {maven_settings_path}. " + f"Provided maven settings file not found: {custom_maven_settings_path}. " f"Fix: create the file, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> maven_user_settings), " - f"or remove the setting to use default (~/.m2/settings.xml)" + f"or remove the setting to use default ({default_maven_settings_path})" ) self.logger.log(error_msg, logging.ERROR) raise FileNotFoundError(error_msg) - maven_user_settings = maven_settings_path - self.logger.log(f"Using Maven settings from custom location: {maven_user_settings}", logging.INFO) - elif os.path.exists(default_maven_settings): - maven_user_settings = default_maven_settings - self.logger.log(f"Using Maven settings from default location: {maven_user_settings}", logging.INFO) + maven_settings_path = custom_maven_settings_path + self.logger.log(f"Using Maven settings from custom location: {maven_settings_path}", logging.INFO) + elif os.path.exists(default_maven_settings_path): + maven_settings_path = default_maven_settings_path + self.logger.log(f"Using Maven settings from default location: {maven_settings_path}", logging.INFO) else: + maven_settings_path = None self.logger.log( - f"Maven settings not found at default location ({default_maven_settings}), will use JDTLS defaults", logging.INFO + f"Maven settings not found at default location ({default_maven_settings_path}), will use JDTLS defaults", logging.INFO ) # Gradle user home: default to ~/.gradle default_gradle_home = os.path.join(os.path.expanduser("~"), ".gradle") - gradle_user_home = None - if "gradle_user_home" in custom_java_settings: + custom_gradle_home = self._custom_settings.get("gradle_user_home") + if custom_gradle_home is not None: # User explicitly provided a path - gradle_user_home_path = custom_java_settings["gradle_user_home"] - if not os.path.exists(gradle_user_home_path): + if not os.path.exists(custom_gradle_home): error_msg = ( - f"Gradle user home directory not found: {gradle_user_home_path}. " + f"Gradle user home directory not found: {custom_gradle_home}. " f"Fix: create the directory, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> gradle_user_home), " f"or remove the setting to use default (~/.gradle)" ) self.logger.log(error_msg, logging.ERROR) raise FileNotFoundError(error_msg) - gradle_user_home = gradle_user_home_path + gradle_user_home = custom_gradle_home self.logger.log(f"Using Gradle user home from custom location: {gradle_user_home}", logging.INFO) elif os.path.exists(default_gradle_home): gradle_user_home = default_gradle_home self.logger.log(f"Using Gradle user home from default location: {gradle_user_home}", logging.INFO) else: + gradle_user_home = None self.logger.log( f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults", logging.INFO ) @@ -579,7 +578,7 @@ class EclipseJDTLS(SolidLanguageServer): "checkProjectSettingsExclusions": False, "updateBuildConfiguration": "interactive", "maven": { - "userSettings": maven_user_settings, + "userSettings": maven_settings_path, "globalSettings": None, "notCoveredPluginExecutionSeverity": "warning", "defaultMojoExecutionAction": "ignore", diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 5da8b68d..ceec60df 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -176,7 +176,12 @@ class SolidLanguageServer(ABC): as opposed to HTTP, TCP modes supported by some language servers. """ self._solidlsp_settings = solidlsp_settings - self._custom_settings = solidlsp_settings.get_ls_specific_settings(self.get_language_enum_instance()) + lang = self.get_language_enum_instance() + self._custom_settings = solidlsp_settings.get_ls_specific_settings(lang) + logger.log( + f"Custom config (LS-specific settings) for {lang}: {self._custom_settings}", + logging.DEBUG, + ) self._encoding = config.encoding self.logger = logger self.repository_root_path: str = repository_root_path diff --git a/src/solidlsp/settings.py b/src/solidlsp/settings.py index 0e9e9afc..dff5df9a 100644 --- a/src/solidlsp/settings.py +++ b/src/solidlsp/settings.py @@ -2,14 +2,19 @@ Defines settings for Solid-LSP """ +import logging import os import pathlib from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any +from sensai.util.string import ToStringMixin + if TYPE_CHECKING: from solidlsp.ls_config import Language +log = logging.getLogger(__name__) + @dataclass class SolidLSPSettings: @@ -38,11 +43,31 @@ class SolidLSPSettings: def ls_resources_dir(self): return os.path.join(str(self.solidlsp_dir), "language_servers", "static") - def get_ls_specific_settings(self, language: "Language") -> dict[str, Any]: + class CustomLSSettings(ToStringMixin): + def __init__(self, settings: dict[str, Any] | None) -> None: + self.settings = settings or {} + + def get(self, key: str, default_value: Any = None) -> Any: + """ + Returns the custom setting for the given key or the default value if not set. + If a custom value is set for the given key, the retrieval is logged. + + :param key: the key + :param default_value: the default value to use if no custom value is set + :return: the value + """ + if key in self.settings: + value = self.settings[key] + log.info("Using custom LS setting %s for key '%s'", value, key) + else: + value = default_value + return value + + def get_ls_specific_settings(self, language: "Language") -> CustomLSSettings: """ Get the language server specific settings for the given language. :param language: The programming language. :return: A dictionary of settings for the language server. """ - return self.ls_specific_settings.get(language, {}) + return self.CustomLSSettings(self.ls_specific_settings.get(language)) From cb038418eb527086b85d0659f31ada0294140b72 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 10 Nov 2025 20:29:46 +0100 Subject: [PATCH 3/3] Intelephense: Add customizable setting ignore_vendor Resolves #731 --- src/solidlsp/language_servers/intelephense.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py index f03262da..aabcb583 100644 --- a/src/solidlsp/language_servers/intelephense.py +++ b/src/solidlsp/language_servers/intelephense.py @@ -28,15 +28,12 @@ class Intelephense(SolidLanguageServer): You can pass the following entries in ls_specific_settings["php"]: - maxMemory: sets intelephense.maxMemory - maxFileSize: sets intelephense.files.maxSize + - ignore_vendor: whether or ignore directories named "vendor" (default: true) """ @override def is_ignored_dirname(self, dirname: str) -> bool: - # For PHP projects, we should ignore: - # - vendor: third-party dependencies list[str]: @@ -100,6 +97,18 @@ class Intelephense(SolidLanguageServer): ) self.request_id = 0 + # For PHP projects, we should ignore: + # - node_modules: if the project has JavaScript components + # - cache: commonly used for caching + # - (configurable) vendor: third-party dependencies InitializeParams: """ Returns the initialization params for the Intelephense Language Server.