From 5bafde5e355ecd5087c36e0975f46522f88c2f98 Mon Sep 17 00:00:00 2001 From: Sergey Belov Date: Thu, 2 Apr 2026 01:20:00 +0300 Subject: [PATCH] feat(solidlsp): add ls_args and ls_extra_args to LanguageServerDependencyProviderSinglePath - ls_extra_args appends to the default launch command args - ls_args fully replaces the default args (executable path is always kept) - Documented new options in ClangdLanguageServer docstring - Added unit tests for all combinations in test_cpp_dependency_provider.py --- .../clangd_language_server.py | 4 ++ src/solidlsp/ls.py | 7 +- .../cpp/test_cpp_dependency_provider.py | 69 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 test/solidlsp/cpp/test_cpp_dependency_provider.py diff --git a/src/solidlsp/language_servers/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server.py index c5488810b..9fb47cb60 100644 --- a/src/solidlsp/language_servers/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server.py @@ -28,6 +28,10 @@ class ClangdLanguageServer(SolidLanguageServer): ``compile_commands.json`` if needed. - clangd_version: Override the pinned Clangd version downloaded by Serena (default: the bundled Serena version). + - ls_extra_args: Extra arguments appended to the default clangd launch command + (e.g. ``["--query-driver=/usr/bin/arm-none-eabi-gcc"]``). + - ls_args: Fully replace the default clangd arguments (executable path is kept). + Use when you need complete control over the argument list. """ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index c33628115..6c4c4a21b 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -324,7 +324,12 @@ def create_launch_command(self) -> list[str]: core_path = path else: core_path = self._get_or_install_core_dependency() - return self._create_launch_command(core_path) + ls_args: list[str] | None = self._custom_settings.get("ls_args", None) + if ls_args is not None: + return [core_path] + ls_args + cmd = self._create_launch_command(core_path) + ls_extra_args: list[str] = self._custom_settings.get("ls_extra_args", []) + return cmd + ls_extra_args @abstractmethod def _create_launch_command(self, core_path: str) -> list[str]: diff --git a/test/solidlsp/cpp/test_cpp_dependency_provider.py b/test/solidlsp/cpp/test_cpp_dependency_provider.py new file mode 100644 index 000000000..69632ac6d --- /dev/null +++ b/test/solidlsp/cpp/test_cpp_dependency_provider.py @@ -0,0 +1,69 @@ +"""Tests for ClangdLanguageServer.DependencyProvider launch command construction.""" + +from pathlib import Path +from unittest.mock import patch + +import pytest + +from solidlsp.language_servers.clangd_language_server import ClangdLanguageServer +from solidlsp.settings import SolidLSPSettings + + +def _make_provider( + tmp_path: Path, + custom_settings: dict | None = None, +) -> ClangdLanguageServer.DependencyProvider: + return ClangdLanguageServer.DependencyProvider( + custom_settings=SolidLSPSettings.CustomLSSettings(custom_settings or {}), + ls_resources_dir=str(tmp_path), + ) + + +@pytest.mark.cpp +class TestClangdDependencyProvider: + def test_default_args(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path) + with patch.object(provider, "_get_or_install_core_dependency", return_value="/usr/bin/clangd"): + cmd = provider.create_launch_command() + assert cmd == ["/usr/bin/clangd", "--background-index"] + + def test_ls_extra_args_appended_to_defaults(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path, {"ls_extra_args": ["--query-driver=/usr/bin/gcc", "--log=verbose"]}) + with patch.object(provider, "_get_or_install_core_dependency", return_value="/usr/bin/clangd"): + cmd = provider.create_launch_command() + assert cmd == ["/usr/bin/clangd", "--background-index", "--query-driver=/usr/bin/gcc", "--log=verbose"] + + def test_ls_args_replaces_default_args(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path, {"ls_args": ["--log=verbose"]}) + with patch.object(provider, "_get_or_install_core_dependency", return_value="/usr/bin/clangd"): + cmd = provider.create_launch_command() + assert cmd == ["/usr/bin/clangd", "--log=verbose"] + + def test_ls_args_empty_list_removes_all_default_args(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path, {"ls_args": []}) + with patch.object(provider, "_get_or_install_core_dependency", return_value="/usr/bin/clangd"): + cmd = provider.create_launch_command() + assert cmd == ["/usr/bin/clangd"] + + def test_ls_path_overrides_executable(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path, {"ls_path": "/custom/clangd"}) + with patch.object( + provider, + "_get_or_install_core_dependency", + side_effect=AssertionError("should not be called when ls_path is set"), + ): + cmd = provider.create_launch_command() + assert cmd[0] == "/custom/clangd" + assert "--background-index" in cmd + + def test_ls_path_with_ls_extra_args(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path, {"ls_path": "/custom/clangd", "ls_extra_args": ["--query-driver=/usr/bin/arm-gcc"]}) + with patch.object(provider, "_get_or_install_core_dependency", side_effect=AssertionError("should not be called")): + cmd = provider.create_launch_command() + assert cmd == ["/custom/clangd", "--background-index", "--query-driver=/usr/bin/arm-gcc"] + + def test_ls_path_with_ls_args(self, tmp_path: Path) -> None: + provider = _make_provider(tmp_path, {"ls_path": "/custom/clangd", "ls_args": ["--log=error"]}) + with patch.object(provider, "_get_or_install_core_dependency", side_effect=AssertionError("should not be called")): + cmd = provider.create_launch_command() + assert cmd == ["/custom/clangd", "--log=error"]