Some Ruby projects that I work on have Rubocop with a bunch of custom or agreed upon rules and some are just a collection of Ruby files. In both cases, I still want to have LSP functionality in Neovim. So here is how I switch from Rubocop’s LSP to Shopify’s Ruby LSP dynamically.
First thing’s first. I use rbenv to sandbox Ruby versions and rbenv-gemset files to sandbox project dependencies. If you use other toolsets to manage this then you’ll want to change things accordingly. I typically setup a base global Ruby version to the latest release and then install Shopify’s Ruby LSP for that version of Ruby:
rbenv install 3.4.1
rbnev global 3.4.1
gem install ruby-lsp
🚨 WARNING 🚨 Do not install LSPs with mason-lsp-config as it will not sandbox Ruby gems correctly.
Now that we have that out of the way, we’ll need to install some NeoVim plugins; specifically nvim-lspconfig and cmp-nvim-lsp. I have the latter defined in file for completions.lua
and the former in a plugin file called lsp-config.lua
. For cmp-nvim-lsp
, I just have it defined as a plugin for Lazy to load. For my lsp-config.lua
file I have the following:
{
'neovim/nvim-lspconfig',
config = function()
local capabilities = require('cmp_nvim_lsp').default_capabilities()
local lspconfig = require('lspconfig')
local util = require('lspconfig.util')
local function choose_ruby_lsp()
local project_root = util.root_pattern("Gemfile", ".rubocop.yml", ".ruby-version")(vim.fn.getcwd())
if project_root and vim.fn.filereadable(project_root .. "/.rubocop.yml") == 1 then
return "rubocop"
else
return "ruby-lsp"
end
end
local function find_rbenv_executable(executable)
local path = vim.fn.system("rbenv which " .. executable):gsub("\n", "")
if vim.fn.filereadable(path) == 1 then
return path
else
print("Warning: " .. executable .. " not found via rbenv")
return nil
end
end
local selected_lsp = choose_ruby_lsp()
if selected_lsp == "rubocop" then
local rubocop_path = find_rbenv_executable("rubocop")
if rubocop_path then
lspconfig.rubocop.setup({
cmd = { "bundle", "exec", rubocop_path, "--lsp" },
filetypes = { "ruby" },
flags = { debounce_text_changes = 100 },
root_dir = util.root_pattern(".rubocop.yml", "Gemfile"),
capabilities = capabilities,
})
end
else
local ruby_lsp_path = find_rbenv_executable("ruby-lsp")
if ruby_lsp_path then
lspconfig.ruby_lsp.setup({
cmd = { ruby_lsp_path },
filetypes = { "ruby" },
flags = { debounce_text_changes = 100 },
root_dir = util.root_pattern("Gemfile", ".ruby-version"),
capabilities = capabilities
})
end
end
lspconfig.lua_ls.setup({
capabilities = capabilities
})
end
},
The first thing that we do is check to see if there is a .rubocop.yml
file in our project. If there is we set the local variable selected_lsp
to rubocop
. If not then we set it to ruby-lsp
. If the selected_lsp
is Rubocop then the command that we run is bundle exec <rubocop_path> --lsp
. If it’s not Rubocop then we just get the path to the ruby-lsp
binary.
That’s pretty much it. I use Lua to inspect the filesytem and change things accordingly.