]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/diag.vim
9eaa30c07f25713f05516c336e9ed4926d2e1dd8
[vim-lsp.git] / autoload / lsp / diag.vim
1 vim9script
2
3 # Functions related to handling LSP diagnostics.
4
5 import './options.vim' as opt
6 import './buffer.vim' as buf
7 import './util.vim'
8
9 # [bnr] = {
10 #   serverDiagnostics: {
11 #     lspServer1Id: [diag, diag, diag]
12 #     lspServer2Id: [diag, diag, diag]
13 #   },
14 #   serverDiagnosticsByLnum: {
15 #     lspServer1Id: { [lnum]: [diag, diag diag] },
16 #     lspServer2Id: { [lnum]: [diag, diag diag] },
17 #   },
18 #   sortedDiagnostics: [lspServer1.diags, ...lspServer2.diags]->sort()
19 # }
20 var diagsMap: dict<dict<any>> = {}
21
22 # Initialize the signs and the text property type used for diagnostics.
23 export def InitOnce()
24   # Signs used for LSP diagnostics
25   hlset([{name: 'LspDiagLine', default: true, linksto: 'DiffAdd'}])
26   hlset([{name: 'LspDiagSignErrorText', default: true, linksto: 'ErrorMsg'}])
27   hlset([{name: 'LspDiagSignWarningText', default: true, linksto: 'Search'}])
28   hlset([{name: 'LspDiagSignInfoText', default: true, linksto: 'Pmenu'}])
29   hlset([{name: 'LspDiagSignHintText', default: true, linksto: 'Question'}])
30   sign_define([
31     {
32       name: 'LspDiagError',
33       text: opt.lspOptions.diagSignErrorText,
34       texthl: 'LspDiagSignErrorText',
35       linehl: 'LspDiagLine'
36     },
37     {
38       name: 'LspDiagWarning',
39       text: opt.lspOptions.diagSignWarningText,
40       texthl: 'LspDiagSignWarningText',
41       linehl: 'LspDiagLine'
42     },
43     {
44       name: 'LspDiagInfo',
45       text: opt.lspOptions.diagSignInfoText,
46       texthl: 'LspDiagSignInfoText',
47       linehl: 'LspDiagLine'
48     },
49     {
50       name: 'LspDiagHint',
51       text: opt.lspOptions.diagSignHintText,
52       texthl: 'LspDiagSignHintText',
53       linehl: 'LspDiagLine'
54     }
55   ])
56
57   hlset([{name: 'LspDiagInlineError', default: true, linksto: 'SpellBad'}])
58   hlset([{name: 'LspDiagInlineWarning', default: true, linksto: 'SpellCap'}])
59   hlset([{name: 'LspDiagInlineInfo', default: true, linksto: 'SpellRare'}])
60   hlset([{name: 'LspDiagInlineHint', default: true, linksto: 'SpellLocal'}])
61
62   var override = &cursorline
63       && &cursorlineopt =~ '\<line\>\|\<screenline\>\|\<both\>'
64
65   prop_type_add('LspDiagInlineError',
66                       { highlight: 'LspDiagInlineError',
67                         priority: 10,
68                         override: override })
69   prop_type_add('LspDiagInlineWarning',
70                       { highlight: 'LspDiagInlineWarning',
71                         priority: 9,
72                         override: override })
73   prop_type_add('LspDiagInlineInfo',
74                       { highlight: 'LspDiagInlineInfo',
75                         priority: 8,
76                         override: override })
77   prop_type_add('LspDiagInlineHint',
78                       { highlight: 'LspDiagInlineHint',
79                         priority: 7,
80                         override: override })
81
82   hlset([{name: 'LspDiagVirtualText', default: true, linksto: 'LineNr'}])
83   prop_type_add('LspDiagVirtualText', {highlight: 'LspDiagVirtualText',
84                                        override: true})
85
86   if opt.lspOptions.aleSupport
87     autocmd_add([{group: 'LspAleCmds', event: 'User', pattern: 'ALEWantResults', cmd: 'AleHook(g:ale_want_results_buffer)'}])
88   endif
89 enddef
90
91 # Sort diagnostics ascending based on line and character offset
92 def SortDiags(diags: list<dict<any>>): list<dict<any>>
93   return diags->sort((a, b) => {
94     var linediff = a.range.start.line - b.range.start.line
95     if linediff == 0
96       return a.range.start.character - b.range.start.character
97     endif
98     return linediff
99   })
100 enddef
101
102 # Remove the diagnostics stored for buffer 'bnr'
103 export def DiagRemoveFile(bnr: number)
104   if diagsMap->has_key(bnr)
105     diagsMap->remove(bnr)
106   endif
107 enddef
108
109 def DiagSevToSignName(severity: number): string
110   var typeMap: list<string> = ['LspDiagError', 'LspDiagWarning',
111                                                 'LspDiagInfo', 'LspDiagHint']
112   if severity > 4
113     return 'LspDiagHint'
114   endif
115   return typeMap[severity - 1]
116 enddef
117
118 def DiagSevToInlineHLName(severity: number): string
119   var typeMap: list<string> = [
120     'LspDiagInlineError',
121     'LspDiagInlineWarning',
122     'LspDiagInlineInfo',
123     'LspDiagInlineHint'
124   ]
125   if severity > 4
126     return 'LspDiagInlineHint'
127   endif
128   return typeMap[severity - 1]
129 enddef
130
131 def DiagSevToSymbolText(severity: number): string
132   var typeMap: list<string> = [
133     opt.lspOptions.diagSignErrorText,
134     opt.lspOptions.diagSignWarningText,
135     opt.lspOptions.diagSignInfoText,
136     opt.lspOptions.diagSignHintText
137   ]
138   if severity > 4
139     return opt.lspOptions.diagSignHintText
140   endif
141   return typeMap[severity - 1]
142 enddef
143
144 # Remove signs and text properties for diagnostics in buffer
145 def RemoveDiagVisualsForBuffer(bnr: number)
146   # Remove all the existing diagnostic signs
147   sign_unplace('LSPDiag', {buffer: bnr})
148
149   if opt.lspOptions.showDiagWithVirtualText
150     # Remove all the existing virtual text
151     prop_remove({type: 'LspDiagVirtualText', bufnr: bnr, all: true})
152   endif
153
154   if opt.lspOptions.highlightDiagInline
155     # Remove all the existing virtual text
156     prop_remove({type: 'LspDiagInlineError', bufnr: bnr, all: true})
157     prop_remove({type: 'LspDiagInlineWarning', bufnr: bnr, all: true})
158     prop_remove({type: 'LspDiagInlineInfo', bufnr: bnr, all: true})
159     prop_remove({type: 'LspDiagInlineHint', bufnr: bnr, all: true})
160   endif
161 enddef
162
163 # Refresh the placed diagnostics in buffer 'bnr'
164 # This inline signs, inline props, and virtual text diagnostics
165 def DiagsRefresh(bnr: number)
166   bnr->bufload()
167
168   RemoveDiagVisualsForBuffer(bnr)
169
170   if !diagsMap->has_key(bnr) ||
171       diagsMap[bnr].sortedDiagnostics->empty()
172     return
173   endif
174
175   # Initialize default/fallback properties for diagnostic virtual text:
176   var diag_align: string = 'above'
177   var diag_wrap: string = 'truncate'
178   var diag_symbol: string = '┌─'
179
180   if opt.lspOptions.diagVirtualTextAlign == 'below'
181     diag_align = 'below'
182     diag_wrap = 'truncate'
183     diag_symbol = '└─'
184   elseif opt.lspOptions.diagVirtualTextAlign == 'after'
185     diag_align = 'after'
186     diag_wrap = 'wrap'
187     diag_symbol = 'E>'
188   endif
189
190   var signs: list<dict<any>> = []
191   var diags: list<dict<any>> = diagsMap[bnr].sortedDiagnostics
192   for diag in diags
193     # TODO: prioritize most important severity if there are multiple diagnostics
194     # from the same line
195     var lnum = diag.range.start.line + 1
196     signs->add({id: 0, buffer: bnr, group: 'LSPDiag',
197                                 lnum: lnum,
198                                 name: DiagSevToSignName(diag.severity),
199                                 priority: 10 - diag.severity})
200
201     try
202       if opt.lspOptions.highlightDiagInline
203         prop_add(diag.range.start.line + 1,
204                   util.GetLineByteFromPos(bnr, diag.range.start) + 1,
205                   {end_lnum: diag.range.end.line + 1,
206                     end_col: util.GetLineByteFromPos(bnr, diag.range.end) + 1,
207                     bufnr: bnr,
208                     type: DiagSevToInlineHLName(diag.severity)})
209       endif
210
211       if opt.lspOptions.showDiagWithVirtualText
212
213         var padding: number
214         var symbol: string = diag_symbol
215
216         if diag_align == 'after'
217           padding = 3
218           symbol = DiagSevToSymbolText(diag.severity)
219         else
220           var charIdx = util.GetCharIdxWithoutCompChar(bnr, diag.range.start)
221           padding = charIdx
222           if padding > 0
223             padding = strdisplaywidth(getline(diag.range.start.line + 1)[ : charIdx - 1])
224           endif
225         endif
226
227         prop_add(lnum, 0, {bufnr: bnr,
228                            type: 'LspDiagVirtualText',
229                            text: $'{symbol} {diag.message}',
230                            text_align: diag_align,
231                            text_wrap: diag_wrap,
232                            text_padding_left: padding})
233       endif
234     catch /E966\|E964/ # Invalid lnum | Invalid col
235       # Diagnostics arrive asynchronous and the document changed while they wore
236       # send. Ignore this as new once will arrive shortly.
237     endtry
238   endfor
239
240   signs->sign_placelist()
241 enddef
242
243 # Sends diagnostics to Ale
244 def SendAleDiags(bnr: number, timerid: number)
245   if !diagsMap->has_key(bnr)
246     return
247   endif
248
249   # Conver to Ale's diagnostics format (:h ale-loclist-format)
250   ale#other_source#ShowResults(bnr, 'lsp', diagsMap[bnr].sortedDiagnostics->mapnew((_, v) => {
251      return {text: v.message,
252              lnum: v.range.start.line + 1,
253              col: util.GetLineByteFromPos(bnr, v.range.start) + 1,
254              end_lnum: v.range.end.line + 1,
255              end_col: util.GetLineByteFromPos(bnr, v.range.end) + 1,
256              type: "EWIH"[v.severity - 1]}
257   }))
258 enddef
259
260 # Hook called when Ale wants to retrieve new diagnostics
261 def AleHook(bnr: number)
262   ale#other_source#StartChecking(bnr, 'lsp')
263   timer_start(0, function('SendAleDiags', [bnr]))
264 enddef
265
266 # New LSP diagnostic messages received from the server for a file.
267 # Update the signs placed in the buffer for this file
268 export def ProcessNewDiags(bnr: number)
269   if opt.lspOptions.autoPopulateDiags
270     DiagsUpdateLocList(bnr)
271   endif
272
273   if opt.lspOptions.aleSupport
274     SendAleDiags(bnr, -1)
275     return
276   elseif !opt.lspOptions.autoHighlightDiags
277     return
278   endif
279
280   if bnr == -1 || !diagsMap->has_key(bnr)
281     return
282   endif
283
284   var curmode: string = mode()
285   if curmode == 'i' || curmode == 'R' || curmode == 'Rv'
286     # postpone placing signs in insert mode and replace mode. These will be
287     # placed after the user returns to Normal mode.
288     b:LspDiagsUpdatePending = true
289     return
290   endif
291
292   DiagsRefresh(bnr)
293 enddef
294
295 # process a diagnostic notification message from the LSP server
296 # Notification: textDocument/publishDiagnostics
297 # Param: PublishDiagnosticsParams
298 export def DiagNotification(lspserver: dict<any>, uri: string, diags_arg: list<dict<any>>): void
299   # Diagnostics are disabled for this server
300   if lspserver.features->has_key('diagnostics') && !lspserver.features.diagnostics
301     return
302   endif
303
304   var fname: string = util.LspUriToFile(uri)
305   var bnr: number = fname->bufnr()
306   if bnr == -1
307     # Is this condition possible?
308     return
309   endif
310
311   var newDiags: list<dict<any>> = diags_arg
312
313   if lspserver.needOffsetEncoding
314     # Decode the position encoding in all the diags
315     newDiags->map((_, dval) => {
316         lspserver.decodeRange(bnr, dval.range)
317         return dval
318       })
319   endif
320
321   if lspserver.processDiagHandler != null_function
322     newDiags = lspserver.processDiagHandler(diags_arg)
323   endif
324
325   # TODO: Is the buffer (bnr) always a loaded buffer? Should we load it here?
326   var lastlnum: number = bnr->getbufinfo()[0].linecount
327
328   # store the diagnostic for each line separately
329   var diagsByLnum: dict<list<dict<any>>> = {}
330
331   var diagWithinRange: list<dict<any>> = []
332   for diag in newDiags
333     if diag.range.start.line + 1 > lastlnum
334       # Make sure the line number is a valid buffer line number
335       diag.range.start.line = lastlnum - 1
336     endif
337
338     var lnum = diag.range.start.line + 1
339     if !diagsByLnum->has_key(lnum)
340       diagsByLnum[lnum] = []
341     endif
342     diagsByLnum[lnum]->add(diag)
343
344     diagWithinRange->add(diag)
345   endfor
346
347   var serverDiags: dict<list<any>> = diagsMap->has_key(bnr) ?
348       diagsMap[bnr].serverDiagnostics : {}
349   serverDiags[lspserver.id] = diagWithinRange
350
351   var serverDiagsByLnum: dict<dict<list<any>>> = diagsMap->has_key(bnr) ?
352       diagsMap[bnr].serverDiagnosticsByLnum : {}
353   serverDiagsByLnum[lspserver.id] = diagsByLnum
354
355   # store the diagnostic for each line separately
356   var joinedServerDiags: list<dict<any>> = []
357   for diags in serverDiags->values()
358     for diag in diags
359       joinedServerDiags->add(diag)
360     endfor
361   endfor
362
363   var sortedDiags = SortDiags(joinedServerDiags)
364
365   diagsMap[bnr] = {
366     sortedDiagnostics: sortedDiags,
367     serverDiagnosticsByLnum: serverDiagsByLnum,
368     serverDiagnostics: serverDiags
369   }
370
371   ProcessNewDiags(bnr)
372
373   # Notify user scripts that diags has been updated
374   if exists('#User#LspDiagsUpdated')
375     :doautocmd <nomodeline> User LspDiagsUpdated
376   endif
377 enddef
378
379 # get the count of error in the current buffer
380 export def DiagsGetErrorCount(): dict<number>
381   var errCount = 0
382   var warnCount = 0
383   var infoCount = 0
384   var hintCount = 0
385
386   var bnr: number = bufnr()
387   if diagsMap->has_key(bnr)
388     var diags = diagsMap[bnr].sortedDiagnostics
389     for diag in diags
390       var severity = diag->get('severity', -1)
391       if severity == 1
392         errCount += 1
393       elseif severity == 2
394         warnCount += 1
395       elseif severity == 3
396         infoCount += 1
397       elseif severity == 4
398         hintCount += 1
399       endif
400     endfor
401   endif
402
403   return {Error: errCount, Warn: warnCount, Info: infoCount, Hint: hintCount}
404 enddef
405
406 # Map the LSP DiagnosticSeverity to a quickfix type character
407 def DiagSevToQfType(severity: number): string
408   var typeMap: list<string> = ['E', 'W', 'I', 'N']
409
410   if severity > 4
411     return ''
412   endif
413
414   return typeMap[severity - 1]
415 enddef
416
417 # Update the location list window for the current window with the diagnostic
418 # messages.
419 # Returns true if diagnostics is not empty and false if it is empty.
420 def DiagsUpdateLocList(bnr: number): bool
421   var fname: string = bnr->bufname()->fnamemodify(':p')
422   if fname->empty()
423     return false
424   endif
425
426   var LspQfId: number = bnr->getbufvar('LspQfId', 0)
427   if !LspQfId->empty() && getloclist(0, {id: LspQfId}).id != LspQfId
428     LspQfId = 0
429   endif
430
431   if !diagsMap->has_key(bnr) ||
432       diagsMap[bnr].sortedDiagnostics->empty()
433     if LspQfId != 0
434       setloclist(0, [], 'r', {id: LspQfId, items: []})
435     endif
436     return false
437   endif
438
439   var qflist: list<dict<any>> = []
440   var text: string
441
442   var diags = diagsMap[bnr].sortedDiagnostics
443   for diag in diags
444     text = diag.message->substitute("\n\\+", "\n", 'g')
445     qflist->add({filename: fname,
446                     lnum: diag.range.start.line + 1,
447                     col: util.GetLineByteFromPos(bnr, diag.range.start) + 1,
448                     end_lnum: diag.range.end.line + 1,
449                     end_col: util.GetLineByteFromPos(bnr, diag.range.end) + 1,
450                     text: text,
451                     type: DiagSevToQfType(diag.severity)})
452   endfor
453
454   var op: string = ' '
455   var props = {title: 'Language Server Diagnostics', items: qflist}
456   if LspQfId != 0
457     op = 'r'
458     props.id = LspQfId
459   endif
460   setloclist(0, [], op, props)
461   if LspQfId == 0
462     setbufvar(bnr, 'LspQfId', getloclist(0, {id: 0}).id)
463   endif
464
465   return true
466 enddef
467
468 # Display the diagnostic messages from the LSP server for the current buffer
469 # in a location list
470 export def ShowAllDiags(): void
471   if !DiagsUpdateLocList(bufnr())
472     util.WarnMsg($'No diagnostic messages found for {@%}')
473     return
474   endif
475
476   var save_winid = win_getid()
477   # make the diagnostics error list the active one and open it
478   var LspQfId: number = getbufvar(bufnr(), 'LspQfId', 0)
479   var LspQfNr: number = getloclist(0, {id: LspQfId, nr: 0}).nr
480   exe $':{LspQfNr} lhistory'
481   :lopen
482   if !opt.lspOptions.keepFocusInDiags
483     save_winid->win_gotoid()
484   endif
485 enddef
486
487 # Display the message of 'diag' in a popup window right below the position in
488 # the diagnostic message.
489 def ShowDiagInPopup(diag: dict<any>)
490   var dlnum = diag.range.start.line + 1
491   var ltext = dlnum->getline()
492   var dlcol = ltext->byteidxcomp(diag.range.start.character) + 1
493
494   var lastline = line('$')
495   if dlnum > lastline
496     # The line number is outside the last line in the file.
497     dlnum = lastline
498   endif
499   if dlcol < 1
500     # The column is outside the last character in line.
501     dlcol = ltext->len() + 1
502   endif
503   var d = screenpos(0, dlnum, dlcol)
504   if d->empty()
505     # If the diag position cannot be converted to Vim lnum/col, then use
506     # the current cursor position
507     d = {row: line('.'), col: col('.')}
508   endif
509
510   # Display a popup right below the diagnostics position
511   var msg = diag.message->split("\n")
512   var msglen = msg->reduce((acc, val) => max([acc, val->strcharlen()]), 0)
513
514   var ppopts = {}
515   ppopts.pos = 'topleft'
516   ppopts.line = d.row + 1
517   ppopts.moved = 'any'
518
519   if msglen > &columns
520     ppopts.wrap = true
521     ppopts.col = 1
522   else
523     ppopts.wrap = false
524     ppopts.col = d.col
525   endif
526
527   popup_create(msg, ppopts)
528 enddef
529
530 # Display the 'diag' message in a popup or in the status message area
531 def DisplayDiag(diag: dict<any>)
532   if opt.lspOptions.showDiagInPopup
533     # Display the diagnostic message in a popup window.
534     ShowDiagInPopup(diag)
535   else
536     # Display the diagnostic message in the status message area
537     :echo diag.message
538   endif
539 enddef
540
541 # Show the diagnostic message for the current line
542 export def ShowCurrentDiag(atPos: bool)
543   var bnr: number = bufnr()
544   var lnum: number = line('.')
545   var col: number = charcol('.')
546   var diag: dict<any> = GetDiagByPos(bnr, lnum, col, atPos)
547   if diag->empty()
548     util.WarnMsg($'No diagnostic messages found for current {atPos ? "position" : "line"}')
549   else
550     DisplayDiag(diag)
551   endif
552 enddef
553
554 # Show the diagnostic message for the current line without linebreak
555 export def ShowCurrentDiagInStatusLine()
556   var bnr: number = bufnr()
557   var lnum: number = line('.')
558   var col: number = charcol('.')
559   var diag: dict<any> = GetDiagByPos(bnr, lnum, col)
560   if !diag->empty()
561     # 15 is a enough length not to cause line break
562     var max_width = &columns - 15
563     var code = ''
564     if diag->has_key('code')
565       code = $'[{diag.code}] '
566     endif
567     var msgNoLineBreak = code .. substitute(substitute(diag.message, "\n", ' ', ''), "\\n", ' ', '')
568     :echo msgNoLineBreak[ : max_width]
569   else
570     :echo ''
571   endif
572 enddef
573
574 # Get the diagnostic from the LSP server for a particular line and character
575 # offset in a file
576 export def GetDiagByPos(bnr: number, lnum: number, col: number,
577                         atPos: bool = false): dict<any>
578   var diags_in_line = GetDiagsByLine(bnr, lnum)
579
580   for diag in diags_in_line
581     var startCharIdx = util.GetCharIdxWithoutCompChar(bnr, diag.range.start)
582     var endCharIdx = util.GetCharIdxWithoutCompChar(bnr, diag.range.end)
583     if atPos
584       if col >= startCharIdx + 1 && col < endCharIdx + 1
585         return diag
586       endif
587     elseif col <= startCharIdx + 1
588       return diag
589     endif
590   endfor
591
592   # No diagnostic to the right of the position, return the last one instead
593   if !atPos && diags_in_line->len() > 0
594     return diags_in_line[-1]
595   endif
596
597   return {}
598 enddef
599
600 # Get all diagnostics from the LSP server for a particular line in a file
601 export def GetDiagsByLine(bnr: number, lnum: number, lspserver: dict<any> = null_dict): list<dict<any>>
602   if !diagsMap->has_key(bnr)
603     return []
604   endif
605
606   var diags: list<dict<any>> = []
607
608   var serverDiagsByLnum = diagsMap[bnr].serverDiagnosticsByLnum
609
610   if lspserver == null_dict
611     for diagsByLnum in serverDiagsByLnum->values()
612       if diagsByLnum->has_key(lnum)
613         diags->extend(diagsByLnum[lnum])
614       endif
615     endfor
616   else
617     if !serverDiagsByLnum->has_key(lspserver.id)
618       return []
619     endif
620     if serverDiagsByLnum[lspserver.id]->has_key(lnum)
621       diags = serverDiagsByLnum[lspserver.id][lnum]
622     endif
623   endif
624
625   return diags->sort((a, b) => {
626     return a.range.start.character - b.range.start.character
627   })
628 enddef
629
630 # Utility function to do the actual jump
631 def JumpDiag(diag: dict<any>)
632   var startPos: dict<number> = diag.range.start
633   setcursorcharpos(startPos.line + 1,
634                    util.GetCharIdxWithoutCompChar(bufnr(), startPos) + 1)
635   if !opt.lspOptions.showDiagWithVirtualText
636     :redraw
637     DisplayDiag(diag)
638   endif
639 enddef
640
641 # jump to the next/previous/first diagnostic message in the current buffer
642 export def LspDiagsJump(which: string, a_count: number = 0): void
643   var fname: string = expand('%:p')
644   if fname->empty()
645     return
646   endif
647   var bnr: number = bufnr()
648
649   if !diagsMap->has_key(bnr) ||
650       diagsMap[bnr].sortedDiagnostics->empty()
651     util.WarnMsg($'No diagnostic messages found for {fname}')
652     return
653   endif
654
655   var diags = diagsMap[bnr].sortedDiagnostics
656
657   if which == 'first'
658     JumpDiag(diags[0])
659     return
660   endif
661
662   if which == 'last'
663     JumpDiag(diags[-1])
664     return
665   endif
666
667   # Find the entry just before the current line (binary search)
668   var count = a_count > 1 ? a_count : 1
669   var curlnum: number = line('.')
670   var curcol: number = charcol('.')
671   for diag in (which == 'next' || which == 'here') ?
672                                         diags : diags->copy()->reverse()
673     var lnum = diag.range.start.line + 1
674     var col = util.GetCharIdxWithoutCompChar(bnr, diag.range.start) + 1
675     if (which == 'next' && (lnum > curlnum || lnum == curlnum && col > curcol))
676           || (which == 'prev' && (lnum < curlnum || lnum == curlnum
677                                                         && col < curcol))
678           || (which == 'here' && (lnum == curlnum && col >= curcol))
679
680       # Skip over as many diags as "count" dictates
681       count = count - 1
682       if count > 0
683         continue
684       endif
685
686       JumpDiag(diag)
687       return
688     endif
689   endfor
690
691   # If [count] exceeded the remaining diags
692   if which == 'next' && a_count > 1 && a_count != count
693     JumpDiag(diags[-1])
694     return
695   endif
696
697   # If [count] exceeded the previous diags
698   if which == 'prev' && a_count > 1 && a_count != count
699     JumpDiag(diags[0])
700     return
701   endif
702
703   if which == 'here'
704     util.WarnMsg('No more diagnostics found on this line')
705   else
706     util.WarnMsg('No more diagnostics found')
707   endif
708 enddef
709
710 # Disable the LSP diagnostics highlighting in all the buffers
711 export def DiagsHighlightDisable()
712   # turn off all diags highlight
713   opt.lspOptions.autoHighlightDiags = false
714   for binfo in getbufinfo({bufloaded: true})
715       RemoveDiagVisualsForBuffer(binfo.bufnr)
716   endfor
717 enddef
718
719 # Enable the LSP diagnostics highlighting
720 export def DiagsHighlightEnable()
721   opt.lspOptions.autoHighlightDiags = true
722   for binfo in getbufinfo({bufloaded: true})
723     DiagsRefresh(binfo.bufnr)
724   endfor
725 enddef
726
727 # vim: tabstop=8 shiftwidth=2 softtabstop=2