From 082afe9e848035347ccd1ac99288f0184c8f894f Mon Sep 17 00:00:00 2001 From: Asher Haig Date: Sat, 14 Mar 2026 07:14:23 -0400 Subject: [PATCH 1/2] Fix empty find_referencing_symbols by adding --scratch-path to sourcekit-lsp sourcekit-lsp was launched with no arguments, giving it no location to store its background index. Without --scratch-path, textDocument/references always returns empty because there is no index store for cross-file symbol resolution. - Pass --scratch-path /.build/sourcekit-lsp when launching - Increase local indexing delay from 5s to 10s (real projects need more time) - Add retry logic for local runs when references are empty, not just CI Fixes root cause of issue #876. Co-Authored-By: Claude Opus 4.6 --- .../language_servers/sourcekit_lsp.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/solidlsp/language_servers/sourcekit_lsp.py b/src/solidlsp/language_servers/sourcekit_lsp.py index ba55e35bf..33a33cf85 100644 --- a/src/solidlsp/language_servers/sourcekit_lsp.py +++ b/src/solidlsp/language_servers/sourcekit_lsp.py @@ -49,8 +49,15 @@ def __init__(self, config: LanguageServerConfig, repository_root_path: str, soli sourcekit_version = self._get_sourcekit_lsp_version() log.info(f"Starting sourcekit lsp with version: {sourcekit_version}") + # sourcekit-lsp needs --scratch-path for background indexing and cross-file references. + # Without it, textDocument/references returns empty because there's no index store. + scratch_path = os.path.join(repository_root_path, ".build", "sourcekit-lsp") + os.makedirs(scratch_path, exist_ok=True) + cmd = ["sourcekit-lsp", "--scratch-path", scratch_path] + log.info(f"sourcekit-lsp scratch path: {scratch_path}") + super().__init__( - config, repository_root_path, ProcessLaunchInfo(cmd="sourcekit-lsp", cwd=repository_root_path), "swift", solidlsp_settings + config, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "swift", solidlsp_settings ) self.request_id = 0 self._did_sleep_before_requesting_references = False @@ -344,24 +351,26 @@ def request_references(self, relative_file_path: str, line: int, column: int) -> # 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 + # Increased delay for project indexing: 15s CI, 10s local + # 5s was insufficient for real projects — sourcekit-lsp needs time to index + base_delay = 15 if os.getenv("CI") else 10 remaining_delay = max(2, base_delay - elapsed) else: # Fallback if initialization timestamp is missing - remaining_delay = 15 if os.getenv("CI") else 5 + remaining_delay = 15 if os.getenv("CI") else 10 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 + # Get references with retry logic — indexing may not be complete on first request 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) + # If no references found, retry once after additional delay (indexing may still be in progress) + if not references: + retry_delay = 5 if os.getenv("CI") else 3 + log.info(f"No references found - retrying after additional {retry_delay}s delay (index may still be building)") + time.sleep(retry_delay) references = super().request_references(relative_file_path, line, column) return references From ea2114f79022681ffe74a976063ddf217d0ba82a Mon Sep 17 00:00:00 2001 From: Asher Haig Date: Sat, 14 Mar 2026 07:31:28 -0400 Subject: [PATCH 2/2] Resolve sourcekit-lsp via xcrun with DEVELOPER_DIR for Xcode On macOS, bare sourcekit-lsp resolves to Command Line Tools version which has limited indexing capabilities. xcrun without DEVELOPER_DIR also resolves to CLT. Setting DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer gives the full Xcode sourcekit-lsp with proper background indexing support. Co-Authored-By: Claude Opus 4.6 --- .../language_servers/sourcekit_lsp.py | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/solidlsp/language_servers/sourcekit_lsp.py b/src/solidlsp/language_servers/sourcekit_lsp.py index 33a33cf85..bc9a7ff1e 100644 --- a/src/solidlsp/language_servers/sourcekit_lsp.py +++ b/src/solidlsp/language_servers/sourcekit_lsp.py @@ -30,6 +30,36 @@ def is_ignored_dirname(self, dirname: str) -> bool: # - dist/build: common output directories return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"] + @staticmethod + def _resolve_sourcekit_lsp_path() -> str: + """Resolve the path to sourcekit-lsp, preferring Xcode's version over Command Line Tools. + + On macOS, bare `sourcekit-lsp` may resolve to the Command Line Tools version which + has limited capabilities. Using `xcrun --find sourcekit-lsp` resolves to Xcode's + full-featured version when Xcode is installed. + """ + # Try xcrun with DEVELOPER_DIR pointing to Xcode (not Command Line Tools) + xcode_path = "/Applications/Xcode.app/Contents/Developer" + for env_override in [{"DEVELOPER_DIR": xcode_path}, {}]: + try: + run_env = {**os.environ, **env_override} if env_override else None + result = subprocess.run( + ["xcrun", "--find", "sourcekit-lsp"], + capture_output=True, text=True, check=False, timeout=10, + env=run_env, + ) + if result.returncode == 0: + resolved = result.stdout.strip() + if resolved and os.path.isfile(resolved): + log.info(f"Resolved sourcekit-lsp via xcrun: {resolved}") + return resolved + except (FileNotFoundError, subprocess.TimeoutExpired): + pass + + # Fall back to bare command (Linux, or macOS without Xcode) + log.info("xcrun not available or failed, falling back to bare sourcekit-lsp") + return "sourcekit-lsp" + @staticmethod def _get_sourcekit_lsp_version() -> str: """Get the installed sourcekit-lsp version or raise error if sourcekit was not found.""" @@ -49,12 +79,15 @@ def __init__(self, config: LanguageServerConfig, repository_root_path: str, soli sourcekit_version = self._get_sourcekit_lsp_version() log.info(f"Starting sourcekit lsp with version: {sourcekit_version}") + # Resolve sourcekit-lsp path — prefer Xcode's version over Command Line Tools + sourcekit_path = self._resolve_sourcekit_lsp_path() + # sourcekit-lsp needs --scratch-path for background indexing and cross-file references. # Without it, textDocument/references returns empty because there's no index store. scratch_path = os.path.join(repository_root_path, ".build", "sourcekit-lsp") os.makedirs(scratch_path, exist_ok=True) - cmd = ["sourcekit-lsp", "--scratch-path", scratch_path] - log.info(f"sourcekit-lsp scratch path: {scratch_path}") + cmd = [sourcekit_path, "--scratch-path", scratch_path] + log.info(f"sourcekit-lsp path: {sourcekit_path}, scratch path: {scratch_path}") super().__init__( config, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "swift", solidlsp_settings