Switch to .h/cpp via lsp

Is there a way to swap between header and cpp using language server?

I’m on a project where the .h and cpp are not in the same location and need something more intelligent than file extension substitution.

Did you try the command listed in CONFIG.md for clangd?

It’s ClangdSwitchSourceHeader

searching around i saw this

    require'lspconfig'.clangd.setup {
        commands = {
            ClangdSwitchSourceHeader = {
                    local bufnr = require'lspconfig'.util.validate_bufnr(0)
                    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("edit "..vim.uri_to_fname(result))
                        vim.api.nvim_command("bdelete "..tostring(bufnr))

only issue is the final bdelete i added to cleanup the old file i swapped from doesn’t always work. When it does work it will complain that it didnt delete anything.

is the inconsistent behavior related to async in some way?
i can repro the inconsistent behavior using the exact original with the extra bdelete command

issue was i had two calls to the lsp setup.

Are you using clangd? or ccls?

If clangd, then as @mjlbach suggests, what’s wrong with this?


This command is already built-in if you’re using nvim-lspconfig and clangd.

It won’t delete the original buffer, I suppose the idea is that you might switch to the header/source and then switch back. And maybe back and forth a few times. What’s the use case for deleting the switched-from buffer? Just keeping less buffers in the buffer list?

I did just try adding the “bdelete” line to my config, and I don’t get an error when switching+deleting, but I only tried it with a few files. There are times when the unaltered command doesn’t work for me, it’s rare but happens. Usually I think it’s related to recently-added files, like if I switch git branches while files are open. Also there are times when the correct header is ambiguous (i.e. builds for multiple architectures, each with their own header with the same name in a different dir).

ah it was the first thing i tried, it didnt auto complete so i went searching after that.
The reason it didn’t auto complete at the time was that i wasn’t in a cpp file. doh.

Just keeping less buffers in the buffer list?


I don’t get an error when switching+deleting

My problem was I had two nvim_lsp.clangd.setup calls.

where is ClangdSwitchSourceHeader?

lspconfig automatically maps it when clangd attaches to a cpp buffer, see CONFIG.md

I can’t get it to switch to .h/cpp in a split without changing this line in the nvim-lspconfig core. This suggests it’s basic vimscript, but it is not for me :cry:… And I would like to do as much as possible in lua. Any help is greatly appreciated.

You’re right, it is that line that’s responsible for opening the new file. To do it in a new vertical split just change the word ‘edit’ to ‘vsplit’

Here’s an example of how to change it to be a command where you can specify if you want it opened in a new split, new vertical split, or in the current window.

1 Like

I think this needs updating for neovim 0.6 since it broke for me… Will try to fix it myself, but it might take a while…
UPDATE: I manged to fix it by removing nvim_lspconfig.util.compat_handler call and instead using just the function provided to it in its place.

Ah, yes I think you found the right solution. From this PR: chore: remove compat shim for pre-0.5.1 handlers by clason · Pull Request #1530 · neovim/nvim-lspconfig · GitHub
from about 3 days ago it looks like the util.compat_handler wrapper needs to be removed.

Also, from another PR about 7 days ago: Use client.request for unique calls (clangd & texlab) by ranjithshegde · Pull Request #1503 · neovim/nvim-lspconfig · GitHub
util.get_active_client_by_name(bufnr, 'clangd') can be used send the textDocument/switchSourceHeader request to just the clangd server, which I suppose might play more nicely if you’re using clangd and ccls at the same time.

I’ve updated my local config to this:

	local function switch_source_header_splitcmd(bufnr, splitcmd)
		bufnr = require'lspconfig'.util.validate_bufnr(bufnr)
		local clangd_client = require'lspconfig'.util.get_active_client_by_name(bufnr, 'clangd')
		local params = {uri = vim.uri_from_bufnr(bufnr)}
		if clangd_client then
			clangd_client.request("textDocument/switchSourceHeader", params, function(err, result)
				if err then
				if not result then
					print("Corresponding file can’t be determined")
				vim.api.nvim_command(splitcmd .. " " .. vim.uri_to_fname(result))
			end, bufnr)
			print 'textDocument/switchSourceHeader is not supported by the clangd server active on the current buffer'

If you have some time, would you check to see if this works for you also? If so, we should update the wiki post.

Why do you need to copy this into your config? Lspconfig provides this out of the box.

It’s based on the function provided by lspconfig, but the important difference is that with the custom function you can specify opening the alt file in a vsplit or split (or run any other command on the alt file name), whereas the lspconfig function has ‘edit ‘ hard-coded.
Hence the custom function takes an additional parameter (splitcmd), which is the command to be run on the alt file name.