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