Prevent detaching LSP Clients

I have several functions such as this one:

    function M.import_sort(async, cb)
      local view = fn.winsaveview()
      local path = fn.fnameescape(fn.expand("%:p"))
      local executable_path = find_executable("import-sort")
      local stdout = vim.loop.new_pipe(false)
      local stderr = vim.loop.new_pipe(false)


      if fn.executable(executable_path) then
        if true == async then
          handle = vim.loop.spawn(executable_path, {
            args = {path, "--write"},
            stdio = {stdout,stderr}
          },
          vim.schedule_wrap(function()
            stdout:read_stop()
            stderr:read_stop()
            stdout:close()
            stderr:close()
            handle:close()
            vim.api.nvim_command[["checktime"]]
            if cb ~= nil then
              fn.winrestview(view)
              cb()
            end
          end
          )
          )
          vim.loop.read_start(stdout, onread)
          vim.loop.read_start(stderr, onread)
        else
          fn.system(executable_path .. " " .. path .. " " .. "--write")
          vim.api.nvim_command[["checktime"]]
        end
      else
        error("Cannot find import-sort executable")
      end
    end

which take the current buffer and run it through some sort of external tool for linting, sorting dependencies, or formatting. Even when I call winsaveview() and winrestview(), the currently attached LSP client (tsserver in this case) will disconnect every time this function runs. Which in my case is after the BufWritePost autocmd. Is there a better way of preventing frequent disconnects from languages servers?

It’s odd to me that they’re even being detached in the first place. Should this be considered a bug in Neovim?

I’m going to guess it’s winrestview. You can add a hook to get the list of client IDs attached to the buffers within the window you are saving, and then manually attach the language server client back to these buffers at the end of your function. I assume winrestview does something internally (changing buffer ids or whatnot) that causes the client not to be attached to those buffers.

Changing buffer ids should be visible in the output from :ls right?

I found the C implementations of those 2 particular functions and I don’t see anything that would obviously delete and re-create a buffer: https://github.com/neovim/neovim/blob/a2b6e5ed4ecdd2733ee71656ee0df6f2ce4aaf6c/src/nvim/eval/funcs.c#L11316-L11384.

Can anyone replicate the behavior?

I actually can’t replicate this behavior. I took the following steps:

  1. Open a 150-line Python file in the Neovim TUI, too big to fit in one terminal screen.
  2. Write :LspInfo to confirm that jedi_language_server is running and attached to the current buffer.
  3. Write :ls to confirm that I have exactly one buffer, numbered 1.
  4. Move my cursor to somewhere in the middle of the file.
  5. Write :let d = winsaveview().
  6. Move my cursor somewhere else in the file.
  7. Write :call winrestview(d).
  8. Look at :LspInfo and :ls output to confirm that there is still exactly 1 instance of Jedi Language Server and it is still attached to the current and only buffer, which is numbered 1.

Given that there exists at least one language server (when configured with lspconfig) such that this procedure works as expected, I would sooner start to look for bugs in either the Lua LSP code or in tsserver itself.

I ended up removing winsaveview() and using function() vim.lsp.buf_attach_client(0,1)end as a callback. Seems to have fixed the issue. Though I do want to try without automatically reattaching the client and see if it really was winsaveview the whole time.

I don’t think removing winsaveview() did anything other than removed the behavior where my cursor would jump back to where it was after the sorting program was executed. Without calling vim.lsp.buf_attach_client(0,1) as a callback, the client disconnects and never reconnects.