Implementing VS Code-like Ruby LSP Features in Neovim

Published on 2025-01-03

Last updated on 2025-01-13

if you’ve used Ruby LSP in VS Code, You’re probably familiar with its ability to navigate between controller actions, route definition and view files using the Code Lens feature. In this post, we’ll walk through configuring Neovim to achieve the same functionality.

ruby lsp rails controller action to view ruby lsp rails controller action to view

Prerequisites

this guide uses kickstart.nvim as its foundation. However, the concepts and techniques discussed here can be applied to any Neovim configuration. Let’s dive in!

Configuring Ruby LSP

Enabling Code Lens Information

first, we need to enable the display of route information and “Jump to View” indicators arount controller actions. By default, we can trigger this manually using the command :lua vim.lsp.codelens.refresh.

code lens before config code lens after config

Automating Code Lens Updates

Rather than manually refreshing Code Lens, we can automate this process. taking inspiration from kickstart.nvim’s existing code, we’ll add logic to check for LSP client availability and Code Lens support. when both conditions are met, we’ll automatically trigger the refresh function for the current buffer.


+    if client and client.server_capabilities.codeLensProvider then
+      vim.api.nvim_create_autocmd({ 'BufEnter', 'CursorHold', 'InsertLeave' }, {
+        buffer = event.buf,
+        callback = vim.lsp.codelens.refresh,
+      })
+    end

    -- The following code creates a keymap to toggle inlay hints in your
    -- code, if the language server you are using supports them
    --
    -- This may be unwanted, since they displace some of your code
    if client and client.supports_method(vim.lsp.protocol.Methods.textDocument_inlayHint) then
        map('<leader>th', function()
          vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled { bufnr = event.buf })
        end, '[T]oggle Inlay [H]ints')
    end

Adding Ruby LSP Configuration

local servers = {
+ ruby_lsp = {
+   on_attach = function(client, bufnr)
+     vim.keymap.set('n', '<leader>cl', vim.lsp.codelens.run, { noremap = true, silent = true })
+
+     client.commands = client.commands or {}
+
+     client.commands['rubyLsp.openFile'] = function(command)
+       local file_path = command.arguments[1][1]
+
+       local path, line = string.match(file_path, '(.+)#L(%d+)')
+       path = path or file_path -- if no line number, use the whole path
+
+       path = string.gsub(path, 'file://', '')
+       vim.cmd('edit ' .. path)
+
+       if line then
+         vim.cmd(line)
+       end
+     end
+   end,
+ },
  lua_ls = {
    ...
  },
}

require('mason-lspconfig').setup {
  handlers = {
    function(server_name)
      local server = servers[server_name] or {}
      server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {})

+     server.on_attach = server.on_attach or function(client, bufnr) end
      require('lspconfig')[server_name].setup(server)
    end,
  },
}

Understanding the configuration

Let’s break down the key components of our configuration:

  1. Keybinding Setup: we set keymap <leader>cl to trigger vim.lsp.codelens.run. which displays the Code Lens popup for navigationg to route information or view files.
vim.keymap.set('n', '<leader>cl', vim.lsp.codelens.run, { noremap = true, silent = true })
code lens run
  1. Custom Command Handler: we implement the rubyLsp.openFile command handler to provess navigation requests from the LSP client. this implementation is based on the Ruby LSP source code, which provides file paths in the format file://path/to/file.rb#L12.
client.commands = client.commands or {}

client.commands['rubyLsp.openFile'] = function(command)
  local file_path = command.arguments[1][1]

  local path, line = string.match(file_path, '(.+)#L(%d+)')
  path = path or file_path -- if no line number, use the whole path

  path = string.gsub(path, 'file://', '')
  vim.cmd('edit ' .. path)

  if line then
    vim.cmd(line)
  end
end
  1. LSP Server Intgration: Finally, we ensure our LSP server handles these commands by setting up the appropriate on_attach function
server.on_attach = server.on_attach or function(client, bufnr) end

Conclusion

With this configuration, we’ve successfully implemented VS Code-style navigation features in Neovim using Ruby LSP. This enhancement makes navigating Rails projects in Neovim more efficient and intuitive. The same approach can be used to implement additional LSP commands as needed.