]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/lsp.vim
b2e6baf40176a6d96e38f7f1708bb6f95e2b6921
[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()
341   var bnr: number = expand('<abuf>')->str2nr()
342   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)->filter(
343     (key, lspsrv) => !lspsrv->empty() && lspsrv.running
344   )
345
346   if lspservers->empty()
347     return
348   endif
349
350   for lspserver in lspservers
351     lspserver.didSaveFile(bnr)
352   endfor
353 enddef
354
355 # Called after leaving insert mode. Used to process diag messages (if any)
356 def LspLeftInsertMode(bnr: number)
357   var updatePending: bool = bnr->getbufvar('LspDiagsUpdatePending', false)
358   if !updatePending
359     return
360   endif
361   setbufvar(bnr, 'LspDiagsUpdatePending', false)
362
363   diag.ProcessNewDiags(bnr)
364 enddef
365
366 # Add buffer-local autocmds when attaching a LSP server to a buffer
367 def AddBufLocalAutocmds(lspserver: dict<any>, bnr: number): void
368   var acmds: list<dict<any>> = []
369
370   # file saved notification handler
371   acmds->add({bufnr: bnr,
372               event: 'BufWritePost',
373               group: 'LSPBufferAutocmds',
374               cmd: 'LspSavedFile()'})
375
376   # Update the diagnostics when insert mode is stopped
377   acmds->add({bufnr: bnr,
378               event: 'InsertLeave',
379               group: 'LSPBufferAutocmds',
380               cmd: $'LspLeftInsertMode({bnr})'})
381
382   # Auto highlight all the occurrences of the current keyword
383   if opt.lspOptions.autoHighlight &&
384                         lspserver.isDocumentHighlightProvider
385     acmds->add({bufnr: bnr,
386                 event: 'CursorMoved',
387                 group: 'LSPBufferAutocmds',
388                 cmd: 'call LspDocHighlightClear() | call LspDocHighlight("silent")'})
389   endif
390
391   # Show diagnostics on the status line
392   if opt.lspOptions.showDiagOnStatusLine
393     acmds->add({bufnr: bnr,
394                 event: 'CursorMoved',
395                 group: 'LSPBufferAutocmds',
396                 cmd: 'LspShowCurrentDiagInStatusLine()'})
397   endif
398
399   autocmd_add(acmds)
400 enddef
401
402 def BufferInit(lspserverId: number, bnr: number): void
403   var lspserver = buf.BufLspServerGetById(bnr, lspserverId)
404   if lspserver->empty() || !lspserver.running
405     return
406   endif
407
408   var ftype: string = bnr->getbufvar('&filetype')
409   lspserver.textdocDidOpen(bnr, ftype)
410
411   # add a listener to track changes to this buffer
412   listener_add((_bnr: number, start: number, end: number, added: number, changes: list<dict<number>>) => {
413     lspserver.textdocDidChange(bnr, start, end, added, changes)
414   }, bnr)
415
416   AddBufLocalAutocmds(lspserver, bnr)
417
418   diag.BufferInit(lspserver, bnr)
419   signature.BufferInit(lspserver)
420   inlayhints.BufferInit(lspserver, bnr)
421
422   var allServersReady = true
423   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
424   for lspsrv in lspservers
425     if !lspsrv.ready
426       allServersReady = false
427       break
428     endif
429   endfor
430
431   if allServersReady
432     for lspsrv in lspservers
433       # It's only possible to initialize completion when all server capabilities
434       # are known.
435       var completionServer = buf.BufLspServerGet(bnr, 'completion')
436       if !completionServer->empty() && lspsrv.id == completionServer.id
437         completion.BufferInit(lspsrv, bnr, ftype)
438       endif
439     endfor
440
441     if exists('#User#LspAttached')
442       doautocmd <nomodeline> User LspAttached
443     endif
444   endif
445 enddef
446
447 # A new buffer is opened. If LSP is supported for this buffer, then add it
448 export def AddFile(bnr: number): void
449   if buf.BufHasLspServer(bnr)
450     # LSP server for this buffer is already initialized and running
451     return
452   endif
453
454   # Skip remote files
455   if util.LspUriRemote(bnr->bufname()->fnamemodify(':p'))
456     return
457   endif
458
459   var ftype: string = bnr->getbufvar('&filetype')
460   if ftype->empty()
461     return
462   endif
463   var lspservers: list<dict<any>> = LspGetServers(bnr, ftype)
464   if lspservers->empty()
465     return
466   endif
467   for lspserver in lspservers
468     if !lspserver.running
469       if !lspInitializedOnce
470         LspInitOnce()
471       endif
472       lspserver.startServer(bnr)
473     endif
474     buf.BufLspServerSet(bnr, lspserver)
475
476     if lspserver.ready
477       BufferInit(lspserver.id, bnr)
478     else
479       augroup LSPBufferAutocmds
480         exe $'autocmd User LspServerReady_{lspserver.id} ++once BufferInit({lspserver.id}, {bnr})'
481       augroup END
482     endif
483   endfor
484 enddef
485
486 # Notify LSP server to remove a file
487 export def RemoveFile(bnr: number): void
488   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
489   for lspserver in lspservers->copy()
490     if lspserver->empty()
491       continue
492     endif
493     if lspserver.running
494       lspserver.textdocDidClose(bnr)
495     endif
496     diag.DiagRemoveFile(bnr)
497     buf.BufLspServerRemove(bnr, lspserver)
498   endfor
499 enddef
500
501 # Buffer 'bnr' is loaded in a window, send the latest buffer contents to the
502 # language servers.
503 export def BufferLoadedInWin(bnr: number)
504   var lspservers: list<dict<any>> = buf.BufLspServersGet(bnr)
505   if lspservers->empty()
506     # No language servers for this buffer
507     return
508   endif
509   for lspserver in lspservers
510     if !lspserver->empty() && lspserver.ready
511       lspserver.textdocDidChange(bnr, 0, 0, 0, [])
512     endif
513   endfor
514   # Refresh the displayed diags visuals
515   if opt.lspOptions.autoHighlightDiags
516     diag.DiagsRefresh(bnr)
517   endif
518 enddef
519
520 # Stop all the LSP servers
521 export def StopAllServers()
522   for lspserver in LSPServers
523     if lspserver.running
524       lspserver.stopServer()
525     endif
526   endfor
527 enddef
528
529 # Add all the buffers with 'filetype' set to "ftype" to the language server.
530 def AddBuffersToLsp(ftype: string)
531   # Add all the buffers with the same file type as the current buffer
532   for binfo in getbufinfo({bufloaded: 1})
533     if binfo.bufnr->getbufvar('&filetype') == ftype
534       AddFile(binfo.bufnr)
535     endif
536   endfor
537 enddef
538
539 # Restart the LSP server for the current buffer
540 def RestartServer()
541   var lspservers: list<dict<any>> = buf.CurbufGetServers()
542   if lspservers->empty()
543     util.WarnMsg($'No Lsp servers found for "{@%}"')
544     return
545   endif
546
547   # Remove all the buffers with the same file type as the current buffer
548   var ftype: string = &filetype
549   for binfo in getbufinfo()
550     if binfo.bufnr->getbufvar('&filetype') == ftype
551       RemoveFile(binfo.bufnr)
552     endif
553   endfor
554
555   for lspserver in lspservers
556     # Stop the server (if running)
557     if lspserver.running
558       lspserver.stopServer()
559     endif
560
561     # Start the server again
562     lspserver.startServer(bufnr())
563   endfor
564
565   AddBuffersToLsp(ftype)
566 enddef
567
568 # Add the LSP server for files with 'filetype' as "ftype".
569 def AddServerForFiltype(lspserver: dict<any>, ftype: string, omnicompl: bool)
570   LspAddServer(ftype, lspserver)
571   completion.OmniComplSet(ftype, omnicompl)
572
573   # If a buffer of this file type is already present, then send it to the LSP
574   # server now.
575   AddBuffersToLsp(ftype)
576 enddef
577
578 # Register a LSP server for one or more file types
579 export def AddServer(serverList: list<dict<any>>)
580   for server in serverList
581     if !server->has_key('filetype') || !server->has_key('path')
582       util.ErrMsg('LSP server information is missing filetype or path')
583       continue
584     endif
585     # Enable omni-completion by default
586     server.omnicompl = server->get('omnicompl', true)
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 # Display the diagnostics for the current line in the status line.
754 export def LspShowCurrentDiagInStatusLine()
755   var fname: string = @%
756   if fname->empty()
757     return
758   endif
759
760   diag.ShowCurrentDiagInStatusLine()
761 enddef
762
763 # get the count of diagnostics in the current buffer
764 export def ErrorCount(): dict<number>
765   var res = {Error: 0, Warn: 0, Info: 0, Hint: 0}
766   var fname: string = @%
767   if fname->empty()
768     return res
769   endif
770
771   return diag.DiagsGetErrorCount(bufnr())
772 enddef
773
774 # jump to the next/previous/first diagnostic message in the current buffer
775 export def JumpToDiag(which: string, count: number = 0): void
776   diag.LspDiagsJump(which, count)
777 enddef
778
779 # Display the hover message from the LSP server for the current cursor
780 # location
781 export def Hover(cmdmods: string)
782   var lspserver: dict<any> = buf.CurbufGetServerChecked('hover')
783   if lspserver->empty()
784     return
785   endif
786
787   lspserver.hover(cmdmods)
788 enddef
789
790 # Enable or disable inlay hints
791 export def InlayHints(ctl: string)
792   if ctl == 'enable'
793     inlayhints.InlayHintsEnable()
794   elseif ctl == 'disable'
795     inlayhints.InlayHintsDisable()
796   else
797     util.ErrMsg($'LspInlayHints - Unsupported argument "{ctl}"')
798   endif
799 enddef
800
801 # Command-line completion for the ":LspInlayHints" command
802 export def LspInlayHintsComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
803   var l = ['enable', 'disable']
804   return filter(l, (_, val) => val =~ $'^{arglead}')
805 enddef
806
807 # show symbol references
808 export def ShowReferences(peek: bool)
809   var lspserver: dict<any> = buf.CurbufGetServerChecked('references')
810   if lspserver->empty()
811     return
812   endif
813
814   lspserver.showReferences(peek)
815 enddef
816
817 # highlight all the places where a symbol is referenced
818 def g:LspDocHighlight(cmdmods: string = '')
819   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
820   if lspserver->empty()
821     return
822   endif
823
824   lspserver.docHighlight(cmdmods)
825 enddef
826
827 # clear the symbol reference highlight
828 def g:LspDocHighlightClear()
829   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentHighlight')
830   if lspserver->empty()
831     return
832   endif
833
834   if has('patch-9.0.0233')
835     prop_remove({types: ['LspTextRef', 'LspReadRef', 'LspWriteRef'], all: true})
836   else
837     prop_remove({type: 'LspTextRef', all: true})
838     prop_remove({type: 'LspReadRef', all: true})
839     prop_remove({type: 'LspWriteRef', all: true})
840   endif
841 enddef
842
843 def g:LspRequestDocSymbols()
844   if outline.SkipOutlineRefresh()
845     return
846   endif
847
848   var fname: string = @%
849   if fname->empty()
850     return
851   endif
852
853   var lspserver: dict<any> = buf.CurbufGetServer('documentSymbol')
854   if lspserver->empty() || !lspserver.running || !lspserver.ready
855     return
856   endif
857
858   lspserver.getDocSymbols(fname, true)
859 enddef
860
861 # open a window and display all the symbols in a file (outline)
862 export def Outline(cmdmods: string, winsize: number)
863   var fname: string = @%
864   if fname->empty()
865     return
866   endif
867
868   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
869   if lspserver->empty() || !lspserver.running || !lspserver.ready
870     return
871   endif
872
873   outline.OpenOutlineWindow(cmdmods, winsize)
874   g:LspRequestDocSymbols()
875 enddef
876
877 # show all the symbols in a file in a popup menu
878 export def ShowDocSymbols()
879   var fname: string = @%
880   if fname->empty()
881     return
882   endif
883
884   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentSymbol')
885   if lspserver->empty() || !lspserver.running || !lspserver.ready
886     return
887   endif
888
889   lspserver.getDocSymbols(fname, false)
890 enddef
891
892 # Format the entire file
893 export def TextDocFormat(range_args: number, line1: number, line2: number)
894   if !&modifiable
895     util.ErrMsg('Current file is not a modifiable file')
896     return
897   endif
898
899   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
900   if lspserver->empty()
901     return
902   endif
903
904   var fname: string = @%
905   if range_args > 0
906     lspserver.textDocFormat(fname, true, line1, line2)
907   else
908     lspserver.textDocFormat(fname, false, 0, 0)
909   endif
910 enddef
911
912 # TODO: Add support for textDocument.onTypeFormatting?
913 # Will this slow down Vim?
914
915 # Display all the locations where the current symbol is called from.
916 # Uses LSP "callHierarchy/incomingCalls" request
917 export def IncomingCalls()
918   var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
919   if lspserver->empty()
920     return
921   endif
922
923   lspserver.incomingCalls(@%)
924 enddef
925
926 # Display all the symbols used by the current symbol.
927 # Uses LSP "callHierarchy/outgoingCalls" request
928 export def OutgoingCalls()
929   var lspserver: dict<any> = buf.CurbufGetServerChecked('callHierarchy')
930   if lspserver->empty()
931     return
932   endif
933
934   lspserver.outgoingCalls(@%)
935 enddef
936
937 # Display the type hierarchy for the current symbol.  Direction is 0 for
938 # sub types and 1 for super types.
939 export def TypeHierarchy(direction: number)
940   var lspserver: dict<any> = buf.CurbufGetServerChecked('typeHierarchy')
941   if lspserver->empty()
942     return
943   endif
944
945   lspserver.typeHierarchy(direction)
946 enddef
947
948 # Rename a symbol
949 # Uses LSP "textDocument/rename" request
950 export def Rename(a_newName: string)
951   var lspserver: dict<any> = buf.CurbufGetServerChecked('rename')
952   if lspserver->empty()
953     return
954   endif
955
956   var newName: string = a_newName
957   if newName->empty()
958     var sym: string = expand('<cword>')
959     newName = input($"Rename symbol '{sym}' to: ", sym)
960     if newName->empty()
961       return
962     endif
963
964     # clear the input prompt
965     :echo "\r"
966   endif
967
968   lspserver.renameSymbol(newName)
969 enddef
970
971 # Perform a code action
972 # Uses LSP "textDocument/codeAction" request
973 export def CodeAction(line1: number, line2: number, query: string)
974   var lspserver: dict<any> = buf.CurbufGetServerChecked('codeAction')
975   if lspserver->empty()
976     return
977   endif
978
979   var fname: string = @%
980   lspserver.codeAction(fname, line1, line2, query)
981 enddef
982
983 # Code lens
984 # Uses LSP "textDocument/codeLens" request
985 export def CodeLens()
986   var lspserver: dict<any> = buf.CurbufGetServerChecked('codeLens')
987   if lspserver->empty()
988     return
989   endif
990
991   lspserver.codeLens(@%)
992 enddef
993
994 # Perform a workspace wide symbol lookup
995 # Uses LSP "workspace/symbol" request
996 export def SymbolSearch(queryArg: string, cmdmods: string)
997   var lspserver: dict<any> = buf.CurbufGetServerChecked('workspaceSymbol')
998   if lspserver->empty()
999     return
1000   endif
1001
1002   var query: string = queryArg
1003   if query->empty()
1004     query = input('Lookup symbol: ', expand('<cword>'))
1005     if query->empty()
1006       return
1007     endif
1008   endif
1009   :redraw!
1010
1011   lspserver.workspaceQuery(query, true, cmdmods)
1012 enddef
1013
1014 # Display the list of workspace folders
1015 export def ListWorkspaceFolders()
1016   var lspservers: list<dict<any>> = buf.CurbufGetServers()
1017   for lspserver in lspservers
1018     util.InfoMsg($'Workspace Folders: "{lspserver.name}" {lspserver.workspaceFolders->string()}')
1019   endfor
1020 enddef
1021
1022 # Add a workspace folder. Default is to use the current folder.
1023 export def AddWorkspaceFolder(dirArg: string)
1024   var dirName: string = dirArg
1025   if dirName->empty()
1026     dirName = input('Add Workspace Folder: ', getcwd(), 'dir')
1027     if dirName->empty()
1028       return
1029     endif
1030   endif
1031   :redraw!
1032   if !dirName->isdirectory()
1033     util.ErrMsg($'{dirName} is not a directory')
1034     return
1035   endif
1036
1037   var lspservers: list<dict<any>> = buf.CurbufGetServers()
1038
1039   for lspserver in lspservers
1040     lspserver.addWorkspaceFolder(dirName)
1041   endfor
1042 enddef
1043
1044 # Remove a workspace folder. Default is to use the current folder.
1045 export def RemoveWorkspaceFolder(dirArg: string)
1046   var dirName: string = dirArg
1047   if dirName->empty()
1048     dirName = input('Remove Workspace Folder: ', getcwd(), 'dir')
1049     if dirName->empty()
1050       return
1051     endif
1052   endif
1053   :redraw!
1054   if !dirName->isdirectory()
1055     util.ErrMsg($'{dirName} is not a directory')
1056     return
1057   endif
1058
1059   var lspservers: list<dict<any>> = buf.CurbufGetServers()
1060   for lspserver in lspservers
1061     lspserver.removeWorkspaceFolder(dirName)
1062   endfor
1063 enddef
1064
1065 # expand the previous selection or start a new selection
1066 export def SelectionExpand()
1067   var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
1068   if lspserver->empty()
1069     return
1070   endif
1071
1072   lspserver.selectionExpand()
1073 enddef
1074
1075 # shrink the previous selection or start a new selection
1076 export def SelectionShrink()
1077   var lspserver: dict<any> = buf.CurbufGetServerChecked('selectionRange')
1078   if lspserver->empty()
1079     return
1080   endif
1081
1082   lspserver.selectionShrink()
1083 enddef
1084
1085 # fold the entire document
1086 export def FoldDocument()
1087   var lspserver: dict<any> = buf.CurbufGetServerChecked('foldingRange')
1088   if lspserver->empty()
1089     return
1090   endif
1091
1092   if &foldmethod != 'manual'
1093     util.ErrMsg("Only works when 'foldmethod' is 'manual'")
1094     return
1095   endif
1096
1097   var fname: string = @%
1098   lspserver.foldRange(fname)
1099 enddef
1100
1101 # Enable diagnostic highlighting for all the buffers
1102 export def DiagHighlightEnable()
1103   diag.DiagsHighlightEnable()
1104 enddef
1105
1106 # Disable diagnostic highlighting for all the buffers
1107 export def DiagHighlightDisable()
1108   diag.DiagsHighlightDisable()
1109 enddef
1110
1111 # Function to use with the 'tagfunc' option.
1112 export def TagFunc(pat: string, flags: string, info: dict<any>): any
1113   var lspserver: dict<any> = buf.CurbufGetServerChecked('definition')
1114   if lspserver->empty()
1115     return v:null
1116   endif
1117
1118   return lspserver.tagFunc(pat, flags, info)
1119 enddef
1120
1121 # Function to use with the 'formatexpr' option.
1122 export def FormatExpr(): number
1123   var lspserver: dict<any> = buf.CurbufGetServerChecked('documentFormatting')
1124   if lspserver->empty()
1125     return 1
1126   endif
1127
1128   lspserver.textDocFormat(@%, true, v:lnum, v:lnum + v:count - 1)
1129   return 0
1130 enddef
1131
1132 export def RegisterCmdHandler(cmd: string, Handler: func)
1133   codeaction.RegisterCmdHandler(cmd, Handler)
1134 enddef
1135
1136 # Command-line completion for the ":LspServer <cmd>" and ":LspDiag <cmd>" sub
1137 # commands
1138 def LspSubCmdComplete(cmds: list<string>, arglead: string, cmdline: string, cursorPos: number): list<string>
1139   var wordBegin = cmdline->match('\s\+\zs\S', cursorPos)
1140   if wordBegin == -1
1141     return cmds
1142   endif
1143
1144   # Make sure there are no additional sub-commands
1145   var wordEnd = cmdline->stridx(' ', wordBegin)
1146   if wordEnd == -1
1147     return cmds->filter((_, val) => val =~ $'^{arglead}')
1148   endif
1149
1150   return []
1151 enddef
1152
1153 # Command-line completion for the ":LspDiag highlight" command
1154 def LspDiagHighlightComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1155   return LspSubCmdComplete(['enable', 'disable'], arglead, cmdline, cursorPos)
1156 enddef
1157
1158 # Command-line completion for the ":LspDiag" command
1159 export def LspDiagComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1160   var wordBegin = -1
1161   var wordEnd = -1
1162   var l = ['first', 'current', 'here', 'highlight', 'last', 'next', 'prev',
1163            'show']
1164
1165   # Skip the command name
1166   var i = cmdline->stridx(' ', 0)
1167   wordBegin = cmdline->match('\s\+\zs\S', i)
1168   if wordBegin == -1
1169     return l
1170   endif
1171
1172   wordEnd = cmdline->stridx(' ', wordBegin)
1173   if wordEnd == -1
1174     return filter(l, (_, val) => val =~ $'^{arglead}')
1175   endif
1176
1177   var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
1178   if cmd == 'highlight'
1179     return LspDiagHighlightComplete(arglead, cmdline, wordEnd)
1180   endif
1181
1182   return []
1183 enddef
1184
1185 # ":LspDiag" command handler
1186 export def LspDiagCmd(args: string, cmdCount: number, force: bool)
1187   if args->stridx('highlight') == 0
1188     if args[9] == ' '
1189       var subcmd = args[10 : ]->trim()
1190       if subcmd == 'enable'
1191         diag.DiagsHighlightEnable()
1192       elseif subcmd == 'disable'
1193         diag.DiagsHighlightDisable()
1194       else
1195         util.ErrMsg($':LspDiag highlight - Unsupported argument "{subcmd}"')
1196       endif
1197     else
1198       util.ErrMsg('Argument required for ":LspDiag highlight"')
1199     endif
1200   elseif args == 'first'
1201     diag.LspDiagsJump('first', 0)
1202   elseif args == 'current'
1203     LspShowCurrentDiag(force)
1204   elseif args == 'here'
1205     diag.LspDiagsJump('here', 0)
1206   elseif args == 'last'
1207     diag.LspDiagsJump('last', 0)
1208   elseif args == 'next'
1209     diag.LspDiagsJump('next', cmdCount)
1210   elseif args == 'prev'
1211     diag.LspDiagsJump('prev', cmdCount)
1212   elseif args == 'show'
1213     ShowDiagnostics()
1214   else
1215     util.ErrMsg($':LspDiag - Unsupported argument "{args}"')
1216   endif
1217 enddef
1218
1219 # Command-line completion for the ":LspServer debug" command
1220 def LspServerDebugComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1221   return LspSubCmdComplete(['errors', 'messages', 'off', 'on'], arglead,
1222                            cmdline, cursorPos)
1223 enddef
1224
1225 # Command-line completion for the ":LspServer show" command
1226 def LspServerShowComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1227   return LspSubCmdComplete(['capabilities', 'initializeRequest', 'messages',
1228                             'status'], arglead, cmdline, cursorPos)
1229 enddef
1230
1231 # Command-line completion for the ":LspServer trace" command
1232 def LspServerTraceComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1233   return LspSubCmdComplete(['messages', 'off', 'verbose'], arglead, cmdline,
1234                            cursorPos)
1235 enddef
1236
1237 # Command-line completion for the ":LspServer" command
1238 export def LspServerComplete(arglead: string, cmdline: string, cursorPos: number): list<string>
1239   var wordBegin = -1
1240   var wordEnd = -1
1241   var l = ['debug', 'restart', 'show', 'trace']
1242
1243   # Skip the command name
1244   var i = cmdline->stridx(' ', 0)
1245   wordBegin = cmdline->match('\s\+\zs\S', i)
1246   if wordBegin == -1
1247     return l
1248   endif
1249
1250   wordEnd = cmdline->stridx(' ', wordBegin)
1251   if wordEnd == -1
1252     return filter(l, (_, val) => val =~ $'^{arglead}')
1253   endif
1254
1255   var cmd = cmdline->strpart(wordBegin, wordEnd - wordBegin)
1256   if cmd == 'debug'
1257     return LspServerDebugComplete(arglead, cmdline, wordEnd)
1258   elseif cmd == 'restart'
1259   elseif cmd == 'show'
1260     return LspServerShowComplete(arglead, cmdline, wordEnd)
1261   elseif cmd == 'trace'
1262     return LspServerTraceComplete(arglead, cmdline, wordEnd)
1263   endif
1264
1265   return []
1266 enddef
1267
1268 # ":LspServer" command handler
1269 export def LspServerCmd(args: string)
1270   if args->stridx('debug') == 0
1271     if args[5] == ' '
1272       var subcmd = args[6 : ]->trim()
1273       ServerDebug(subcmd)
1274     else
1275       util.ErrMsg('Argument required for ":LspServer debug"')
1276     endif
1277   elseif args == 'restart'
1278     RestartServer()
1279   elseif args->stridx('show') == 0
1280     if args[4] == ' '
1281       var subcmd = args[5 : ]->trim()
1282       ShowServer(subcmd)
1283     else
1284       util.ErrMsg('Argument required for ":LspServer show"')
1285     endif
1286   elseif args->stridx('trace') == 0
1287     if args[5] == ' '
1288       var subcmd = args[6 : ]->trim()
1289       ServerTraceSet(subcmd)
1290     else
1291       util.ErrMsg('Argument required for ":LspServer trace"')
1292     endif
1293   else
1294     util.ErrMsg($'LspServer - Unsupported argument "{args}"')
1295   endif
1296 enddef
1297
1298 # vim: tabstop=8 shiftwidth=2 softtabstop=2 noexpandtab