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