mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 22:16:23 +00:00
Add Bundler support and timeout configuration for Ruby projects (#419)
* Add Bundler support for Ruby projects This change enhances the Solargraph language server implementation to properly support Ruby projects using Bundler with vendor/bundle configurations. Key improvements: - Detects Bundler projects by checking for Gemfile existence - Supports both 'bin/bundle' and 'bundle' commands - Checks Gemfile.lock for solargraph availability - Uses 'bundle exec solargraph' when appropriate - Provides clear error messages for missing solargraph in Bundler projects - Maintains backward compatibility with non-Bundler projects Fixes permission issues encountered with rbenv/system-managed Ruby installations when trying to install gems globally. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add timeout configuration for Solargraph language server This change adds proper timeout handling to the Solargraph Ruby language server to address initialization timeout issues, especially in Bundler environments. Key improvements: - Set 120-second request timeout for LSP requests and initialization - Add 60-second server readiness timeout with graceful fallback - Enhanced logging for timeout scenarios - Better handling of slow initialization in large projects or Bundler environments This resolves connection timeout issues where Solargraph takes longer than the default 30-second MCP timeout to fully initialize and become ready. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
f2d81812c7
commit
2b4d68bea9
1 file changed
+86
-22
@@ -48,6 +48,9 @@ class Solargraph(SolidLanguageServer):
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
# Set timeout for Solargraph requests - Bundler environments may need more time
|
||||
self.set_request_timeout(120.0) # 120 seconds for initialization and requests
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["vendor"]
|
||||
@@ -67,35 +70,84 @@ class Solargraph(SolidLanguageServer):
|
||||
except FileNotFoundError as e:
|
||||
raise RuntimeError("Ruby is not installed. Please install Ruby before continuing.") from e
|
||||
|
||||
# Check if solargraph is installed
|
||||
# Check for Bundler project (Gemfile exists)
|
||||
gemfile_path = os.path.join(repository_root_path, "Gemfile")
|
||||
gemfile_lock_path = os.path.join(repository_root_path, "Gemfile.lock")
|
||||
is_bundler_project = os.path.exists(gemfile_path)
|
||||
|
||||
if is_bundler_project:
|
||||
logger.log("Detected Bundler project (Gemfile found)", logging.INFO)
|
||||
|
||||
# Check if bundle command is available
|
||||
bundle_path = shutil.which("bundle")
|
||||
if not bundle_path:
|
||||
# Try common bundle executables
|
||||
for bundle_cmd in ["bin/bundle", "bundle"]:
|
||||
bundle_full_path = (
|
||||
os.path.join(repository_root_path, bundle_cmd) if bundle_cmd.startswith("bin/") else shutil.which(bundle_cmd)
|
||||
)
|
||||
if bundle_full_path and os.path.exists(bundle_full_path):
|
||||
bundle_path = bundle_full_path if bundle_cmd.startswith("bin/") else bundle_cmd
|
||||
break
|
||||
|
||||
if not bundle_path:
|
||||
raise RuntimeError("Bundler project detected but 'bundle' command not found. Please install Bundler.")
|
||||
|
||||
# Check if solargraph is in Gemfile.lock
|
||||
solargraph_in_bundle = False
|
||||
if os.path.exists(gemfile_lock_path):
|
||||
try:
|
||||
with open(gemfile_lock_path) as f:
|
||||
content = f.read()
|
||||
solargraph_in_bundle = "solargraph" in content.lower()
|
||||
except Exception as e:
|
||||
logger.log(f"Warning: Could not read Gemfile.lock: {e}", logging.WARNING)
|
||||
|
||||
if solargraph_in_bundle:
|
||||
logger.log("Found solargraph in Gemfile.lock", logging.INFO)
|
||||
return f"{bundle_path} exec solargraph"
|
||||
else:
|
||||
logger.log(
|
||||
"solargraph not found in Gemfile.lock. Please add 'gem \"solargraph\"' to your Gemfile and run 'bundle install'",
|
||||
logging.WARNING,
|
||||
)
|
||||
# Fall through to global installation check
|
||||
|
||||
# Check if solargraph is installed globally
|
||||
# First, try to find solargraph in PATH (includes asdf shims)
|
||||
solargraph_path = shutil.which("solargraph")
|
||||
if solargraph_path:
|
||||
logger.log(f"Found solargraph at: {solargraph_path}", logging.INFO)
|
||||
return solargraph_path
|
||||
|
||||
# Fallback to gem exec
|
||||
runtime_dependencies = [
|
||||
{
|
||||
"url": "https://rubygems.org/downloads/solargraph-0.51.1.gem",
|
||||
"installCommand": "gem install solargraph -v 0.51.1",
|
||||
"binaryName": "solargraph",
|
||||
"archiveType": "gem",
|
||||
}
|
||||
]
|
||||
# Fallback to gem exec (for non-Bundler projects or when global solargraph not found)
|
||||
if not is_bundler_project:
|
||||
runtime_dependencies = [
|
||||
{
|
||||
"url": "https://rubygems.org/downloads/solargraph-0.51.1.gem",
|
||||
"installCommand": "gem install solargraph -v 0.51.1",
|
||||
"binaryName": "solargraph",
|
||||
"archiveType": "gem",
|
||||
}
|
||||
]
|
||||
|
||||
dependency = runtime_dependencies[0]
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path
|
||||
dependency = runtime_dependencies[0]
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path
|
||||
)
|
||||
if result.stdout.strip() == "false":
|
||||
logger.log("Installing Solargraph...", logging.INFO)
|
||||
subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path)
|
||||
|
||||
return "gem exec solargraph"
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}") from e
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"This appears to be a Bundler project, but solargraph is not available. "
|
||||
"Please add 'gem \"solargraph\"' to your Gemfile and run 'bundle install'."
|
||||
)
|
||||
if result.stdout.strip() == "false":
|
||||
logger.log("Installing Solargraph...", logging.INFO)
|
||||
subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path)
|
||||
|
||||
return "gem exec solargraph"
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}") from e
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
@@ -176,4 +228,16 @@ class Solargraph(SolidLanguageServer):
|
||||
self.completions_available.set()
|
||||
|
||||
self.server_ready.set()
|
||||
self.server_ready.wait()
|
||||
|
||||
# Wait for server to be ready with timeout, especially important for Bundler environments
|
||||
server_ready_timeout = 60.0
|
||||
self.logger.log(f"Waiting up to {server_ready_timeout} seconds for Solargraph to become ready...", logging.INFO)
|
||||
|
||||
if self.server_ready.wait(timeout=server_ready_timeout):
|
||||
self.logger.log("Solargraph is ready and available for requests", logging.INFO)
|
||||
else:
|
||||
self.logger.log(
|
||||
f"Timeout waiting for Solargraph to become ready within {server_ready_timeout} seconds, proceeding anyway. "
|
||||
"This may indicate slow initialization in Bundler environment or large project indexing.",
|
||||
logging.WARNING,
|
||||
)
|
||||
Reference in new issue
Block a user