lsp icon indicating copy to clipboard operation
lsp copied to clipboard

CodeAction doesn't work realiably when using multiple servers

Open jclsn opened this issue 3 months ago • 4 comments

I use clangd and cspell-lsp at once. Here is an example configuration for cspell for testing purposes.

let cspell =
			\ #{name: 'cspell',
			\   filetype: [
			\     'c', 
			\     'cpp',
			\     'json',
			\     'gitcommit',
			\     'json',
			\     'markdown',
			\     'python',
			\     'vim',
			\     'yaml',
			\   ],
			\   path: 'cspell-lsp',
			\   args: ['--stdio', 
			\		   '--sortWords',
			\		   '--config', '/home/jan/.vim/cSpell.json',
			\   ]
			\ }

cspell-lsp code actions are not always offered to me. There seems to be a race condition here about which server starts first. If I put cspell-lsp as the first server to load, it works, but then clangd code actions don't work anymore.

jclsn avatar Sep 12 '25 07:09 jclsn

Can you provide some sample code and the expected code actions to reproduce this issue?

yegappan avatar Sep 12 '25 14:09 yegappan

Sure, here is an example C program and an LSP configuration to easily swap the orders of the servers. The cspell-lsp can be installed with npm install -g @vlabo/cspell-lsp. Only the code actions of the first server work. I have to admit that the cspell-lsp also has some issues. Sometimes code actions are only shown when you are at the end of the line (see this issue).

/* ssdasdasdadjakdaslkdasldjaskd */

int main(int argc, char *argv[])
{
	int mybksdjaskdaksdalablvar = 1

	return 0;
}
let lspOpts = #{
	\   aleSupport: v:false,
	\   autoComplete: v:true,
	\   autoHighlight: v:true,
	\   autoHighlightDiags: v:true,
	\   autoPopulateDiags: v:true,
	\   completionMatcher: 'case',
	\   completionMatcherValue: 1,
	\   diagSignErrorText: '❗',
	\   diagSignHintText: '💡',
	\   diagSignInfoText: '💡',
	\   diagSignWarningText: '💡',
	\   diagSignPriority: {
	\       'Error': 100,
	\       'Warning': 99,
	\       'Information': 98,
	\       'Hint': 97
	\   },
	\   echoSignature: v:false,
	\   hideDisabledCodeActions: v:false,
	\   highlightDiagInline: v:true,
	\   hoverInPreview: v:false,
	\   ignoreMissingServer: v:true,
	\   keepFocusInDiags: v:true,
	\   keepFocusInReferences: v:true,
	\   completionTextEdit: v:true,
	\   diagVirtualTextAlign: 'above',
	\   noNewlineInCompletion: v:false,
	\   popupBorder: v:true,
	\	popupBorderSignatureHelp: v:true,
	\   omniComplete: v:null,
	\   outlineOnRight: v:true,
	\   outlineWinSize: 50,
	\   semanticHighlight: v:true,
	\   showDiagInBalloon: v:false,
	\   showDiagInPopup: v:true,
	\   showDiagOnStatusLine: v:false,
	\   showDiagWithSign: v:true,
	\   showDiagWithVirtualText: v:false,
	\   showInlayHints: v:false,
	\   showSignature: v:true,
	\   snippetSupport: v:true,
	\   ultisnipsSupport: v:true,
	\   useBufferCompletion: v:true,
	\   usePopupInCodeAction: v:true,
	\   vsnipSupport: v:false,
	\   useQuickfixForLocations: v:false,
	\   bufferCompletionTimeout: 1,
	\   customCompletionKinds: v:false,
	\   completionKinds: {},
	\   filterCompletionDuplicates: v:true,
	\ }

autocmd User LspSetup call LspOptionsSet(lspOpts)

let clangd =
			\ #{name: 'clangd',
			\  filetype: ['c', 'cpp'],
			\  path: 'clangd',
			\  args: [
			\		'--background-index',
			\		'--clang-tidy',
			\		'--completion-style=detailed'
			\  ]
			\ }

let cspell =
			\ #{name: 'cspell',
			\   filetype: ['c', 'cpp'],
			\   path: 'cspell-lsp',
			\   args: ['--stdio',
			\		   '--sortWords',
			\		   '--config', '/home/jan/.vim/cSpell.json',
			\   ]
			\ }

" Enable language servers
let lspServers = []

" cspell-lsp first -> only cspell code actions work. 
" I you swap the two language servers, only clangd code actions work
call add(lspServers, cspell) 
call add(lspServers, clangd)

autocmd VimEnter * call LspAddServer(lspServers)

nnoremap <leader>ac :LspCodeAction<CR>
nnoremap <silent> <leader>pe :LspDiagPrev<CR>
nnoremap <silent> <leader>ne :LspDiagNext<CR>

jclsn avatar Sep 13 '25 07:09 jclsn

So I just tried another spellchecking language server and it doesn't start up with clangd at all. There definitely seem to still be problems with using two language servers at the same time. I add codebook before clangd, codebook works and clangd doesn't. If I add clangd first, codebooks doesn't work. Probably a race condition somewhere.

Here is the config

let codebook =
			\ #{name: 'codebook',
			\   filetype: [
			\     'c',
			\     'cpp',
			\     'json',
			\     'vim',
			\     'css',
			\     'gitcommit',
			\     'go',
			\     'html',
			\     'js',
			\     'json',
			\     'julia',
			\     'markdown',
			\     'matlab',
			\     'pkl',
			\     'python',
			\     'rust',
			\     'ts',
			\     'vim',
			\     'yaml',
			\   ],
			\   path: 'codebook-lsp',
			\   args: ['serve'
			\   ],
			\   root_markers: [ '.git', 'codebook.toml', '.codebook.toml' ],
			\ }

jclsn avatar Nov 01 '25 11:11 jclsn

I played around with it a little more. The codebook server suffers from the exact same issues as the cspell-lsp, which can't be. This is an issue with the client.

Is it actually possible to register the same capability for multiple servers or can everything (hover, diagnostics, code actions etc.) only be registered once per buffer?

jclsn avatar Nov 02 '25 14:11 jclsn