A Language Server Protocol (LSP) client for Vim, written in Vim9 script. Nothing else to install: Vim frames the protocol messages and matches replies to requests itself, so what is left to the plugin is the conversation and what to do with the answers.
75 of the 95 requests and notifications in the LSP 3.18 meta model are answered, and each of the rest is listed with the reason it is not.
- Completion through
'omnifunc', with the documentation popup beside the menu and snippets whose stops can be stepped through. - Diagnostics as signs, text highlights and a message on the cursor line, whether the server sends them or waits to be asked.
- Reading hover, signature help while a call is being typed, and the symbol under the cursor marked everywhere it is used.
- Jumping to a definition, declaration, type or implementation, with lists
of references, of the symbols in a file or a workspace, of who calls a
function and what it calls, and of what a type is derived from.
CTRL-]and the other tag commands can reach the server through'tagfunc'. - Editing rename across files, formatting a buffer or a range, and code actions from a menu, including the ones a server carries out itself.
- Display inlay hints, code lenses, document links, semantic tokens and folds worked out by the server, each off until asked for.
The buffer is kept in step with the server as it is typed in, the server hears
what it asked to hear about, and what it reports about itself is under
:LspStatus and :LspLog. The details are in :help lsp.txt.
- Vim 9.2.1004 or later, with the
+joband+channelfeatures
(9.2.1160 or later when the server is vim9ls) - A language server for the language you work in, installed separately
What those patches are for
- 9.2.1160: vim9ls needs it for
getinfo() - 9.2.1004:
complete_info()reports in"auto"whether'autocomplete'or a typed key called the'omnifunc'function - 9.2.0997:
ch_sendexpr()takes a String"id", which answers a request the server named with a string - 9.2.0970:
listener_add()takes a"text"option, which gives the callback the text of each change
With a plugin manager, vim-plug for instance:
Plug 'h-east/lsp.vim'Describe the servers to use in g:lsp_server_list. Example:
g:lsp_server_list = [{
filetypes: ['c', 'cpp'], name: 'clangd',
cmd: ['clangd', '--background-index', '--clang-tidy', '--header-insertion=never'],
rootPatterns: ['compile_commands.json', '.git'],
}, {
filetypes: ['vim'], name: 'vim9ls', cmd: function('vim9ls#Command'),
}, {
filetypes: ['python'], name: 'pylsp',
cmd: ['pylsp'], rootPatterns: ['pyproject.toml', '.git'],
}, {
filetypes: ['go'], name: 'gopls',
cmd: ['gopls'], rootPatterns: ['go.work', 'go.mod', '.git'],
}]For legacy Vim script
let g:lsp_server_list = [
\ #{filetypes: ['c', 'cpp'], name: 'clangd',
\ cmd: ['clangd', '--background-index', '--clang-tidy', '--header-insertion=never'],
\ rootPatterns: ['compile_commands.json', '.git']},
\ #{filetypes: ['vim'], name: 'vim9ls', cmd: function('vim9ls#Command')},
\ #{filetypes: ['python'], name: 'pylsp',
\ cmd: ['pylsp'], rootPatterns: ['pyproject.toml', '.git']},
\ #{filetypes: ['go'], name: 'gopls',
\ cmd: ['gopls'], rootPatterns: ['go.work', 'go.mod', '.git']},
\ ]Every key an entry takes is under :help g:lsp_server_list.
More than one entry may name the same filetype. All of them are started and
each is told about the buffer; what is asked for goes to the first entry that
offers it, while what every server reports is shown together. A use key
limits what a server is used for where that is not what you want. See
:help lsp-more-than-one-server.
What the client itself does goes in g:lsp_client_config, one entry per
setting. Example:
g:lsp_client_config = {
highlight_delay: 150,
inlay_hint: true,
}For legacy Vim script
let g:lsp_client_config = #{
\ highlight_delay: 150,
\ inlay_hint: v:true,
\ }Every key that can go in there, and what it is when it is left out, is
listed under :help lsp-configuration, including how each popup is drawn.
Completion goes through 'omnifunc', which Vim uses only where it is told
to, so a few options decide whether much of this shows up at all. Example:
def LspBuffer()
# 'autocomplete' offers what 'complete' names, and "o" names 'omnifunc',
# which the plugin has set.
setlocal complete^=o
setlocal autocomplete # See also 'autocompletedelay'
setlocal completeopt+=popup
setlocal signcolumn=yes
# CTRL-] and the other tag commands ask the server.
setlocal tagfunc=lsp#TagFunc
# The Vim filetype points 'keywordprg' at ":help", which offers more
# than a popup of the same text.
if &filetype != 'vim'
setlocal keywordprg=:LspHover
endif
nnoremap <buffer> gd <Cmd>LspDefinition<CR>
enddef
# Not FileType: a server that is not installed leaves the buffer as it was.
augroup lsprc
autocmd!
autocmd User LspAttached LspBuffer()
augroup ENDFor legacy Vim script
function! s:LspBuffer()
" 'autocomplete' offers what 'complete' names, and "o" names 'omnifunc',
" which the plugin has set.
setlocal complete^=o
setlocal autocomplete " See also 'autocompletedelay'
setlocal completeopt+=popup
setlocal signcolumn=yes
" The Vim filetype points 'keywordprg' at ":help", which offers more
" than a popup of the same text.
if &filetype !=# 'vim'
setlocal keywordprg=:LspHover
endif
nnoremap <buffer> gd <Cmd>LspDefinition<CR>
endfunction
" Not FileType: a server that is not installed leaves the buffer as it was.
augroup lsprc
autocmd!
autocmd User LspAttached call s:LspBuffer()
augroup END- C/C++: clangd 18.1.3
- Vim script: vim9ls 0.1.002
- Python: pylsp 1.15.0, pyright 1.1.411, basedpyright 1.39.10, ty 0.0.78
- Go: gopls 0.18.0
Only what a server offers is asked for, so what you get depends on the server.
| Category | Command | Description |
|---|---|---|
| Server | :LspStart |
Connect this buffer to the servers for its filetype |
:LspStop |
Shut down every running server | |
:LspStatus[!] |
List the running servers, with ! what answers each feature here |
|
:LspConfigCheck |
Report what in g:lsp_client_config or g:lsp_server_list cannot be read |
|
:LspConfigReload |
Tell every running server that its settings changed | |
:LspWorkspaceFolderAdd [{dir}] |
Hand a directory to this server as another workspace folder | |
:LspWorkspaceFolderRemove {dir} |
Take a workspace folder back from this server | |
:LspExecuteCommand {command} |
Have this server run one of the commands it offers | |
:LspLog |
Open what the servers for this buffer have logged | |
| Asking | :LspHover |
What the server knows about the symbol |
:LspSignature |
What the call the cursor is in takes | |
| Jumping | :LspDefinition |
Jump to the definition of the symbol |
:LspDeclaration |
Jump to the declaration, a prototype in C | |
:LspTypeDefinition |
Jump to the definition of the symbol's type | |
:LspImplementation |
Jump to what implements the symbol | |
| Lists | :LspReferences |
Every mention, into the quickfix list |
:LspIncomingCalls |
Who calls the function under the cursor | |
:LspOutgoingCalls |
What the function under the cursor calls | |
:LspSuperTypes |
The types this one is derived from | |
:LspSubTypes |
The types derived from this one | |
:LspOutline |
The symbols in this buffer, into the location list | |
:LspSymbol {query} |
Search the workspace for symbols | |
:LspDiag |
The diagnostics, into the location list | |
:LspWorkspaceDiag |
The diagnostics for the workspace, into the quickfix list | |
| Changing | :LspCodeAction |
Offer what the server can do here |
:LspRename [{name}] |
Rename the symbol everywhere | |
:LspRenameFile [{name}] |
Rename this file, imports and all | |
:LspFormat |
Format this buffer, or a range of it | |
| Display | :LspInlayHint |
Turn the inlay hints on or off |
:LspInlayHintInfo |
What the server reports about the hint here | |
:LspInlayHintApply |
Put the hint here into the file | |
:LspFolding |
Turn the folds from the server on or off | |
:LspCodeLens |
Turn the code lenses on or off | |
:LspCodeLensRun |
Run the lens above this line | |
:LspDocumentLink |
Turn the document links on or off | |
:LspDocumentLinkOpen |
Go where the link under the cursor leads | |
:LspDocumentLinkInfo |
What the server reports about that link | |
:LspSemanticTokens |
Turn the coloring from the server on or off |
See :help lsp.txt for the options and the details.
What this client does with each of the 95 requests and notifications in the LSP 3.18 meta model: 75 are answered, and 20 are left out for the reason given.
Method-by-method tables
| Method | State | Note |
|---|---|---|
initialize |
yes | |
initialized |
yes | |
shutdown |
yes | |
exit |
yes | |
client/registerCapability |
yes | what a server asks for at run time |
client/unregisterCapability |
yes | |
$/cancelRequest |
yes | |
$/progress |
yes | both the server's work and its own requests |
$/setTrace |
no | :LspLog shows what the server logs anyway |
$/logTrace |
no | nothing asks for a trace |
| Method | State | Note |
|---|---|---|
textDocument/didOpen |
yes | |
textDocument/didChange |
yes | incremental, through listener_add() |
textDocument/didSave |
yes | |
textDocument/didClose |
yes | |
textDocument/willSave |
yes | |
textDocument/willSaveWaitUntil |
yes | waited for before the write |
notebookDocument/didOpen |
no | Vim has no notebook to open |
notebookDocument/didChange |
no | |
notebookDocument/didSave |
no | |
notebookDocument/didClose |
no |
| Method | State | Note |
|---|---|---|
textDocument/completion |
yes | with the context and isIncomplete |
completionItem/resolve |
yes | for the documentation popup |
textDocument/hover |
yes | :LspHover |
textDocument/signatureHelp |
yes | :LspSignature |
textDocument/declaration |
yes | :LspDeclaration |
textDocument/definition |
yes | :LspDefinition |
textDocument/typeDefinition |
yes | :LspTypeDefinition |
textDocument/implementation |
yes | :LspImplementation |
textDocument/references |
yes | :LspReferences |
textDocument/documentHighlight |
yes | the other uses of the symbol |
textDocument/documentSymbol |
yes | :LspOutline |
textDocument/codeAction |
yes | :LspCodeAction |
codeAction/resolve |
yes | |
textDocument/codeLens |
yes | :LspCodeLens |
codeLens/resolve |
yes | |
textDocument/documentLink |
yes | :LspDocumentLink |
documentLink/resolve |
yes | |
textDocument/foldingRange |
yes | :LspFolding |
textDocument/selectionRange |
yes | <Plug>(lsp-selection-expand) |
textDocument/prepareCallHierarchy |
yes | |
callHierarchy/incomingCalls |
yes | :LspIncomingCalls |
callHierarchy/outgoingCalls |
yes | :LspOutgoingCalls |
textDocument/prepareTypeHierarchy |
yes | |
typeHierarchy/supertypes |
yes | :LspSuperTypes |
typeHierarchy/subtypes |
yes | :LspSubTypes |
textDocument/semanticTokens/full |
yes | :LspSemanticTokens |
textDocument/semanticTokens/full/delta |
yes | |
textDocument/semanticTokens/range |
yes | |
textDocument/inlayHint |
yes | :LspInlayHint |
inlayHint/resolve |
yes | :LspInlayHintInfo, :LspInlayHintApply |
textDocument/publishDiagnostics |
yes | what a server sends unasked |
textDocument/diagnostic |
yes | what a server waits to be asked for |
textDocument/formatting |
yes | :LspFormat |
textDocument/rangeFormatting |
yes | :{range}LspFormat |
textDocument/rangesFormatting |
no | Vim has one range at a time |
textDocument/onTypeFormatting |
yes | off unless "on_type_formatting" is set |
textDocument/rename |
yes | :LspRename |
textDocument/prepareRename |
yes | turns a rename down before it is sent |
textDocument/linkedEditingRange |
no | nothing here mirrors an edit into another range |
textDocument/documentColor |
no | nothing here shows a color |
textDocument/colorPresentation |
no | |
textDocument/inlineValue |
no | for a debugger, which this is not |
textDocument/inlineCompletion |
no | completion is asked for, not offered while typing |
textDocument/moniker |
no | for an indexer, which this is not |
| Method | State | Note |
|---|---|---|
workspace/symbol |
yes | :LspSymbol |
workspaceSymbol/resolve |
yes | |
workspace/configuration |
yes | answered from settings in the server entry |
workspace/didChangeConfiguration |
yes | :LspConfigReload |
workspace/workspaceFolders |
yes | |
workspace/didChangeWorkspaceFolders |
yes | :LspWorkspaceFolderAdd, :LspWorkspaceFolderRemove |
workspace/didChangeWatchedFiles |
yes | for the files a server asks to watch |
workspace/executeCommand |
yes | for a code action or lens the server runs, and :LspExecuteCommand |
workspace/applyEdit |
yes | changes the server works out on its own |
workspace/diagnostic |
yes | asked by :LspWorkspaceDiag, or at startup when "workspace_diagnostics" is set |
workspace/willCreateFiles |
yes | |
workspace/didCreateFiles |
yes | |
workspace/willRenameFiles |
yes | :LspRenameFile |
workspace/didRenameFiles |
yes | |
workspace/willDeleteFiles |
no | nothing here deletes a file |
workspace/didDeleteFiles |
no | |
workspace/codeLens/refresh |
yes | |
workspace/inlayHint/refresh |
yes | |
workspace/semanticTokens/refresh |
yes | |
workspace/diagnostic/refresh |
yes | |
workspace/foldingRange/refresh |
yes | |
workspace/inlineValue/refresh |
no | |
workspace/textDocumentContent |
no | for a document the server makes up |
workspace/textDocumentContent/refresh |
no |
| Method | State | Note |
|---|---|---|
window/showMessage |
yes | |
window/showMessageRequest |
yes | an answer is picked from a menu |
window/logMessage |
yes | :LspLog |
window/showDocument |
yes | opens it here, or hands a URI to :URLOpen |
window/workDoneProgress/create |
yes | |
window/workDoneProgress/cancel |
no | nothing here offers a way to call off the server's work |
telemetry/event |
no | there is nowhere to send it |
It is written as much to put Vim's own side of the protocol through its paces as to be used every day, and some of what turned up that way has gone into Vim itself. Expect it to move at that pace.
How a report or a patch is best put, and how the tests are run, is in CONTRIBUTING.
This plugin is developed with the support of AI (Claude).
Same terms as Vim itself: the Vim license, in LICENSE here and under
:help license.
