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