Skip to main content
Glama
zls.py10.2 kB
""" Provides Zig specific instantiation of the LanguageServer class using ZLS (Zig Language Server). """ import logging import os import pathlib import platform import shutil import subprocess import threading from overrides import override 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 log = logging.getLogger(__name__) class ZigLanguageServer(SolidLanguageServer): """ Provides Zig specific instantiation of the LanguageServer class using ZLS. """ @override def is_ignored_dirname(self, dirname: str) -> bool: # For Zig projects, we should ignore: # - zig-cache: build cache directory # - zig-out: default build output directory # - .zig-cache: alternative cache location # - node_modules: if the project has JavaScript components return super().is_ignored_dirname(dirname) or dirname in ["zig-cache", "zig-out", ".zig-cache", "node_modules", "build", "dist"] @staticmethod def _get_zig_version() -> str | None: """Get the installed Zig version or None if not found.""" try: result = subprocess.run(["zig", "version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: return None return None @staticmethod def _get_zls_version() -> str | None: """Get the installed ZLS version or None if not found.""" try: result = subprocess.run(["zls", "--version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: return None return None @staticmethod def _check_zls_installed() -> bool: """Check if ZLS is installed in the system.""" return shutil.which("zls") is not None @staticmethod def _setup_runtime_dependency() -> bool: """ Check if required Zig runtime dependencies are available. Raises RuntimeError with helpful message if dependencies are missing. """ # Check for Windows and provide error message if platform.system() == "Windows": raise RuntimeError( "Windows is not supported by ZLS in this integration. " "Cross-file references don't work reliably on Windows. Reason unknown." ) zig_version = ZigLanguageServer._get_zig_version() if not zig_version: raise RuntimeError( "Zig is not installed. Please install Zig from https://ziglang.org/download/ and make sure it is added to your PATH." ) if not ZigLanguageServer._check_zls_installed(): zls_version = ZigLanguageServer._get_zls_version() if not zls_version: raise RuntimeError( "Found Zig but ZLS (Zig Language Server) is not installed.\n" "Please install ZLS from https://github.com/zigtools/zls\n" "You can install it via:\n" " - Package managers (brew install zls, scoop install zls, etc.)\n" " - Download pre-built binaries from GitHub releases\n" " - Build from source with: zig build -Doptimize=ReleaseSafe\n\n" "After installation, make sure 'zls' is added to your PATH." ) return True def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): self._setup_runtime_dependency() super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd="zls", cwd=repository_root_path), "zig", solidlsp_settings) self.server_ready = threading.Event() self.request_id = 0 @staticmethod def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the Zig Language Server. """ root_uri = pathlib.Path(repository_absolute_path).as_uri() initialize_params = { "locale": "en", "capabilities": { "textDocument": { "synchronization": {"didSave": True, "dynamicRegistration": True}, "definition": {"dynamicRegistration": True}, "references": {"dynamicRegistration": True}, "documentSymbol": { "dynamicRegistration": True, "hierarchicalDocumentSymbolSupport": True, "symbolKind": {"valueSet": list(range(1, 27))}, }, "completion": { "dynamicRegistration": True, "completionItem": { "snippetSupport": True, "commitCharactersSupport": True, "documentationFormat": ["markdown", "plaintext"], "deprecatedSupport": True, "preselectSupport": True, }, }, "hover": { "dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"], }, }, "workspace": { "workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}, "configuration": True, }, }, "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": root_uri, "workspaceFolders": [ { "uri": root_uri, "name": os.path.basename(repository_absolute_path), } ], "initializationOptions": { # ZLS specific options based on schema.json # Critical paths for ZLS to understand the project "zig_exe_path": shutil.which("zig"), # Path to zig executable "zig_lib_path": None, # Let ZLS auto-detect "build_runner_path": None, # Let ZLS use its built-in runner "global_cache_path": None, # Let ZLS use default cache # Build configuration "enable_build_on_save": True, # Enable to analyze project structure "build_on_save_args": ["build"], # Features "enable_snippets": True, "enable_argument_placeholders": True, "semantic_tokens": "full", "warn_style": False, "highlight_global_var_declarations": False, "skip_std_references": False, "prefer_ast_check_as_child_process": True, "completion_label_details": True, # Inlay hints configuration "inlay_hints_show_variable_type_hints": True, "inlay_hints_show_struct_literal_field_type": True, "inlay_hints_show_parameter_name": True, "inlay_hints_show_builtin": True, "inlay_hints_exclude_single_argument": True, "inlay_hints_hide_redundant_param_names": False, "inlay_hints_hide_redundant_param_names_last_token": False, }, } return initialize_params # type: ignore[return-value] def _start_server(self) -> None: """Start ZLS server process""" def register_capability_handler(params: dict) -> None: return def window_log_message(msg: dict) -> None: log.info(f"LSP: window/logMessage: {msg}") def do_nothing(params: dict) -> None: return self.server.on_request("client/registerCapability", register_capability_handler) self.server.on_notification("window/logMessage", window_log_message) self.server.on_notification("$/progress", do_nothing) self.server.on_notification("textDocument/publishDiagnostics", do_nothing) log.info("Starting ZLS 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) # Verify server capabilities assert "textDocumentSync" in init_response["capabilities"] assert "definitionProvider" in init_response["capabilities"] assert "documentSymbolProvider" in init_response["capabilities"] assert "referencesProvider" in init_response["capabilities"] self.server.notify.initialized({}) self.completions_available.set() # ZLS server is ready after initialization self.server_ready.set() self.server_ready.wait() # Open build.zig if it exists to help ZLS understand project structure build_zig_path = os.path.join(self.repository_root_path, "build.zig") if os.path.exists(build_zig_path): try: with open(build_zig_path, encoding="utf-8") as f: content = f.read() uri = pathlib.Path(build_zig_path).as_uri() self.server.notify.did_open_text_document( { "textDocument": { "uri": uri, "languageId": "zig", "version": 1, "text": content, } } ) log.info("Opened build.zig to provide project context to ZLS") except Exception as e: log.warning(f"Failed to open build.zig: {e}")

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