From d1275627a8863734a0927a50a4e74b3c56412ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20H=E1=BB=A9a?= Date: Mon, 6 Apr 2026 17:31:28 +0700 Subject: [PATCH] Support mise, asdf and rvm for Ruby version managers (#1254) Backwards compatible since rbenv is used first if it is found. Then shutil.which is used to check availability of alternative ruby version managers --- src/solidlsp/language_servers/ruby_lsp.py | 61 +++++++++++++++-------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/solidlsp/language_servers/ruby_lsp.py b/src/solidlsp/language_servers/ruby_lsp.py index 4a3d41b8..7bd56256 100644 --- a/src/solidlsp/language_servers/ruby_lsp.py +++ b/src/solidlsp/language_servers/ruby_lsp.py @@ -109,39 +109,57 @@ class RubyLsp(SolidLanguageServer): """ ls_specific_settings = solidlsp_settings.get_ls_specific_settings(Language.RUBY) ruby_lsp_version = ls_specific_settings.get("ruby_lsp_version", RUBY_LSP_VERSION) - # Detect rbenv-managed Ruby environment - # When .ruby-version exists, it indicates the project uses rbenv for version management. - # rbenv automatically reads .ruby-version to determine which Ruby version to use. - # Using "rbenv exec" ensures commands run with the correct Ruby version and its gems. + # Detect Ruby version manager environment + # Using the version manager's exec wrapper ensures commands run with the correct Ruby version + # and its gems. # - # Why rbenv is preferred over system Ruby: - # - Respects project-specific Ruby versions - # - Avoids bundler version mismatches between system and project - # - Ensures consistent environment across developers - # - # Fallback behavior: - # If .ruby-version doesn't exist or rbenv isn't installed, we fall back to system Ruby. - # This may cause issues if: - # - System Ruby version differs from what the project expects - # - System bundler version is incompatible with Gemfile.lock - # - Project gems aren't installed in system Ruby + # Priority order: + # 1. rbenv - .ruby-version + rbenv binary; uses "rbenv exec " + # 2. mise - .ruby-version + mise binary; uses "mise exec ruby -- " (scoped to ruby + # tool only, avoids activating unrelated tools in .tool-versions) + # 3. asdf - .tool-versions + asdf binary; uses "asdf exec " + # 4. RVM - .ruby-version + rvm-exec binary; uses "rvm-exec ruby-X.Y.Z " + # rvm-exec requires the version string as first arg (e.g. "ruby-3.2.0"); + # may live at ~/.rvm/bin/rvm-exec if not on PATH + # 5. system Ruby - fallback, may cause version/gem mismatches ruby_version_file = os.path.join(repository_root_path, ".ruby-version") + tool_versions_file = os.path.join(repository_root_path, ".tool-versions") + rvm_exec_path = shutil.which("rvm-exec") or os.path.join(os.path.expanduser("~"), ".rvm", "bin", "rvm-exec") + use_rbenv = os.path.exists(ruby_version_file) and shutil.which("rbenv") is not None + use_mise = os.path.exists(ruby_version_file) and not use_rbenv and shutil.which("mise") is not None + use_asdf = os.path.exists(tool_versions_file) and not use_rbenv and not use_mise and shutil.which("asdf") is not None + use_rvm = os.path.exists(ruby_version_file) and not use_rbenv and not use_mise and not use_asdf and os.path.exists(rvm_exec_path) if use_rbenv: ruby_cmd = ["rbenv", "exec", "ruby"] bundle_cmd = ["rbenv", "exec", "bundle"] log.info(f"Using rbenv-managed Ruby (found {ruby_version_file})") + elif use_mise: + ruby_cmd = ["mise", "exec", "ruby", "--", "ruby"] + bundle_cmd = ["mise", "exec", "ruby", "--", "bundle"] + log.info(f"Using mise-managed Ruby (found {ruby_version_file})") + elif use_asdf: + ruby_cmd = ["asdf", "exec", "ruby"] + bundle_cmd = ["asdf", "exec", "bundle"] + log.info(f"Using asdf-managed Ruby (found {tool_versions_file})") + elif use_rvm: + with open(ruby_version_file) as _f: + raw_version = _f.read().strip() + rvm_ruby_version = raw_version if raw_version.startswith("ruby-") else f"ruby-{raw_version}" + ruby_cmd = [rvm_exec_path, rvm_ruby_version, "ruby"] + bundle_cmd = [rvm_exec_path, rvm_ruby_version, "bundle"] + log.info(f"Using RVM-managed Ruby (found {ruby_version_file}, version={rvm_ruby_version})") else: ruby_cmd = ["ruby"] bundle_cmd = ["bundle"] - if os.path.exists(ruby_version_file): + if os.path.exists(ruby_version_file) or os.path.exists(tool_versions_file): log.warning( - f"Found {ruby_version_file} but rbenv is not installed. " - "Using system Ruby. Consider installing rbenv for better version management: https://github.com/rbenv/rbenv", + "Found Ruby version file but no supported version manager (rbenv, mise, asdf, rvm) detected. " + "Using system Ruby. Consider installing mise: https://mise.jdx.dev", ) else: - log.info("No .ruby-version file found, using system Ruby") + log.info("No Ruby version file found, using system Ruby") # Check if Ruby is installed try: @@ -166,9 +184,10 @@ class RubyLsp(SolidLanguageServer): except FileNotFoundError as e: raise RuntimeError( "Ruby is not installed or not found in PATH. Please install Ruby using one of these methods:\n" + " - Using mise: mise install ruby && mise use ruby (https://mise.jdx.dev)\n" " - Using rbenv: rbenv install 3.0.0 && rbenv global 3.0.0\n" - " - Using RVM: rvm install 3.0.0 && rvm use 3.0.0 --default\n" - " - Using asdf: asdf install ruby 3.0.0 && asdf global ruby 3.0.0\n" + " - Using asdf: asdf install ruby 3.0.0 && asdf global ruby 3.0.0\n" + " - Using RVM: rvm install 3.0.0 && rvm use 3.0.0 --default\n" " - System package manager (brew install ruby, apt install ruby, etc.)" ) from e