]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/lsp.vim
Diags are not highlighted after a buffer is reloaded
[vim-lsp.git] / autoload / lsp / lsp.vim
1 vim9script
2
3 # Vim9 LSP client
4 #
5 # The functions called by plugin/lsp.vim are in this file.
6
7 # Needs Vim 9.0 and higher
8 if v:version < 900
9   finish
10 endif
11
12 import './options.vim' as opt
13 import './lspserver.vim' as lserver
14 import './util.vim'
15 import './buffer.vim' as buf
16 import './completion.vim'
17 import './textedit.vim'
18 import './diag.vim'
19 import './symbol.vim'
20 import './outline.vim'
21 import './signature.vim'
22 import './codeaction.vim'
23 import './inlayhints.vim'
24
25 # LSP server information
26 var LSPServers: list<dict<any>> = []
27
28 # filetype to LSP server map
29 var ftypeServerMap: dict<list<dict<any>>> = {}
30
31 var lspInitializedOnce = false
32
33 def LspInitOnce()
34   hlset([
35     {name: 'LspTextRef', default: true, linksto: 'Search'},
36     {name: 'LspReadRef', default: true, linksto: 'DiffChange'},
37     {name: 'LspWriteRef', default: true, linksto: 'DiffDelete'}
38   ])
39
40   var override = &cursorline
41       && &cursorlineopt =~ '\<line\>\|\<screenline\>\|\<both\>'
42
43   prop_type_add('LspTextRef', {highlight: 'LspTextRef', override: override})
44   prop_type_add('LspReadRef', {highlight: 'LspReadRef', override: override})
45   prop_type_add('LspWriteRef', {highlight: 'LspWriteRef', override: override})
46
47   diag.InitOnce()
48   inlayhints.InitOnce()
49   signature.InitOnce()
50   symbol.InitOnce()
51
52   :set ballooneval balloonevalterm
53   lspInitializedOnce = true
54 enddef
55
56 # Returns the LSP servers for the a specific filetype. Based on how well there
57 # score, LSP servers with the same score are being returned.
58 # Returns an empty list if the servers is not found.
59 def LspGetServers(bnr: number, ftype: string): list<dict<any>>
60   if !ftypeServerMap->has_key(ftype)
61     return []
62   endif
63
64   var bufDir = bnr->bufname()->fnamemodify(':p:h')
65
66   return ftypeServerMap[ftype]->filter((key, lspserver) => {
67     # Don't run the server if no path is found
68     if !lspserver.runIfSearchFiles->empty()
69       var path = util.FindNearestRootDir(bufDir, lspserver.runIfSearchFiles)
70
71       if path->empty()
72         return false
73       endif
74     endif
75
76     # Don't run the server if a path is found
77     if !lspserver.runUnlessSearchFiles->empty()
78       var path = util.FindNearestRootDir(bufDir, lspserver.runUnlessSearchFiles)
79
80       if !path->empty()
81         return false
82       endif
83     endif
84
85     # Run the server
86     return true
87   })
88 enddef
89
90 # Add a LSP server for a filetype
91 def LspAddServer(ftype: string, lspsrv: dict<any>)
92   var lspsrvlst = ftypeServerMap->has_key(ftype) ? ftypeServerMap[ftype] : []
93   lspsrvlst->add(lspsrv)
94   ftypeServerMap[ftype] = lspsrvlst
95 enddef
96
97 # Enable/disable the logging of the language server protocol messages
98 def ServerDebug(arg: string)
99   if ['errors', 'messages', 'off', 'on']->index(arg) == -1
100     util.ErrMsg($'Unsupported argument "{arg}"')
101     return
102   endif
103
104   var lspservers: list<dict<any>> = buf.CurbufGetServers()
105   if lspservers->empty()
106     util.WarnMsg($'No Lsp servers found for "{@%}"')
107     return
108   endif
109
110   for lspserver in lspservers
111     if arg == 'on'
112       util.ClearTraceLogs(lspserver.logfile)
113       util.ClearTraceLogs(lspserver.errfile)
114       lspserver.debug = true
115     elseif arg == 'off'
116       lspserver.debug = false
117     elseif arg == 'messages'
118       util.ServerMessagesShow(lspserver.logfile)
119     else
120       util.ServerMessagesShow(lspserver.errfile)
121     endif
122   endfor
123 enddef
124
125 # Show information about all the LSP servers
126 export def ShowAllServers()
127   var lines = []
128   # Add filetype to server mapping information
129   lines->add('Filetype Information')
130   lines->add('====================')
131   for [ftype, lspservers] in ftypeServerMap->items()
132     for lspserver in lspservers
133       lines->add($"Filetype: '{ftype}'")
134       lines->add($"Server Name: '{lspserver.name}'")
135       lines->add($"Server Path: '{lspserver.path}'")
136       lines->add($"Status: {lspserver.running ? 'Running' : 'Not running'}")
137       lines->add('')
138     endfor
139   endfor
140
141   # Add buffer to server mapping information
142   lines->add('Buffer Information')
143   lines->add('==================')
144   for bnr in range(1, bufnr('$'))
145     var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
146     if !lspservers->empty()
147       lines->add($"Buffer: '{bufname(bnr)}'")
148       for lspserver in lspservers
149         lines->add($"Server Path: '{lspserver.path}'")
150         lines->add($"Status: {lspserver.running ? 'Running' : 'Not running'}")
151       endfor
152       lines->add('')
153     endif
154   endfor
155
156   var wid = bufwinid('Language-Servers')
157   if wid != -1
158     wid->win_gotoid()
159     :setlocal modifiable
160     :silent! :%d _
161   else
162     :new Language-Servers
163     :setlocal buftype=nofile
164     :setlocal bufhidden=wipe
165     :setlocal noswapfile
166     :setlocal nonumber nornu
167     :setlocal fdc=0 signcolumn=no
168   endif
169   setline(1, lines)
170   :setlocal nomodified
171   :setlocal nomodifiable
172 enddef
173
174 # Create a new window containing the buffer "bname" or if the window is
175 # already present then jump to it.
176 def OpenScratchWindow(bname: string)
177   var wid = bufwinid(bname)
178   if wid != -1
179     wid->win_gotoid()
180     :setlocal modifiable
181     :silent! :%d _
182   else
183     exe $':new {bname}'
184     :setlocal buftype=nofile
185     :setlocal bufhidden=wipe
186     :setlocal noswapfile
187     :setlocal nonumber nornu
188     :setlocal fdc=0 signcolumn=no
189   endif
190 enddef
191
192 # Show the status of the LSP server for the current buffer
193 def ShowServer(arg: string)
194   if ['status', 'capabilities', 'initializeRequest', 'messages']->index(arg) == -1
195     util.ErrMsg($'Unsupported argument "{arg}"')
196     return
197   endif
198
199   var lspservers: list<dict<any>> = buf.CurbufGetServers()
200   if lspservers->empty()
201     util.WarnMsg($'No Lsp servers found for "{@%}"')
202     return
203   endif
204
205   var windowName: string = ''
206   var lines: list<string> = []
207   if arg->empty() || arg == 'status'
208     windowName = $'LangServer-Status'
209     for lspserver in lspservers
210       if !lines->empty()
211         lines->extend(['', repeat('=', &columns), ''])
212       endif
213       var msg = $"LSP server '{lspserver.name}' is "
214       if lspserver.running
215         msg ..= 'running'
216       else
217         msg ..= 'not running'
218       endif
219       lines->add(msg)
220     endfor
221   elseif arg == 'capabilities'
222     windowName = $'LangServer-Capabilities'
223     for lspserver in lspservers
224       if !lines->empty()
225         lines->extend(['', repeat('=', &columns), ''])
226       endif
227       lines->extend(lspserver.getCapabilities())
228     endfor
229   elseif arg == 'initializeRequest'
230     windowName = $'LangServer-InitializeRequest'
231     for lspserver in lspservers
232       if !lines->empty()
233         lines->extend(['', repeat('=', &columns), ''])
234       endif
235       lines->extend(lspserver.getInitializeRequest())
236     endfor
237   elseif arg == 'messages'
238     windowName = $'LangServer-Messages'
239     for lspserver in lspservers
240       if !lines->empty()
241         lines->extend(['', repeat('=', &columns), ''])
242       endif
243       lines->extend(lspserver.getMessages())
244     endfor
245   else
246     util.ErrMsg($'Unsupported argument "{arg}"')
247     return
248   endif
249
250   if lines->len() > 1
251     OpenScratchWindow(windowName)
252     setline(1, lines)
253     :setlocal nomodified
254     :setlocal nomodifiable
255   else
256     util.InfoMsg(lines[0])
257   endif
258 enddef
259
260 # Get LSP server running status for filetype "ftype"
261 # Return true if running, or false if not found or not running
262 export def ServerRunning(ftype: string): bool
263   if ftypeServerMap->has_key(ftype)
264     var lspservers = ftypeServerMap[ftype]
265     for lspserver in lspservers
266       if lspserver.running
267         return true
268       endif
269     endfor
270   endif
271
272   return false
273 enddef
274
275 # Go to a definition using "textDocument/definition" LSP request
276 export def GotoDefinition(peek: bool, cmdmods: string, count: number)
277   var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
278   if lspserver->empty()
279     return
280   endif
281
282   lspserver.gotoDefinition(peek, cmdmods, count)
283 enddef
284
285 # Go to a declaration using "textDocument/declaration" LSP request
286 export def GotoDeclaration(peek: bool, cmdmods: string, count: number)
287   var lspserver: dict<any> = buf.CurbufGetServerChecked('declaration')
288   if lspserver->empty()
289     return
290   endif
291
292   lspserver.gotoDeclaration(peek, cmdmods, count)
293 enddef
294
295 # Go to a type definition using "textDocument/typeDefinition" LSP request
296 export def GotoTypedef(peek: bool, cmdmods: string, count: number)
297   var lspserver: dict<any> = buf.CurbufGetServerChecked('typeDefinition')
298   if lspserver->empty()
299     return
300   endif
301
302   lspserver.gotoTypeDef(peek, cmdmods, count)
303 enddef
304
305 # Go to a implementation using "textDocument/implementation" LSP request
306 export def GotoImplementation(peek: bool, cmdmods: string, count: number)
307   var lspserver: dict<any> = buf.CurbufGetServerChecked('implementation')
308   if lspserver->empty()
309     return
310   endif
311
312   lspserver.gotoImplementation(peek, cmdmods, count)
313 enddef
314
315 # Switch source header using "textDocument/switchSourceHeader" LSP request
316 # (Clangd specifc extension)
317 export def SwitchSourceHeader()
318   var lspserver: dict<any> = buf.CurbufGetServerChecked()
319   if lspserver->empty()
320     return
321   endif
322
323   lspserver.switchSourceHeader()
324 enddef
325
326 # Show the signature using "textDocument/signatureHelp" LSP method
327 # Invoked from an insert-mode mapping, so return an empty string.
328 def g:LspShowSignature(): string
329   var lspserver: dict<any> = buf.CurbufGetServerChecked('signatureHelp')
330   if lspserver->empty()
331     return ''
332   endif
333
334   # first send all the changes in the current buffer to the LSP server
335   listener_flush()
336   lspserver.showSignature()
337   return ''
338 enddef
339
340 # A buffer is saved. Send the "textDocument/didSave" LSP notification
341 def LspSavedFile()
342   var bnr: number = expand('<abuf>')->str2nr()
343   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)->filter(
344     (key, lspsrv) => !lspsrv->empty() && lspsrv.running
345   )
346
347   if lspservers->empty()
348     return
349   endif
350
351   for lspserver in lspservers
352     lspserver.didSaveFile(bnr)
353   endfor
354 enddef
355
356 # Return the diagnostic text from the LSP server for the current mouse line to
357 # display in a balloon
358 var lspDiagPopupID: number = 0
359 var lspDiagPopupInfo: dict<any> = {}
360 def g:LspDiagExpr(): any
361   # Display the diagnostic message only if the mouse is over the gutter for
362   # the signs.
363   if opt.lspOptions.noDiagHoverOnLine && v:beval_col >= 2
364     return ''
365   endif
366
367   var diagsInfo: list<dict<any>> = diag.GetDiagsByLine(
368     v:beval_bufnr,
369     v:beval_lnum
370   )
371   if diagsInfo->empty()
372     # No diagnostic for the current cursor location
373     return ''
374   endif
375
376   # Include all diagnostics from the current line in the message
377   var message: list<string> = []
378   for diag in diagsInfo
379     message->extend(diag.message->split("\n"))
380   endfor
381
382   return message
383 enddef
384
385 # Called after leaving insert mode. Used to process diag messages (if any)
386 def LspLeftInsertMode()
387   if !exists('b:LspDiagsUpdatePending')
388     return
389   endif
390   :unlet b:LspDiagsUpdatePending
391
392   var bnr: number = bufnr()
393   diag.ProcessNewDiags(bnr)
394 enddef
395
396 # Add buffer-local autocmds when attaching a LSP server to a buffer
397 def AddBufLocalAutocmds(lspserver: dict<any>, bnr: number): void
398   var acmds: list<dict<any>> = []
399
400   # file saved notification handler
401   acmds->add({bufnr: bnr,
402               event: 'BufWritePost',
403               group: 'LSPBufferAutocmds',
404               cmd: 'LspSavedFile()'})
405
406   # Update the diagnostics when insert mode is stopped
407   acmds->add({bufnr: bnr,
408               event: 'InsertLeave',
409               group: 'LSPBufferAutocmds',
410               cmd: 'LspLeftInsertMode()'})
411
412   # Auto highlight all the occurrences of the current keyword
413   if opt.lspOptions.autoHighlight &&
414                         lspserver.isDocumentHighlightProvider
415     acmds->add({bufnr: bnr,
416                 event: 'CursorMoved',
417                 group: 'LSPBufferAutocmds',
418                 cmd: 'call LspDocHighlightClear() | call LspDocHighlight("silent")'})
419   endif
420
421   # Show diagnostics on the status line
422   if opt.lspOptions.showDiagOnStatusLine
423     acmds->add({bufnr: bnr,
424                 event: 'CursorMoved',
425                 group: 'LSPBufferAutocmds',
426                 cmd: 'LspShowCurrentDiagInStatusLine()'})
427   endif
428
429   autocmd_add(acmds)
430 enddef
431
432 def BufferInit(lspserverId: number, bnr: number): void
433   var lspserver = buf.BufLspServerGetById(bnr, lspserverId)
434   if lspserver->empty() || !lspserver.running
435     return
436   endif
437
438   var ftype: string = bnr->getbufvar('&filetype')
439   lspserver.textdocDidOpen(bnr, ftype)
440
441   # add a listener to track changes to this buffer
442   listener_add((_bnr: number, start: number, end: number, added: number, changes: list<dict<number>>) => {
443     lspserver.textdocDidChange(bnr, start, end, added, changes)
444   }, bnr)
445
446   AddBufLocalAutocmds(lspserver, bnr)
447
448   setbufvar(bnr, '&balloonexpr', 'g:LspDiagExpr()')
449
450   signature.BufferInit(lspserver)
451   inlayhints.BufferInit(lspserver, bnr)
452
453   var allServersReady = true
454   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
455   for lspsrv in lspservers
456     if !lspsrv.ready
457       allServersReady = false
458       break
459     endif
460   endfor
461
462   if allServersReady
463     for lspsrv in lspservers
464       # It's only possible to initialize completion when all server capabilities
465       # are known.
466       var completionServer = buf.BufLspServerGet(bnr, 'completion')
467       if !completionServer->empty() && lspsrv.id == completionServer.id
468         completion.BufferInit(lspsrv, bnr, ftype)
469       endif
470     endfor
471
472     if exists('#User#LspAttached')
473       doautocmd <nomodeline> User LspAttached
474     endif
475   endif
476 enddef
477
478 # A new buffer is opened. If LSP is supported for this buffer, then add it
479 export def AddFile(bnr: number): void
480   if buf.BufHasLspServer(bnr)
481     # LSP server for this buffer is already initialized and running
482     return
483   endif
484
485   # Skip remote files
486   if util.LspUriRemote(bnr->bufname()->fnamemodify(':p'))
487     return
488   endif
489
490   var ftype: string = bnr->getbufvar('&filetype')
491   if ftype->empty()
492     return
493   endif
494   var lspservers: list<dict<any>> = LspGetServers(bnr, ftype)
495   if lspservers->empty()
496     return
497   endif
498   for lspserver in lspservers
499     if !lspserver.running
500       if !lspInitializedOnce
501         LspInitOnce()
502       endif
503       lspserver.startServer(bnr)
504     endif
505     buf.BufLspServerSet(bnr, lspserver)
506
507     if lspserver.ready
508       BufferInit(lspserver.id, bnr)
509     else
510       augroup LSPBufferAutocmds
511         exe $'autocmd User LspServerReady_{lspserver.id} ++once BufferInit({lspserver.id}, {bnr})'
512       augroup END
513     endif
514   endfor
515 enddef
516
517 # Notify LSP server to remove a file
518 export def RemoveFile(bnr: number): void
519   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
520   for lspserver in lspservers->copy()
521     if lspserver->empty()
522       continue
523     endif
524     if lspserver.running
525       lspserver.textdocDidClose(bnr)
526     endif
527     diag.DiagRemoveFile(bnr)
528     buf.BufLspServerRemove(bnr, lspserver)
529   endfor
530 enddef
531
532 # Buffer 'bnr' is loaded in a window, send the latest buffer contents to the
533 # language servers.
534 export def BufferLoadedInWin(bnr: number)
535   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
536   if lspservers->empty()
537     # No language servers for this buffer
538     return
539   endif
540   for lspserver in lspservers
541     if !lspserver->empty() && lspserver.ready
542       lspserver.textdocDidChange(bnr, 0, 0, 0, [])
543     endif
544   endfor
545   # Refresh the displayed diags visuals
546   diag.DiagsRefresh(bnr)
547 enddef
548
549 # Stop all the LSP servers
550 export def StopAllServers()
551   for lspserver in LSPServers
552     if lspserver.running
553       lspserver.stopServer()
554     endif
555   endfor
556 enddef
557
558 # Add all the buffers with 'filetype' set to "ftype" to the language server.
559 def AddBuffersToLsp(ftype: string)
560   # Add all the buffers with the same file type as the current buffer
561   for binfo in getbufinfo({bufloaded: 1})
562     if binfo.bufnr->getbufvar('&filetype') == ftype
563       AddFile(binfo.bufnr)
564     endif
565   endfor
566 enddef
567
568 # Restart the LSP server for the current buffer
569 def RestartServer()
570   var lspservers: list<dict<any>> = buf.CurbufGetServers()
571   if lspservers->empty()
572     util.WarnMsg($'No Lsp servers found for "{@%}"')
573     return
574   endif
575
576   # Remove all the buffers with the same file type as the current buffer
577   var ftype: string = &filetype
578   for binfo in getbufinfo()
579     if binfo.bufnr->getbufvar('&filetype') == ftype
580       RemoveFile(binfo.bufnr)
581     endif
582   endfor
583
584   for lspserver in lspservers
585     # Stop the server (if running)
586     if lspserver.running
587       lspserver.stopServer()
588     endif
589
590     # Start the server again
591     lspserver.startServer(bufnr(''))
592   endfor
593
594   AddBuffersToLsp(ftype)
595 enddef
596
597 # Add the LSP server for files with 'filetype' as "ftype".
598 def AddServerForFiltype(lspserver: dict<any>, ftype: string, omnicompl: bool)
599   LspAddServer(ftype, lspserver)
600   completion.OmniComplSet(ftype, omnicompl)
601
602   # If a buffer of this file type is already present, then send it to the LSP
603   # server now.
604   AddBuffersToLsp(ftype)
605 enddef
606
607 # Register a LSP server for one or more file types
608 export def AddServer(serverList: list<dict<any>>)
609   for server in serverList
610     if !server->has_key('filetype') || !server->has_key('path')
611       util.ErrMsg('LSP server information is missing filetype or path')
612       continue
613     endif
614     # Enable omni-completion by default
615     server.omnicompl = server->get('omnicompl', true)
616
617     if !server.path->executable()
618       if !opt.lspOptions.ignoreMissingServer
619         util.ErrMsg($'LSP server {server.path} is not found')
620       endif
621       continue
622     endif
623     if server->has_key('args')
624       if server.args->type() != v:t_list
625         util.ErrMsg($'Arguments for LSP server {server.args} is not a List')
626         continue
627       endif
628     else
629       server.args = []
630     endif
631
632     if !server->has_key('initializationOptions')
633         || server.initializationOptions->type() != v:t_dict
634       server.initializationOptions = {}
635     endif
636
637     if !server->has_key('forceOffsetEncoding')
638         || server.forceOffsetEncoding->type() != v:t_string
639         || (server.forceOffsetEncoding != 'utf-8'
640             && server.forceOffsetEncoding != 'utf-16'
641             && server.forceOffsetEncoding != 'utf-32')
642       server.forceOffsetEncoding = ''
643     endif
644
645     if !server->has_key('customNotificationHandlers')
646         || server.customNotificationHandlers->type() != v:t_dict
647       server.customNotificationHandlers = {}
648     endif
649
650     if server->has_key('processDiagHandler')
651       if server.processDiagHandler->type() != v:t_func
652         util.ErrMsg($'Setting of processDiagHandler {server.processDiagHandler} is not a Funcref nor lambda')
653         continue
654       endif
655     else
656       server.processDiagHandler = null_function
657     endif
658
659     if !server->has_key('customRequestHandlers')
660         || server.customRequestHandlers->type() != v:t_dict
661       server.customRequestHandlers = {}
662     endif
663
664     if !server->has_key('features') || server.features->type() != v:t_dict
665       server.features = {}
666     endif
667
668     if server.omnicompl->type() != v:t_bool
669       util.ErrMsg($'Setting of omnicompl {server.omnicompl} is not a Boolean')
670       continue
671     endif
672
673     if !server->has_key('syncInit')
674       server.syncInit = false
675     endif
676
677     if !server->has_key('name') || server.name->type() != v:t_string
678         || server.name->empty()
679       # Use the executable name (without the extension) as the language server
680       # name.
681       server.name = server.path->fnamemodify(':t:r')
682     endif
683
684     if !server->has_key('debug') || server.debug->type() != v:t_bool
685       server.debug = false
686     endif
687
688     if !server->has_key('traceLevel')
689         || server->type() != v:t_string
690         || (server.traceLevel != 'off' && server.traceLevel != 'debug'
691             && server.traceLevel != 'verbose')
692       server.traceLevel = 'off'
693     endif
694
695     if !server->has_key('workspaceConfig')
696         || server.workspaceConfig->type() != v:t_dict
697       server.workspaceConfig = {}
698     endif
699
700     if !server->has_key('rootSearch') || server.rootSearch->type() != v:t_list
701       server.rootSearch = []
702     endif
703
704     if !server->has_key('runIfSearch') ||
705         server.runIfSearch->type() != v:t_list
706       server.runIfSearch = []
707     endif
708
709     if !server->has_key('runUnlessSearch') ||
710         server.runUnlessSearch->type() != v:t_list
711       server.runUnlessSearch = []
712     endif
713
714     var lspserver: dict<any> = lserver.NewLspServer(server)
715
716     var ftypes = server.filetype
717     if ftypes->type() == v:t_string
718       AddServerForFiltype(lspserver, ftypes, server.omnicompl)
719     elseif ftypes->type() == v:t_list
720       for ftype in ftypes
721         AddServerForFiltype(lspserver, ftype, server.omnicompl)
722       endfor
723     else
724       util.ErrMsg($'Unsupported file type information "{ftypes->string()}" in LSP server registration')
725       continue
726     endif
727   endfor
728 enddef
729
730 # The LSP server is considered ready when the server capabilities are
731 # received ("initialize" LSP reply message)
732 export def ServerReady(): bool
733   var fname: string = @%
734   if fname->empty()
735     return false
736   endif
737
738   var lspservers: list<dict<any>> = buf.CurbufGetServers()
739   if lspservers->empty()
740     return false
741   endif
742
743   for lspserver in lspservers
744     if !lspserver.ready
745       return false
746     endif
747   endfor
748
749   return true
750 enddef
751
752 # set the LSP server trace level for the current buffer
753 # Params: SetTraceParams
754 def ServerTraceSet(traceVal: string)
755   if ['off', 'messages', 'verbose']->index(traceVal) == -1
756     util.ErrMsg($'Unsupported argument "{traceVal}"')
757     return
758   endif
759
760   var lspservers: list<dict<any>> = buf.CurbufGetServers()
761   if lspservers->empty()
762     util.WarnMsg($'No Lsp servers found for "{@%}"')
763     return
764   endif
765
766   for lspserver in lspservers
767     lspserver.setTrace(traceVal)
768   endfor
769 enddef
770
771 # Display the diagnostic messages from the LSP server for the current buffer
772 # in a quickfix list
773 export def ShowDiagnostics(): void
774   diag.ShowAllDiags()
775 enddef
776
777 # Show the diagnostic message for the current line
778 export def LspShowCurrentDiag(atPos: bool)
779   diag.ShowCurrentDiag(atPos)
780 enddef
781
782 # Display the diagnostics for the current line in the status line.
783 export def LspShowCurrentDiagInStatusLine()
784   var fname: string = @%
785   if fname->empty()
786     return
787   endif
788
789   diag.ShowCurrentDiagInStatusLine()
790 enddef
791
792 # get the count of diagnostics in the current buffer
793 export def ErrorCount(): dict<number>
794   var res = {Error: 0, Warn: 0, Info: 0, Hint: 0}
795   var fname: string = @%
796   if fname->empty()
797     return res
798   endif
799
800   return diag.DiagsGetErrorCount()
801 enddef
802
803 # jump to the next/previous/first diagnostic message in the current buffer
804 export def JumpToDiag(which: string, count: number = 0): void
805   diag.LspDiagsJump(which, count)
806 enddef
807
808 # Display the hover message from the LSP server for the current cursor
809 # location
810 export def Hover(cmdmods: string)
811   var lspserver: dict<any> = buf.CurbufGetServerChecked('hover')
812   if lspserver->empty()
813     return
814   endif
815
816   lspserver.hover(cmdmods)
817 enddef
818
819 # Enable or disable inlay hints
820 export def InlayHints(ctl: string)
821   if ctl == 'enable'
822     inlayhints.InlayHintsEnable()
823   elseif ctl == 'disable'
824     inlayhints.InlayHintsDisable()
825   else
826     util.ErrMsg($'LspInlayHints - Unsupported argument "{ctl}"')
827   endif
828 enddef
829
830 # Command-line completion for the ":LspInlayHints" command
831 export def LspInlayHintsComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
832   var l = ['enable', 'disable']
833   return filter(l, (_, val) => val =~ $'^{arglead}')
834 enddef
835
836 # show symbol references
837 export def ShowReferences(peek: bool)
838   var lspserver: dict<any> = buf.CurbufGetServerChecked('references')
839   if lspserver->empty()
840     return
841   endif
842
843   lspserver.showReferences(peek)
844 enddef
845
846 # highlight all the places where a symbol is referenced
847 def g:LspDocHighlight(cmdmods: string = '')
848   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
849   if lspserver->empty()
850     return
851   endif
852
853   lspserver.docHighlight(cmdmods)
854 enddef
855
856 # clear the symbol reference highlight
857 def g:LspDocHighlightClear()
858   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
859   if lspserver->empty()
860     return
861   endif
862
863   if has('patch-9.0.0233')
864     prop_remove({types: ['LspTextRef', 'LspReadRef', 'LspWriteRef'], all: true})
865   else
866     prop_remove({type: 'LspTextRef', all: true})
867     prop_remove({type: 'LspReadRef', all: true})
868     prop_remove({type: 'LspWriteRef', all: true})
869   endif
870 enddef
871
872 def g:LspRequestDocSymbols()
873   if outline.SkipOutlineRefresh()
874     return
875   endif
876
877   var fname: string = @%
878   if fname->empty()
879     return
880   endif
881
882   var lspserver: dict<any> = buf.CurbufGetServer('documentSymbol')
883   if lspserver->empty() || !lspserver.running || !lspserver.ready
884     return
885   endif
886
887   lspserver.getDocSymbols(fname, true)
888 enddef
889
890 # open a window and display all the symbols in a file (outline)
891 export def Outline(cmdmods: string, winsize: number)
892   var fname: string = @%
893   if fname->empty()
894     return
895   endif
896
897   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
898   if lspserver->empty() || !lspserver.running || !lspserver.ready
899     return
900   endif
901
902   outline.OpenOutlineWindow(cmdmods, winsize)
903   g:LspRequestDocSymbols()
904 enddef
905
906 # show all the symbols in a file in a popup menu
907 export def ShowDocSymbols()
908   var fname: string = @%
909   if fname->empty()
910     return
911   endif
912
913   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
914   if lspserver->empty() || !lspserver.running || !lspserver.ready
915     return
916   endif
917
918   lspserver.getDocSymbols(fname, false)
919 enddef
920
921 # Format the entire file
922 export def TextDocFormat(range_args: number, line1: number, line2: number)
923   if !&modifiable
924     util.ErrMsg('Current file is not a modifiable file')
925     return
926   endif
927
928   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
929   if lspserver->empty()
930     return
931   endif
932
933   var fname: string = @%
934   if range_args > 0
935     lspserver.textDocFormat(fname, true, line1, line2)
936   else
937     lspserver.textDocFormat(fname, false, 0, 0)
938   endif
939 enddef
940
941 # TODO: Add support for textDocument.onTypeFormatting?
942 # Will this slow down Vim?
943
944 # Display all the locations where the current symbol is called from.
945 # Uses LSP "callHierarchy/incomingCalls" request
946 export def IncomingCalls()
947   var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
948   if lspserver->empty()
949     return
950   endif
951
952   lspserver.incomingCalls(@%)
953 enddef
954
955 # Display all the symbols used by the current symbol.
956 # Uses LSP "callHierarchy/outgoingCalls" request
957 export def OutgoingCalls()
958   var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
959   if lspserver->empty()
960     return
961   endif
962
963   lspserver.outgoingCalls(@%)
964 enddef
965
966 # Display the type hierarchy for the current symbol.  Direction is 0 for
967 # sub types and 1 for super types.
968 export def TypeHierarchy(direction: number)
969   var lspserver: dict<any> = buf.CurbufGetServerChecked('typeHierarchy')
970   if lspserver->empty()
971     return
972   endif
973
974   lspserver.typeHierarchy(direction)
975 enddef
976
977 # Rename a symbol
978 # Uses LSP "textDocument/rename" request
979 export def Rename(a_newName: string)
980   var lspserver: dict<any> = buf.CurbufGetServerChecked('rename')
981   if lspserver->empty()
982     return
983   endif
984
985   var newName: string = a_newName
986   if newName->empty()
987     var sym: string = expand('<cword>')
988     newName = input($"Rename symbol '{sym}' to: ", sym)
989     if newName->empty()
990       return
991     endif
992
993     # clear the input prompt
994     :echo "\r"
995   endif
996
997   lspserver.renameSymbol(newName)
998 enddef
999
1000 # Perform a code action
1001 # Uses LSP "textDocument/codeAction" request
1002 export def CodeAction(line1: number, line2: number, query: string)
1003   var lspserver: dict<any> = buf.CurbufGetServerChecked('codeAction')
1004   if lspserver->empty()
1005     return
1006   endif
1007
1008   var fname: string = @%
1009   lspserver.codeAction(fname, line1, line2, query)
1010 enddef
1011
1012 # Code lens
1013 # Uses LSP "textDocument/codeLens" request
1014 export def CodeLens()
1015   var lspserver: dict<any> = buf.CurbufGetServerChecked('codeLens')
1016   if lspserver->empty()
1017     return
1018   endif
1019
1020   lspserver.codeLens(@%)
1021 enddef
1022
1023 # Perform a workspace wide symbol lookup
1024 # Uses LSP "workspace/symbol" request
1025 export def SymbolSearch(queryArg: string, cmdmods: string)
1026   var lspserver: dict<any> = buf.CurbufGetServerChecked('workspaceSymbol')
1027   if lspserver->empty()
1028     return
1029   endif
1030
1031   var query: string = queryArg
1032   if query->empty()
1033     query = input('Lookup symbol: ', expand('<cword>'))
1034     if query->empty()
1035       return
1036     endif
1037   endif
1038   :redraw!
1039
1040   lspserver.workspaceQuery(query, true, cmdmods)
1041 enddef
1042
1043 # Display the list of workspace folders
1044 export def ListWorkspaceFolders()
1045   var lspservers: list<dict<any>> = buf.CurbufGetServers()
1046   for lspserver in lspservers
1047     util.InfoMsg($'Workspace Folders: "{lspserver.name}" {lspserver.workspaceFolders->string()}')
1048   endfor
1049 enddef
1050
1051 # Add a workspace folder. Default is to use the current folder.
1052 export def AddWorkspaceFolder(dirArg: string)
1053   var dirName: string = dirArg
1054   if dirName->empty()
1055     dirName = input('Add Workspace Folder: ', getcwd(), 'dir')
1056     if dirName->empty()
1057       return
1058     endif
1059   endif
1060   :redraw!
1061   if !dirName->isdirectory()
1062     util.ErrMsg($'{dirName} is not a directory')
1063     return
1064   endif
1065
1066   var lspservers: list<dict<any>> = buf.CurbufGetServers()
1067
1068   for lspserver in lspservers
1069     lspserver.addWorkspaceFolder(dirName)
1070   endfor
1071 enddef
1072
1073 # Remove a workspace folder. Default is to use the current folder.
1074 export def RemoveWorkspaceFolder(dirArg: string)
1075   var dirName: string = dirArg
1076   if dirName->empty()
1077     dirName = input('Remove Workspace Folder: ', getcwd(), 'dir')
1078     if dirName->empty()
1079       return
1080     endif
1081   endif
1082   :redraw!
1083   if !dirName->isdirectory()
1084     util.ErrMsg($'{dirName} is not a directory')
1085     return
1086   endif
1087
1088   var lspservers: list<dict<any>> = buf.CurbufGetServers()
1089   for lspserver in lspservers
1090     lspserver.removeWorkspaceFolder(dirName)
1091   endfor
1092 enddef
1093
1094 # expand the previous selection or start a new selection
1095 export def SelectionExpand()
1096   var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
1097   if lspserver->empty()
1098     return
1099   endif
1100
1101   lspserver.selectionExpand()
1102 enddef
1103
1104 # shrink the previous selection or start a new selection
1105 export def SelectionShrink()
1106   var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
1107   if lspserver->empty()
1108     return
1109   endif
1110
1111   lspserver.selectionShrink()
1112 enddef
1113
1114 # fold the entire document
1115 export def FoldDocument()
1116   var lspserver: dict<any> = buf.CurbufGetServerChecked('foldingRange')
1117   if lspserver->empty()
1118     return
1119   endif
1120
1121   if &foldmethod != 'manual'
1122     util.ErrMsg("Only works when 'foldmethod' is 'manual'")
1123     return
1124   endif
1125
1126   var fname: string = @%
1127   lspserver.foldRange(fname)
1128 enddef
1129
1130 # Enable diagnostic highlighting for all the buffers
1131 export def DiagHighlightEnable()
1132   diag.DiagsHighlightEnable()
1133 enddef
1134
1135 # Disable diagnostic highlighting for all the buffers
1136 export def DiagHighlightDisable()
1137   diag.DiagsHighlightDisable()
1138 enddef
1139
1140 # Function to use with the 'tagfunc' option.
1141 export def TagFunc(pat: string, flags: string, info: dict<any>): any
1142   var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
1143   if lspserver->empty()
1144     return v:null
1145   endif
1146
1147   return lspserver.tagFunc(pat, flags, info)
1148 enddef
1149
1150 # Function to use with the 'formatexpr' option.
1151 export def FormatExpr(): number
1152   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
1153   if lspserver->empty()
1154     return 1
1155   endif
1156
1157   lspserver.textDocFormat(@%, true, v:lnum, v:lnum + v:count - 1)
1158   return 0
1159 enddef
1160
1161 export def RegisterCmdHandler(cmd: string, Handler: func)
1162   codeaction.RegisterCmdHandler(cmd, Handler)
1163 enddef
1164
1165 # Command-line completion for the ":LspServer <cmd>" sub command
1166 def LspServerSubCmdComplete(cmds: list<string>, arglead: string, cmdline: string, cursorPos: number): list<string>
1167   var wordBegin = cmdline->match('\s\+\zs\S', cursorPos)
1168   if wordBegin == -1
1169     return cmds
1170   endif
1171
1172   # Make sure there are no additional sub-commands
1173   var wordEnd = cmdline->stridx(' ', wordBegin)
1174   if wordEnd == -1
1175     return cmds->filter((_, val) => val =~ $'^{arglead}')
1176   endif
1177
1178   return []
1179 enddef
1180
1181 # Command-line completion for the ":LspServer debug" command
1182 def LspServerDebugComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1183   return LspServerSubCmdComplete(['errors', 'messages', 'off', 'on'],
1184                                  arglead, cmdline, cursorPos)
1185 enddef
1186
1187 # Command-line completion for the ":LspServer show" command
1188 def LspServerShowComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1189   return LspServerSubCmdComplete(['capabilities', 'initializeRequest',
1190                                   'messages', 'status'], arglead, cmdline,
1191                                   cursorPos)
1192 enddef
1193
1194 # Command-line completion for the ":LspServer trace" command
1195 def LspServerTraceComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1196   return LspServerSubCmdComplete(['messages', 'off', 'verbose'],
1197                                  arglead, cmdline, cursorPos)
1198 enddef
1199
1200 # Command-line completion for the ":LspServer" command
1201 export def LspServerComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1202   var wordBegin = -1
1203   var wordEnd = -1
1204   var l = ['debug', 'restart', 'show', 'trace']
1205
1206   # Skip the command name
1207   var i = cmdline->stridx(' ', 0)
1208   wordBegin = cmdline->match('\s\+\zs\S', i)
1209   if wordBegin == -1
1210     return l
1211   endif
1212
1213   wordEnd = cmdline->stridx(' ', wordBegin)
1214   if wordEnd == -1
1215     return filter(l, (_, val) => val =~ $'^{arglead}')
1216   endif
1217
1218   var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
1219   if cmd == 'debug'
1220     return LspServerDebugComplete(arglead, cmdline, wordEnd)
1221   elseif cmd == 'restart'
1222   elseif cmd == 'show'
1223     return LspServerShowComplete(arglead, cmdline, wordEnd)
1224   elseif cmd == 'trace'
1225     return LspServerTraceComplete(arglead, cmdline, wordEnd)
1226   endif
1227
1228   return []
1229 enddef
1230
1231 # ":LspServer" command handler
1232 export def LspServerCmd(args: string)
1233   if args->stridx('debug') == 0
1234     if args[5] == ' '
1235       var subcmd = args[6 : ]->trim()
1236       ServerDebug(subcmd)
1237     else
1238       util.ErrMsg('Argument required')
1239     endif
1240   elseif args == 'restart'
1241     RestartServer()
1242   elseif args->stridx('show') == 0
1243     if args[4] == ' '
1244       var subcmd = args[5 : ]->trim()
1245       ShowServer(subcmd)
1246     else
1247       util.ErrMsg('Argument required')
1248     endif
1249   elseif args->stridx('trace') == 0
1250     if args[5] == ' '
1251       var subcmd = args[6 : ]->trim()
1252       ServerTraceSet(subcmd)
1253     else
1254       util.ErrMsg('Argument required')
1255     endif
1256   else
1257     util.ErrMsg($'LspServer - Unsupported argument "{args}"')
1258   endif
1259 enddef
1260
1261 # vim: tabstop=8 shiftwidth=2 softtabstop=2 noexpandtab