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