Skip to main content
Glama
rust_analyzer.py30.7 kB
""" Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust. """ import logging import os import pathlib import subprocess import threading from typing import cast 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 RustAnalyzer(SolidLanguageServer): """ Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust. """ @staticmethod def _determine_log_level(line: str) -> int: """Classify rust-analyzer stderr output to avoid false-positive errors.""" line_lower = line.lower() # Known informational/warning messages from rust-analyzer that aren't critical errors if any( [ "failed to find any projects in" in line_lower, "fetchworkspaceerror" in line_lower, ] ): return logging.DEBUG return SolidLanguageServer._determine_log_level(line) @staticmethod def _get_rustup_version() -> str | None: """Get installed rustup version or None if not found.""" try: result = subprocess.run(["rustup", "--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_rust_analyzer_path() -> str | None: """Get rust-analyzer path via rustup. Returns None if not found.""" try: # Note: we avoid using system PATH to avoid picking up incorrect aliases result = subprocess.run(["rustup", "which", "rust-analyzer"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: pass return None @staticmethod def _ensure_rust_analyzer_installed() -> str: """ Ensure rust-analyzer is available, install via rustup if needed. :return: path to rust-analyzer executable """ path = RustAnalyzer._get_rust_analyzer_path() if path: return path # Check if rustup is available if not RustAnalyzer._get_rustup_version(): raise RuntimeError( "Neither rust-analyzer nor rustup is installed.\n" "Please install Rust via https://rustup.rs/ or install rust-analyzer separately." ) # Try to install rust-analyzer component result = subprocess.run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True) if result.returncode != 0: raise RuntimeError(f"Failed to install rust-analyzer via rustup: {result.stderr}") # Try again after installation path = RustAnalyzer._get_rust_analyzer_path() if not path: raise RuntimeError("rust-analyzer installation succeeded but binary not found in PATH") return path def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): """ Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ rustanalyzer_executable_path = self._ensure_rust_analyzer_installed() log.info(f"Using rust-analyzer at: {rustanalyzer_executable_path}") super().__init__( config, repository_root_path, ProcessLaunchInfo(cmd=rustanalyzer_executable_path, cwd=repository_root_path), "rust", 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() @override def is_ignored_dirname(self, dirname: str) -> bool: return super().is_ignored_dirname(dirname) or dirname in ["target"] @staticmethod def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the Rust Analyzer Language Server. """ root_uri = pathlib.Path(repository_absolute_path).as_uri() initialize_params = { "clientInfo": {"name": "Visual Studio Code - Insiders", "version": "1.82.0-insider"}, "locale": "en", "capabilities": { "workspace": { "applyEdit": True, "workspaceEdit": { "documentChanges": True, "resourceOperations": ["create", "rename", "delete"], "failureHandling": "textOnlyTransactional", "normalizesLineEndings": True, "changeAnnotationSupport": {"groupsOnLabel": True}, }, "configuration": True, "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True}, "symbol": { "dynamicRegistration": True, "symbolKind": {"valueSet": list(range(1, 27))}, "tagSupport": {"valueSet": [1]}, "resolveSupport": {"properties": ["location.range"]}, }, "codeLens": {"refreshSupport": True}, "executeCommand": {"dynamicRegistration": True}, "didChangeConfiguration": {"dynamicRegistration": True}, "workspaceFolders": True, "semanticTokens": {"refreshSupport": True}, "fileOperations": { "dynamicRegistration": True, "didCreate": True, "didRename": True, "didDelete": True, "willCreate": True, "willRename": True, "willDelete": True, }, "inlineValue": {"refreshSupport": True}, "inlayHint": {"refreshSupport": True}, "diagnostics": {"refreshSupport": True}, }, "textDocument": { "publishDiagnostics": { "relatedInformation": True, "versionSupport": False, "tagSupport": {"valueSet": [1, 2]}, "codeDescriptionSupport": True, "dataSupport": True, }, "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True}, "completion": { "dynamicRegistration": True, "contextSupport": True, "completionItem": { "snippetSupport": True, "commitCharactersSupport": True, "documentationFormat": ["markdown", "plaintext"], "deprecatedSupport": True, "preselectSupport": True, "tagSupport": {"valueSet": [1]}, "insertReplaceSupport": True, "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]}, "insertTextModeSupport": {"valueSet": [1, 2]}, "labelDetailsSupport": True, }, "insertTextMode": 2, "completionItemKind": { "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] }, "completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode"]}, }, "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, "signatureHelp": { "dynamicRegistration": True, "signatureInformation": { "documentationFormat": ["markdown", "plaintext"], "parameterInformation": {"labelOffsetSupport": True}, "activeParameterSupport": True, }, "contextSupport": True, }, "definition": {"dynamicRegistration": True, "linkSupport": True}, "references": {"dynamicRegistration": True}, "documentHighlight": {"dynamicRegistration": True}, "documentSymbol": { "dynamicRegistration": True, "symbolKind": {"valueSet": list(range(1, 27))}, "hierarchicalDocumentSymbolSupport": True, "tagSupport": {"valueSet": [1]}, "labelSupport": True, }, "codeAction": { "dynamicRegistration": True, "isPreferredSupport": True, "disabledSupport": True, "dataSupport": True, "resolveSupport": {"properties": ["edit"]}, "codeActionLiteralSupport": { "codeActionKind": { "valueSet": [ "", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports", ] } }, "honorsChangeAnnotations": False, }, "codeLens": {"dynamicRegistration": True}, "formatting": {"dynamicRegistration": True}, "rangeFormatting": {"dynamicRegistration": True}, "onTypeFormatting": {"dynamicRegistration": True}, "rename": { "dynamicRegistration": True, "prepareSupport": True, "prepareSupportDefaultBehavior": 1, "honorsChangeAnnotations": True, }, "documentLink": {"dynamicRegistration": True, "tooltipSupport": True}, "typeDefinition": {"dynamicRegistration": True, "linkSupport": True}, "implementation": {"dynamicRegistration": True, "linkSupport": True}, "colorProvider": {"dynamicRegistration": True}, "foldingRange": { "dynamicRegistration": True, "rangeLimit": 5000, "lineFoldingOnly": True, "foldingRangeKind": {"valueSet": ["comment", "imports", "region"]}, "foldingRange": {"collapsedText": False}, }, "declaration": {"dynamicRegistration": True, "linkSupport": True}, "selectionRange": {"dynamicRegistration": True}, "callHierarchy": {"dynamicRegistration": True}, "semanticTokens": { "dynamicRegistration": True, "tokenTypes": [ "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator", ], "tokenModifiers": [ "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", ], "formats": ["relative"], "requests": {"range": True, "full": {"delta": True}}, "multilineTokenSupport": False, "overlappingTokenSupport": False, "serverCancelSupport": True, "augmentsSyntaxTokens": False, }, "linkedEditingRange": {"dynamicRegistration": True}, "typeHierarchy": {"dynamicRegistration": True}, "inlineValue": {"dynamicRegistration": True}, "inlayHint": { "dynamicRegistration": True, "resolveSupport": {"properties": ["tooltip", "textEdits", "label.tooltip", "label.location", "label.command"]}, }, "diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False}, }, "window": { "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}}, "showDocument": {"support": True}, "workDoneProgress": True, }, "general": { "staleRequestSupport": { "cancel": True, "retryOnContentModified": [ "textDocument/semanticTokens/full", "textDocument/semanticTokens/range", "textDocument/semanticTokens/full/delta", ], }, "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"}, "markdown": { "parser": "marked", "version": "1.1.0", "allowedTags": [ "ul", "li", "p", "code", "blockquote", "ol", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "em", "pre", "table", "thead", "tbody", "tr", "th", "td", "div", "del", "a", "strong", "br", "img", "span", ], }, "positionEncodings": ["utf-16"], }, "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}}, "experimental": { "snippetTextEdit": True, "codeActionGroup": True, "hoverActions": True, "serverStatusNotification": True, "colorDiagnosticOutput": True, "openServerLogs": True, "localDocs": True, "commands": { "commands": [ "rust-analyzer.runSingle", "rust-analyzer.debugSingle", "rust-analyzer.showReferences", "rust-analyzer.gotoLocation", "editor.action.triggerParameterHints", ] }, }, }, "initializationOptions": { "cargoRunner": None, "runnables": {"extraEnv": None, "problemMatcher": ["$rustc"], "command": None, "extraArgs": []}, "statusBar": {"clickAction": "openLogs"}, "server": {"path": None, "extraEnv": None}, "trace": {"server": "verbose", "extension": False}, "debug": { "engine": "auto", "sourceFileMap": {"/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"}, "openDebugPane": False, "engineSettings": {}, }, "restartServerOnConfigChange": False, "typing": {"continueCommentsOnNewline": True, "autoClosingAngleBrackets": {"enable": False}}, "diagnostics": { "previewRustcOutput": False, "useRustcErrorCode": False, "disabled": [], "enable": True, "experimental": {"enable": False}, "remapPrefix": {}, "warningsAsHint": [], "warningsAsInfo": [], }, "discoverProjectRunner": None, "showUnlinkedFileNotification": True, "showDependenciesExplorer": True, "assist": {"emitMustUse": False, "expressionFillDefault": "todo"}, "cachePriming": {"enable": True, "numThreads": 0}, "cargo": { "autoreload": True, "buildScripts": { "enable": True, "invocationLocation": "workspace", "invocationStrategy": "per_workspace", "overrideCommand": None, "useRustcWrapper": True, }, "cfgs": [], "extraArgs": [], "extraEnv": {}, "features": [], "noDefaultFeatures": False, "sysroot": "discover", "sysrootSrc": None, "target": None, "unsetTest": ["core"], }, "checkOnSave": True, "check": { "allTargets": True, "command": "check", "extraArgs": [], "extraEnv": {}, "features": None, "ignore": [], "invocationLocation": "workspace", "invocationStrategy": "per_workspace", "noDefaultFeatures": None, "overrideCommand": None, "targets": None, }, "completion": { "autoimport": {"enable": True}, "autoself": {"enable": True}, "callable": {"snippets": "fill_arguments"}, "fullFunctionSignatures": {"enable": False}, "limit": None, "postfix": {"enable": True}, "privateEditable": {"enable": False}, "snippets": { "custom": { "Arc::new": { "postfix": "arc", "body": "Arc::new(${receiver})", "requires": "std::sync::Arc", "description": "Put the expression into an `Arc`", "scope": "expr", }, "Rc::new": { "postfix": "rc", "body": "Rc::new(${receiver})", "requires": "std::rc::Rc", "description": "Put the expression into an `Rc`", "scope": "expr", }, "Box::pin": { "postfix": "pinbox", "body": "Box::pin(${receiver})", "requires": "std::boxed::Box", "description": "Put the expression into a pinned `Box`", "scope": "expr", }, "Ok": { "postfix": "ok", "body": "Ok(${receiver})", "description": "Wrap the expression in a `Result::Ok`", "scope": "expr", }, "Err": { "postfix": "err", "body": "Err(${receiver})", "description": "Wrap the expression in a `Result::Err`", "scope": "expr", }, "Some": { "postfix": "some", "body": "Some(${receiver})", "description": "Wrap the expression in an `Option::Some`", "scope": "expr", }, } }, }, "files": {"excludeDirs": [], "watcher": "client"}, "highlightRelated": { "breakPoints": {"enable": True}, "closureCaptures": {"enable": True}, "exitPoints": {"enable": True}, "references": {"enable": True}, "yieldPoints": {"enable": True}, }, "hover": { "actions": { "debug": {"enable": True}, "enable": True, "gotoTypeDef": {"enable": True}, "implementations": {"enable": True}, "references": {"enable": False}, "run": {"enable": True}, }, "documentation": {"enable": True, "keywords": {"enable": True}}, "links": {"enable": True}, "memoryLayout": {"alignment": "hexadecimal", "enable": True, "niches": False, "offset": "hexadecimal", "size": "both"}, }, "imports": { "granularity": {"enforce": False, "group": "crate"}, "group": {"enable": True}, "merge": {"glob": True}, "preferNoStd": False, "preferPrelude": False, "prefix": "plain", }, "inlayHints": { "bindingModeHints": {"enable": False}, "chainingHints": {"enable": True}, "closingBraceHints": {"enable": True, "minLines": 25}, "closureCaptureHints": {"enable": False}, "closureReturnTypeHints": {"enable": "never"}, "closureStyle": "impl_fn", "discriminantHints": {"enable": "never"}, "expressionAdjustmentHints": {"enable": "never", "hideOutsideUnsafe": False, "mode": "prefix"}, "lifetimeElisionHints": {"enable": "never", "useParameterNames": False}, "maxLength": 25, "parameterHints": {"enable": True}, "reborrowHints": {"enable": "never"}, "renderColons": True, "typeHints": {"enable": True, "hideClosureInitialization": False, "hideNamedConstructor": False}, }, "interpret": {"tests": False}, "joinLines": {"joinAssignments": True, "joinElseIf": True, "removeTrailingComma": True, "unwrapTrivialBlock": True}, "lens": { "debug": {"enable": True}, "enable": True, "forceCustomCommands": True, "implementations": {"enable": True}, "location": "above_name", "references": { "adt": {"enable": False}, "enumVariant": {"enable": False}, "method": {"enable": False}, "trait": {"enable": False}, }, "run": {"enable": True}, }, "linkedProjects": [], "lru": {"capacity": None, "query": {"capacities": {}}}, "notifications": {"cargoTomlNotFound": True}, "numThreads": None, "procMacro": {"attributes": {"enable": True}, "enable": True, "ignored": {}, "server": None}, "references": {"excludeImports": False}, "rust": {"analyzerTargetDir": None}, "rustc": {"source": None}, "rustfmt": {"extraArgs": [], "overrideCommand": None, "rangeFormatting": {"enable": False}}, "semanticHighlighting": { "doc": {"comment": {"inject": {"enable": True}}}, "nonStandardTokens": True, "operator": {"enable": True, "specialization": {"enable": False}}, "punctuation": {"enable": False, "separate": {"macro": {"bang": False}}, "specialization": {"enable": False}}, "strings": {"enable": True}, }, "signatureInfo": {"detail": "full", "documentation": {"enable": True}}, "workspace": {"symbol": {"search": {"kind": "only_types", "limit": 128, "scope": "workspace"}}}, }, "trace": "verbose", "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": root_uri, "workspaceFolders": [ { "uri": root_uri, "name": os.path.basename(repository_absolute_path), } ], } return cast(InitializeParams, initialize_params) def _start_server(self) -> None: """ Starts the Rust Analyzer Language Server """ def register_capability_handler(params: dict) -> 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: dict) -> 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: dict) -> list: return [] def do_nothing(params: dict) -> None: return def check_experimental_status(params: dict) -> 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 RustAnalyzer 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"] == { "resolveProvider": True, "triggerCharacters": [":", ".", "'", "("], "completionItem": {"labelDetailsSupport": True}, } self.server.notify.initialized({}) self.completions_available.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