Multiple parallel langauge servers

I believe you can setup multiple servers, but I haven’t tried it. I used to switch back and forth between ccls and clangd until finally I’ve settled on clangd since v12.0 is much improved (it lacks snippets and a few gotos, but is fairly stable especially with the incremental_sync feature in nvim that’s being worked on right now). You mention you use ccls for completion, do you find its completion better than clangd’s?

I can answer your #2 question with my config for clangd SwitchSourceHeader.
Basically you just need to call vim.lsp.buf_request(bufnr, method, params, handler) where bufnr is the buffer #, params contains the file uri, method is textDocument/switchSourceHeader, and the handler is a callback function which will handle the response.
The callback function gets called once clangd responds, and it’s given the uri of the header/source file (or an err, if clangd can’t find it). There’s a helper function built-into vim to convert a uri to a filename, which is handy here. Then you can have your callback open the file by using normal vim commands (:new filename), or do anything you want with the filename.

I made a little helper function so you can have it open in a new split, vsplit, or current window.

local function switch_source_header_splitcmd(bufnr, splitcmd)
    bufnr = require'lspconfig'.util.validate_bufnr(bufnr)
    local params = { uri = vim.uri_from_bufnr(bufnr) }
    vim.lsp.buf_request(bufnr, 'textDocument/switchSourceHeader', params, function(err, _, result)
        if err then error(tostring(err)) end
        if not result then print ("Corresponding file can’t be determined") return end
        vim.api.nvim_command(splitcmd..' '..vim.uri_to_fname(result))
    end)
end

require'lspconfig'.clangd.switch_source_header_splitcmd = switch_source_header_splitcmd

require'lspconfig'.clangd.setup {
   -----snip------
    commands = {
    	ClangdSwitchSourceHeader = {
    		function() switch_source_header_splitcmd(0, "edit") end;
    		description = "Open source/header in a new vsplit";
    	},
    	ClangdSwitchSourceHeaderVSplit = {
    		function() switch_source_header_splitcmd(0, "vsplit") end;
    		description = "Open source/header in a new vsplit";
    	},
    	ClangdSwitchSourceHeaderSplit = {
    		function() switch_source_header_splitcmd(0, "split") end;
    		description = "Open source/header in a new split";
    	}
    }
}

Then you can call the commands like ordinary vim commands, e.g.
:ClangdSwitchSourceHeaderVSplit
or
nnoremap <leader>h :ClangdSwitchSourceHeader<CR>

2 Likes