Automatically choose one language server to format code when using multiple language servers

See Automatically choose one language server to formatting when using multiple language servers · Issue #14952 · neovim/neovim · GitHub for past discussion. Anyone have an idea for how to do this?

2 Likes

Right your own formatting function that calls

local client = function_that_gets_the_client_I_want_filtering_by_name
client.request("textDocument/formatting", {} , nil, vim.api.nvim_get_current_buf())

and put that in on_attach bound to a key.

You can iterate over clients attached to the buffer with

for client in vim.lsp.get_active_clients() do
   print(client.name)  
end

Using this:

nvim_lsp.pyls.setup {
  on_attach = function(client, bufnr) 
    if <condition> then
      -- print(client.name)  -- I see "pyls" printed here
      client.request("textDocument/formatting", {} , nil, vim.api.nvim_get_current_buf())
      -- client.resolved_capabilities.document_formatting = false
    end
    on_attach(client, bufnr)
  end,
  settings = {
    pyls = {
      plugins = {
        -- This requires: pip install yapf
        yapf = { enabled = true },
        autopep8 = { enabled = false },
        pylint = { enabled = true },
        pycodestyle = { enabled = false },
      }
    }
  }
}

Results in:

RPC[Error] code_name = InvalidParams, message = "TypeError: 'NoneType' object is not subscriptable" data = {
  traceback = { "  File \"/usr/local/google/home/kovas/.local/lib/python3.9/site-packages/pyls_jsonrpc/endpoint.py\"
, line 113, in consume\n    self._handle_request(message['id'], message['method'], message.get('params'))\n", '  Fil
e "/usr/local/google/home/kovas/.local/lib/python3.9/site-packages/pyls_jsonrpc/endpoint.py", line 182, in _handle_r
equest\n    handler_result = handler(params)\n', '  File "/usr/local/google/home/kovas/.local/lib/python3.9/site-pac
kages/pyls_jsonrpc/dispatchers.py", line 23, in handler\n    return method(**(params or {}))\n', "  File \"/usr/loca
l/google/home/kovas/.local/lib/python3.9/site-packages/pyls/python_ls.py\", line 338, in m_text_document__formatting
\n    return self.format_document(textDocument['uri'])\n" }

AND I still get the option to choose between language servers when trying to format :frowning:.

Perhaps I misunderstood?

Any ideas what I might be doing wrong here?

Here is my formatting function. I rewrite the select_client function and make it choose the language server I want. The code of formatting_sync is from the builitin lsp.

local function select_client(method)
  local clients = vim.tbl_values(vim.lsp.buf_get_clients())
  clients =
    vim.tbl_filter(
    function(client)
      return client.supports_method(method)
    end,
    clients
  )

  for i = 1, #clients do
    if  clients[i].name == "efm" then
      return clients[i]
    end
  end

  return nil
end

function formatting_sync(options, timeout_ms)
  local client = select_client("textDocument/formatting")
  if client == nil then
    return
  end

  local params = util.make_formatting_params(options)
  local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
  if result and result.result then
    util.apply_text_edits(result.result)
  elseif err then
    vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN)
  end
end

Hmmm, I added that code to my init.lua with no change in behavior. Do you then reference that formatting_sync function later? I feel like my understanding of how this system works isn’t quite complete yet…

Yes, you can call it later in somewhere. Just replace the vim.lsp.buf.formatting_sync function.

For example:

vim.cmd([[
augroup formatOnSave
  autocmd!
  autocmd BufWritePost * lua formatting_sync()
augroup end
]])

Ok great I got it working! It looks like the functions you are redefining are at neovim/buf.lua at master · neovim/neovim · GitHub (for future reference). My code ended up looking like this:

function formatting(options)
  local client = select_client("textDocument/formatting")
  if client == nil then return end

  local params = util.make_formatting_params(options)
  return client.request("textDocument/formatting", params, vim.api.nvim_get_current_buf())
end
vim.lsp.buf.formatting = formatting

function range_formatting(options, start_pos, end_pos)
  local client = select_client("textDocument/rangeFormatting")
  if client == nil then return end

  local params = util.make_given_range_params(start_pos, end_pos)
  params.options = util.make_formatting_params(options).options
  return client.request("textDocument/rangeFormatting", params)
end
vim.lsp.buf.range_formatting = range_formatting

as I wasn’t using the sync versions. I also redefined the vim.lsp.buf.formatting functions as I am binding keys using the on_attach function suggested at GitHub - neovim/nvim-lspconfig: Quickstart configurations for the Nvim LSP client, and this was the easiest way to make that work.

1 Like