How to customize LSP actions

Neovim offers many functions to perform LSP actions, like vim.lsp.buf.definition(), but I don’t get how to customize those actions.
For example, is there a way to call vim.lsp.buf.definition(), but opening the definition in a new split?

Generally, this is the way it works:

  1. You, or an automatic trigger (like changing text), induces a request from client to server. In the case of vim.lsp.buf.definition() this triggers a json.rpc request from client to server (manually) here.
  2. Neovim receives a response from the server, which is matched against the list of handlers here
  3. In this case, there is a tiny bit of indirection from the definition handler to the location_handler function, which calls jump_to_location, the function responsible for opening and moving to the new file/buffer.

If you want to override this functionality, you can do something like this in your init.vim (in a lua heredoc) or init.lua. Keep in mind, you have to bring all methods you want to use in scope, so the following won’t run. You’ll need to replace util.jump_to_location with something that handles opening the result in a new split.

  vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, method, result)
  if result == nil or vim.tbl_isempty(result) then
    local _ = log.info() and log.info(method, 'No location found')
    return nil
  end

  -- textDocument/definition can return Location or Location[]
  -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition

  if vim.tbl_islist(result) then
    util.jump_to_location(result[1])

    if #result > 1 then
      util.set_qflist(util.locations_to_items(result))
      api.nvim_command("copen")
      api.nvim_command("wincmd p")
    end
  else
    util.jump_to_location(result)
  end
end
1 Like

I already do that you check my code

function M.goto_definition(opts)
 opts = opts or {}
 local params = vim.lsp.util.make_position_params()
 -- need to process when it have multiple result and multiple language server
 local results_lsp = vim.lsp.buf_request_sync(0, "textDocument/definition", params, opts.timeout or 10000)
    if not results_lsp or vim.tbl_isempty(results_lsp) then
        print("No results from textDocument/definition")
        return
    end
  for _, lsp_data in pairs(results_lsp) do
    if lsp_data ~= nil and lsp_data.result ~= nil and not vim.tbl_isempty(lsp_data.result) then
      for _, value in pairs(lsp_data.result) do
        local range = value.range or value.targetRange
        if range ~= nil then
          local line = range.start.line
          local character = range.start.character
          local file = value.uri or value.targetUri
          -- skip node module
          -- if file ~=nil and not string.match(file,'node_modules') then
          if file ~=nil then
            -- mark current cursor open to jumplist
            vim.api.nvim_command[[execute "normal! m` "]]
            my_open({file , line + 1 , character + 1})
            vim.api.nvim_feedkeys('zz','n',true)
            return
          end
        end
      end
    end
  end
  -- try to call default lsp function
  vim.lsp.buf.definition()
end



1 Like

thanks @mjlbach and @windwp for your responses, knowing better how nvim lsp works I’ll probably adapt @windwp code to have my custom solution.

In any case, it makes sense to me to have some helper functions on nvim or nvim-lsp to customize how to open the definition, type definition, etc. I seems a very common operation, and I guess more people would like to open the definition in a split

Looks like there is already some WIP to make the jump-to-location configurable: