Skip to main content
Glama
sourcekit_lsp.py18.2 kB
import logging import os import pathlib import subprocess import threading import time from overrides import override from solidlsp import ls_types 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 SourceKitLSP(SolidLanguageServer): """ Provides Swift specific instantiation of the LanguageServer class using sourcekit-lsp. """ @override def is_ignored_dirname(self, dirname: str) -> bool: # For Swift projects, we should ignore: # - .build: Swift Package Manager build artifacts # - .swiftpm: Swift Package Manager metadata # - node_modules: if the project has JavaScript components # - dist/build: common output directories return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"] @staticmethod def _get_sourcekit_lsp_version() -> str: """Get the installed sourcekit-lsp version or raise error if sourcekit was not found.""" try: result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() else: raise Exception(f"`sourcekit-lsp -h` resulted in: {result}") except Exception as e: raise RuntimeError( "Could not find sourcekit-lsp, please install it as described in https://github.com/apple/sourcekit-lsp#installation" "And make sure it is available on your PATH." ) from e def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings): sourcekit_version = self._get_sourcekit_lsp_version() log.info(f"Starting sourcekit lsp with version: {sourcekit_version}") super().__init__( config, repository_root_path, ProcessLaunchInfo(cmd="sourcekit-lsp", cwd=repository_root_path), "swift", solidlsp_settings ) self.server_ready = threading.Event() self.request_id = 0 self._did_sleep_before_requesting_references = False self._initialization_timestamp: float | None = None @staticmethod def _get_initialize_params(repository_absolute_path: str) -> InitializeParams: """ Returns the initialize params for the Swift Language Server. """ root_uri = pathlib.Path(repository_absolute_path).as_uri() initialize_params = { "capabilities": { "general": { "markdown": {"parser": "marked", "version": "1.1.0"}, "positionEncodings": ["utf-16"], "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"}, "staleRequestSupport": { "cancel": True, "retryOnContentModified": [ "textDocument/semanticTokens/full", "textDocument/semanticTokens/range", "textDocument/semanticTokens/full/delta", ], }, }, "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}}, "textDocument": { "callHierarchy": {"dynamicRegistration": True}, "codeAction": { "codeActionLiteralSupport": { "codeActionKind": { "valueSet": [ "", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports", ] } }, "dataSupport": True, "disabledSupport": True, "dynamicRegistration": True, "honorsChangeAnnotations": True, "isPreferredSupport": True, "resolveSupport": {"properties": ["edit"]}, }, "codeLens": {"dynamicRegistration": True}, "colorProvider": {"dynamicRegistration": True}, "completion": { "completionItem": { "commitCharactersSupport": True, "deprecatedSupport": True, "documentationFormat": ["markdown", "plaintext"], "insertReplaceSupport": True, "insertTextModeSupport": {"valueSet": [1, 2]}, "labelDetailsSupport": True, "preselectSupport": True, "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]}, "snippetSupport": True, "tagSupport": {"valueSet": [1]}, }, "completionItemKind": { "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] }, "completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode", "data"]}, "contextSupport": True, "dynamicRegistration": True, "insertTextMode": 2, }, "declaration": {"dynamicRegistration": True, "linkSupport": True}, "definition": {"dynamicRegistration": True, "linkSupport": True}, "diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False}, "documentHighlight": {"dynamicRegistration": True}, "documentLink": {"dynamicRegistration": True, "tooltipSupport": True}, "documentSymbol": { "dynamicRegistration": True, "hierarchicalDocumentSymbolSupport": True, "labelSupport": True, "symbolKind": { "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] }, "tagSupport": {"valueSet": [1]}, }, "foldingRange": { "dynamicRegistration": True, "foldingRange": {"collapsedText": False}, "foldingRangeKind": {"valueSet": ["comment", "imports", "region"]}, "lineFoldingOnly": True, "rangeLimit": 5000, }, "formatting": {"dynamicRegistration": True}, "hover": {"contentFormat": ["markdown", "plaintext"], "dynamicRegistration": True}, "implementation": {"dynamicRegistration": True, "linkSupport": True}, "inlayHint": { "dynamicRegistration": True, "resolveSupport": {"properties": ["tooltip", "textEdits", "label.tooltip", "label.location", "label.command"]}, }, "inlineValue": {"dynamicRegistration": True}, "linkedEditingRange": {"dynamicRegistration": True}, "onTypeFormatting": {"dynamicRegistration": True}, "publishDiagnostics": { "codeDescriptionSupport": True, "dataSupport": True, "relatedInformation": True, "tagSupport": {"valueSet": [1, 2]}, "versionSupport": False, }, "rangeFormatting": {"dynamicRegistration": True, "rangesSupport": True}, "references": {"dynamicRegistration": True}, "rename": { "dynamicRegistration": True, "honorsChangeAnnotations": True, "prepareSupport": True, "prepareSupportDefaultBehavior": 1, }, "selectionRange": {"dynamicRegistration": True}, "semanticTokens": { "augmentsSyntaxTokens": True, "dynamicRegistration": True, "formats": ["relative"], "multilineTokenSupport": False, "overlappingTokenSupport": False, "requests": {"full": {"delta": True}, "range": True}, "serverCancelSupport": True, "tokenModifiers": [ "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", ], "tokenTypes": [ "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator", ], }, "signatureHelp": { "contextSupport": True, "dynamicRegistration": True, "signatureInformation": { "activeParameterSupport": True, "documentationFormat": ["markdown", "plaintext"], "parameterInformation": {"labelOffsetSupport": True}, }, }, "synchronization": {"didSave": True, "dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True}, "typeDefinition": {"dynamicRegistration": True, "linkSupport": True}, "typeHierarchy": {"dynamicRegistration": True}, }, "window": { "showDocument": {"support": True}, "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}}, "workDoneProgress": True, }, "workspace": { "applyEdit": True, "codeLens": {"refreshSupport": True}, "configuration": True, "diagnostics": {"refreshSupport": True}, "didChangeConfiguration": {"dynamicRegistration": True}, "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True}, "executeCommand": {"dynamicRegistration": True}, "fileOperations": { "didCreate": True, "didDelete": True, "didRename": True, "dynamicRegistration": True, "willCreate": True, "willDelete": True, "willRename": True, }, "foldingRange": {"refreshSupport": True}, "inlayHint": {"refreshSupport": True}, "inlineValue": {"refreshSupport": True}, "semanticTokens": {"refreshSupport": False}, "symbol": { "dynamicRegistration": True, "resolveSupport": {"properties": ["location.range"]}, "symbolKind": { "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] }, "tagSupport": {"valueSet": [1]}, }, "workspaceEdit": { "changeAnnotationSupport": {"groupsOnLabel": True}, "documentChanges": True, "failureHandling": "textOnlyTransactional", "normalizesLineEndings": True, "resourceOperations": ["create", "rename", "delete"], }, "workspaceFolders": True, }, }, "clientInfo": {"name": "Visual Studio Code", "version": "1.102.2"}, "initializationOptions": { "backgroundIndexing": True, "backgroundPreparationMode": "enabled", "textDocument/codeLens": {"supportedCommands": {"swift.debug": "swift.debug", "swift.run": "swift.run"}}, "window/didChangeActiveDocument": True, "workspace/getReferenceDocument": True, "workspace/peekDocuments": True, }, "locale": "en", "processId": os.getpid(), "rootPath": repository_absolute_path, "rootUri": root_uri, "workspaceFolders": [ { "uri": root_uri, "name": os.path.basename(repository_absolute_path), } ], } return initialize_params # type: ignore[return-value] def _start_server(self) -> None: """Start sourcekit-lsp 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 sourcekit-lsp 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) capabilities = init_response["capabilities"] log.info(f"SourceKit LSP capabilities: {list(capabilities.keys())}") assert "textDocumentSync" in capabilities, "textDocumentSync capability missing" assert "definitionProvider" in capabilities, "definitionProvider capability missing" self.server.notify.initialized({}) self.completions_available.set() self.server_ready.set() self.server_ready.wait() # Mark initialization timestamp for smarter delay calculation self._initialization_timestamp = time.time() @override def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]: # SourceKit LSP needs initialization + indexing time after startup # before it can provide accurate reference information. This sleep # prevents race conditions where references might not be available yet. # CI environments need extra time for project indexing and cross-file analysis if not self._did_sleep_before_requesting_references: # Calculate minimum delay based on how much time has passed since initialization if self._initialization_timestamp: elapsed = time.time() - self._initialization_timestamp # Increased CI delay for project indexing: 15s CI, 5s local base_delay = 15 if os.getenv("CI") else 5 remaining_delay = max(2, base_delay - elapsed) else: # Fallback if initialization timestamp is missing remaining_delay = 15 if os.getenv("CI") else 5 log.info(f"Sleeping {remaining_delay:.1f}s before requesting references for the first time (CI needs extra indexing time)") time.sleep(remaining_delay) self._did_sleep_before_requesting_references = True # Get references with retry logic for CI stability references = super().request_references(relative_file_path, line, column) # In CI, if no references found, retry once after additional delay if os.getenv("CI") and not references: log.info("No references found in CI - retrying after additional 5s delay") time.sleep(5) references = super().request_references(relative_file_path, line, column) return references

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