Skip to main content
Glama
clangd_language_server.py9.97 kB
""" Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++. """ import logging import os import pathlib import threading from typing import Any, cast from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings from .common import RuntimeDependency, RuntimeDependencyCollection log = logging.getLogger(__name__) class ClangdLanguageServer(SolidLanguageServer): """ Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++. As the project gets bigger in size, building index will take time. Try running clangd multiple times to ensure index is built properly. Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example. """ def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): """ Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ clangd_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings) super().__init__( config, repository_root_path, ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path), "cpp", solidlsp_settings ) self.server_ready = threading.Event() self.service_ready_event = threading.Event() self.initialize_searcher_command_available = threading.Event() self.resolve_main_method_available = threading.Event() @classmethod def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str: """ Setup runtime dependencies for ClangdLanguageServer and return the command to start the server. """ import shutil deps = RuntimeDependencyCollection( [ RuntimeDependency( id="Clangd", description="Clangd for Linux (x64)", url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip", platform_id="linux-x64", archive_type="zip", binary_name="clangd_19.1.2/bin/clangd", ), RuntimeDependency( id="Clangd", description="Clangd for Windows (x64)", url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip", platform_id="win-x64", archive_type="zip", binary_name="clangd_19.1.2/bin/clangd.exe", ), RuntimeDependency( id="Clangd", description="Clangd for macOS (x64)", url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip", platform_id="osx-x64", archive_type="zip", binary_name="clangd_19.1.2/bin/clangd", ), RuntimeDependency( id="Clangd", description="Clangd for macOS (Arm64)", url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip", platform_id="osx-arm64", archive_type="zip", binary_name="clangd_19.1.2/bin/clangd", ), ] ) clangd_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "clangd") try: dep = deps.get_single_dep_for_current_platform() except RuntimeError: dep = None if dep is None: # No prebuilt binary available, look for system-installed clangd clangd_executable_path = shutil.which("clangd") if not clangd_executable_path: raise FileNotFoundError( "Clangd is not installed on your system.\n" + "Please install clangd using your system package manager:\n" + " Ubuntu/Debian: sudo apt-get install clangd\n" + " Fedora/RHEL: sudo dnf install clang-tools-extra\n" + " Arch Linux: sudo pacman -S clang\n" + "See https://clangd.llvm.org/installation for more details." ) log.info(f"Using system-installed clangd at {clangd_executable_path}") else: # Standard download and install for platforms with prebuilt binaries clangd_executable_path = deps.binary_path(clangd_ls_dir) if not os.path.exists(clangd_executable_path): log.info(f"Clangd executable not found at {clangd_executable_path}. Downloading from {dep.url}") _ = deps.install(clangd_ls_dir) if not os.path.exists(clangd_executable_path): raise FileNotFoundError( f"Clangd executable not found at {clangd_executable_path}.\n" + "Make sure you have installed clangd. See https://clangd.llvm.org/installation" ) os.chmod(clangd_executable_path, 0o755) return clangd_executable_path @staticmethod def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the clangd Language Server. """ root_uri = pathlib.Path(repository_absolute_path).as_uri() initialize_params = { "locale": "en", "capabilities": { "textDocument": { "synchronization": {"didSave": True, "dynamicRegistration": True}, "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}}, "definition": {"dynamicRegistration": True}, }, "workspace": {"workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}}, }, "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": root_uri, "workspaceFolders": [ { "uri": root_uri, "name": "$name", } ], } return cast(InitializeParams, initialize_params) def _start_server(self) -> None: """ Starts the Clangd Language Server, waits for the server to be ready and yields the LanguageServer instance. Usage: ``` async with lsp.start_server(): # LanguageServer has been initialized and ready to serve requests await lsp.request_definition(...) await lsp.request_references(...) # Shutdown the LanguageServer on exit from scope # LanguageServer has been shutdown """ def register_capability_handler(params: Any) -> None: assert "registrations" in params for registration in params["registrations"]: if registration["method"] == "workspace/executeCommand": self.initialize_searcher_command_available.set() self.resolve_main_method_available.set() return def lang_status_handler(params: Any) -> None: # TODO: Should we wait for # server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}} # Before proceeding? if params["type"] == "ServiceReady" and params["message"] == "ServiceReady": self.service_ready_event.set() def execute_client_command_handler(params: Any) -> list: return [] def do_nothing(params: Any) -> None: return def check_experimental_status(params: Any) -> None: if params["quiescent"] == True: self.server_ready.set() def window_log_message(msg: dict) -> None: log.info(f"LSP: window/logMessage: {msg}") self.server.on_request("client/registerCapability", register_capability_handler) self.server.on_notification("language/status", lang_status_handler) self.server.on_notification("window/logMessage", window_log_message) self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) self.server.on_notification("$/progress", do_nothing) self.server.on_notification("textDocument/publishDiagnostics", do_nothing) self.server.on_notification("language/actionableNotification", do_nothing) self.server.on_notification("experimental/serverStatus", check_experimental_status) log.info("Starting Clangd server process") self.server.start() initialize_params = self._get_initialize_params(self.repository_root_path) log.info("Sending initialize request from LSP client to LSP server and awaiting response") init_response = self.server.send.initialize(initialize_params) assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore assert "completionProvider" in init_response["capabilities"] assert init_response["capabilities"]["completionProvider"] == { "triggerCharacters": [".", "<", ">", ":", '"', "/", "*"], "resolveProvider": False, } self.server.notify.initialized({}) self.completions_available.set() # set ready flag self.server_ready.set() self.server_ready.wait()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/oraios/serena'

If you have feedback or need assistance with the MCP directory API, please join our Discord server