]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/diag.vim
45cf8208538bf909dd4273116094939a7793a4a6
[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   if lspserver.processDiagHandler != null_function
313     newDiags = lspserver.processDiagHandler(diags_arg)
314   endif
315
316   # TODO: Is the buffer (bnr) always a loaded buffer? Should we load it here?
317   var lastlnum: number = bnr->getbufinfo()[0].linecount
318
319   # store the diagnostic for each line separately
320   var diagsByLnum: dict<list<dict<any>>> = {}
321
322   var diagWithinRange: list<dict<any>> = []
323   for diag in newDiags
324     if diag.range.start.line + 1 > lastlnum
325       # Make sure the line number is a valid buffer line number
326       diag.range.start.line = lastlnum - 1
327     endif
328
329     var lnum = diag.range.start.line + 1
330     if !diagsByLnum->has_key(lnum)
331       diagsByLnum[lnum] = []
332     endif
333     diagsByLnum[lnum]->add(diag)
334
335     diagWithinRange->add(diag)
336   endfor
337
338   var serverDiags: dict<list<any>> = diagsMap->has_key(bnr) ?
339       diagsMap[bnr].serverDiagnostics : {}
340   serverDiags[lspserver.id] = diagWithinRange
341
342   var serverDiagsByLnum: dict<dict<list<any>>> = diagsMap->has_key(bnr) ?
343       diagsMap[bnr].serverDiagnosticsByLnum : {}
344   serverDiagsByLnum[lspserver.id] = diagsByLnum
345
346   # store the diagnostic for each line separately
347   var joinedServerDiags: list<dict<any>> = []
348   for diags in serverDiags->values()
349     for diag in diags
350       joinedServerDiags->add(diag)
351     endfor
352   endfor
353
354   var sortedDiags = SortDiags(joinedServerDiags)
355
356   diagsMap[bnr] = {
357     sortedDiagnostics: sortedDiags,
358     serverDiagnosticsByLnum: serverDiagsByLnum,
359     serverDiagnostics: serverDiags
360   }
361
362   ProcessNewDiags(bnr)
363
364   # Notify user scripts that diags has been updated
365   if exists('#User#LspDiagsUpdated')
366     :doautocmd <nomodeline> User LspDiagsUpdated
367   endif
368 enddef
369
370 # get the count of error in the current buffer
371 export def DiagsGetErrorCount(): dict<number>
372   var errCount = 0
373   var warnCount = 0
374   var infoCount = 0
375   var hintCount = 0
376
377   var bnr: number = bufnr()
378   if diagsMap->has_key(bnr)
379     var diags = diagsMap[bnr].sortedDiagnostics
380     for diag in diags
381       var severity = diag->get('severity', -1)
382       if severity == 1
383         errCount += 1
384       elseif severity == 2
385         warnCount += 1
386       elseif severity == 3
387         infoCount += 1
388       elseif severity == 4
389         hintCount += 1
390       endif
391     endfor
392   endif
393
394   return {Error: errCount, Warn: warnCount, Info: infoCount, Hint: hintCount}
395 enddef
396
397 # Map the LSP DiagnosticSeverity to a quickfix type character
398 def DiagSevToQfType(severity: number): string
399   var typeMap: list<string> = ['E', 'W', 'I', 'N']
400
401   if severity > 4
402     return ''
403   endif
404
405   return typeMap[severity - 1]
406 enddef
407
408 # Update the location list window for the current window with the diagnostic
409 # messages.
410 # Returns true if diagnostics is not empty and false if it is empty.
411 def DiagsUpdateLocList(bnr: number): bool
412   var fname: string = bnr->bufname()->fnamemodify(':p')
413   if fname->empty()
414     return false
415   endif
416
417   var LspQfId: number = bnr->getbufvar('LspQfId', 0)
418   if !LspQfId->empty() && getloclist(0, {id: LspQfId}).id != LspQfId
419     LspQfId = 0
420   endif
421
422   if !diagsMap->has_key(bnr) ||
423       diagsMap[bnr].sortedDiagnostics->empty()
424     if LspQfId != 0
425       setloclist(0, [], 'r', {id: LspQfId, items: []})
426     endif
427     return false
428   endif
429
430   var qflist: list<dict<any>> = []
431   var text: string
432
433   var diags = diagsMap[bnr].sortedDiagnostics
434   for diag in diags
435     text = diag.message->substitute("\n\\+", "\n", 'g')
436     qflist->add({filename: fname,
437                     lnum: diag.range.start.line + 1,
438                     col: util.GetLineByteFromPos(bnr, diag.range.start) + 1,
439                     end_lnum: diag.range.end.line + 1,
440                     end_col: util.GetLineByteFromPos(bnr, diag.range.end) + 1,
441                     text: text,
442                     type: DiagSevToQfType(diag.severity)})
443   endfor
444
445   var op: string = ' '
446   var props = {title: 'Language Server Diagnostics', items: qflist}
447   if LspQfId != 0
448     op = 'r'
449     props.id = LspQfId
450   endif
451   setloclist(0, [], op, props)
452   if LspQfId == 0
453     setbufvar(bnr, 'LspQfId', getloclist(0, {id: 0}).id)
454   endif
455
456   return true
457 enddef
458
459 # Display the diagnostic messages from the LSP server for the current buffer
460 # in a location list
461 export def ShowAllDiags(): void
462   if !DiagsUpdateLocList(bufnr())
463     util.WarnMsg($'No diagnostic messages found for {@%}')
464     return
465   endif
466
467   var save_winid = win_getid()
468   # make the diagnostics error list the active one and open it
469   var LspQfId: number = getbufvar(bufnr(), 'LspQfId', 0)
470   var LspQfNr: number = getloclist(0, {id: LspQfId, nr: 0}).nr
471   exe $':{LspQfNr} lhistory'
472   :lopen
473   if !opt.lspOptions.keepFocusInDiags
474     save_winid->win_gotoid()
475   endif
476 enddef
477
478 # Display the message of 'diag' in a popup window right below the position in
479 # the diagnostic message.
480 def ShowDiagInPopup(diag: dict<any>)
481   var dlnum = diag.range.start.line + 1
482   var ltext = dlnum->getline()
483   var dlcol = ltext->byteidxcomp(diag.range.start.character) + 1
484
485   var lastline = line('$')
486   if dlnum > lastline
487     # The line number is outside the last line in the file.
488     dlnum = lastline
489   endif
490   if dlcol < 1
491     # The column is outside the last character in line.
492     dlcol = ltext->len() + 1
493   endif
494   var d = screenpos(0, dlnum, dlcol)
495   if d->empty()
496     # If the diag position cannot be converted to Vim lnum/col, then use
497     # the current cursor position
498     d = {row: line('.'), col: col('.')}
499   endif
500
501   # Display a popup right below the diagnostics position
502   var msg = diag.message->split("\n")
503   var msglen = msg->reduce((acc, val) => max([acc, val->strcharlen()]), 0)
504
505   var ppopts = {}
506   ppopts.pos = 'topleft'
507   ppopts.line = d.row + 1
508   ppopts.moved = 'any'
509
510   if msglen > &columns
511     ppopts.wrap = true
512     ppopts.col = 1
513   else
514     ppopts.wrap = false
515     ppopts.col = d.col
516   endif
517
518   popup_create(msg, ppopts)
519 enddef
520
521 # Display the 'diag' message in a popup or in the status message area
522 def DisplayDiag(diag: dict<any>)
523   if opt.lspOptions.showDiagInPopup
524     # Display the diagnostic message in a popup window.
525     ShowDiagInPopup(diag)
526   else
527     # Display the diagnostic message in the status message area
528     :echo diag.message
529   endif
530 enddef
531
532 # Show the diagnostic message for the current line
533 export def ShowCurrentDiag(atPos: bool)
534   var bnr: number = bufnr()
535   var lnum: number = line('.')
536   var col: number = charcol('.')
537   var diag: dict<any> = GetDiagByPos(bnr, lnum, col, atPos)
538   if diag->empty()
539     util.WarnMsg($'No diagnostic messages found for current {atPos ? "position" : "line"}')
540   else
541     DisplayDiag(diag)
542   endif
543 enddef
544
545 # Show the diagnostic message for the current line without linebreak
546 export def ShowCurrentDiagInStatusLine()
547   var bnr: number = bufnr()
548   var lnum: number = line('.')
549   var col: number = charcol('.')
550   var diag: dict<any> = GetDiagByPos(bnr, lnum, col)
551   if !diag->empty()
552     # 15 is a enough length not to cause line break
553     var max_width = &columns - 15
554     var code = ''
555     if diag->has_key('code')
556       code = $'[{diag.code}] '
557     endif
558     var msgNoLineBreak = code .. substitute(substitute(diag.message, "\n", ' ', ''), "\\n", ' ', '')
559     :echo msgNoLineBreak[ : max_width]
560   else
561     :echo ''
562   endif
563 enddef
564
565 # Get the diagnostic from the LSP server for a particular line and character
566 # offset in a file
567 export def GetDiagByPos(bnr: number, lnum: number, col: number,
568                         atPos: bool = false): dict<any>
569   var diags_in_line = GetDiagsByLine(bnr, lnum)
570
571   for diag in diags_in_line
572     var startCharIdx = util.GetCharIdxWithoutCompChar(bnr, diag.range.start)
573     var endCharIdx = util.GetCharIdxWithoutCompChar(bnr, diag.range.end)
574     if atPos
575       if col >= startCharIdx + 1 && col < endCharIdx + 1
576         return diag
577       endif
578     elseif col <= startCharIdx + 1
579       return diag
580     endif
581   endfor
582
583   # No diagnostic to the right of the position, return the last one instead
584   if !atPos && diags_in_line->len() > 0
585     return diags_in_line[-1]
586   endif
587
588   return {}
589 enddef
590
591 # Get all diagnostics from the LSP server for a particular line in a file
592 export def GetDiagsByLine(bnr: number, lnum: number, lspserver: dict<any> = null_dict): list<dict<any>>
593   if !diagsMap->has_key(bnr)
594     return []
595   endif
596
597   var diags: list<dict<any>> = []
598
599   var serverDiagsByLnum = diagsMap[bnr].serverDiagnosticsByLnum
600
601   if lspserver == null_dict
602     for diagsByLnum in serverDiagsByLnum->values()
603       if diagsByLnum->has_key(lnum)
604         diags->extend(diagsByLnum[lnum])
605       endif
606     endfor
607   else
608     if !serverDiagsByLnum->has_key(lspserver.id)
609       return []
610     endif
611     if serverDiagsByLnum[lspserver.id]->has_key(lnum)
612       diags = serverDiagsByLnum[lspserver.id][lnum]
613     endif
614   endif
615
616   return diags->sort((a, b) => {
617     return a.range.start.character - b.range.start.character
618   })
619 enddef
620
621 # Utility function to do the actual jump
622 def JumpDiag(diag: dict<any>)
623   var startPos: dict<number> = diag.range.start
624   setcursorcharpos(startPos.line + 1,
625                    util.GetCharIdxWithoutCompChar(bufnr(), startPos) + 1)
626   if !opt.lspOptions.showDiagWithVirtualText
627     :redraw
628     DisplayDiag(diag)
629   endif
630 enddef
631
632 # jump to the next/previous/first diagnostic message in the current buffer
633 export def LspDiagsJump(which: string, a_count: number = 0): void
634   var fname: string = expand('%:p')
635   if fname->empty()
636     return
637   endif
638   var bnr: number = bufnr()
639
640   if !diagsMap->has_key(bnr) ||
641       diagsMap[bnr].sortedDiagnostics->empty()
642     util.WarnMsg($'No diagnostic messages found for {fname}')
643     return
644   endif
645
646   var diags = diagsMap[bnr].sortedDiagnostics
647
648   if which == 'first'
649     JumpDiag(diags[0])
650     return
651   endif
652
653   if which == 'last'
654     JumpDiag(diags[-1])
655     return
656   endif
657
658   # Find the entry just before the current line (binary search)
659   var count = a_count > 1 ? a_count : 1
660   var curlnum: number = line('.')
661   var curcol: number = charcol('.')
662   for diag in (which == 'next' || which == 'here') ?
663                                         diags : diags->copy()->reverse()
664     var lnum = diag.range.start.line + 1
665     var col = util.GetCharIdxWithoutCompChar(bnr, diag.range.start) + 1
666     if (which == 'next' && (lnum > curlnum || lnum == curlnum && col > curcol))
667           || (which == 'prev' && (lnum < curlnum || lnum == curlnum
668                                                         && col < curcol))
669           || (which == 'here' && (lnum == curlnum && col >= curcol))
670
671       # Skip over as many diags as "count" dictates
672       count = count - 1
673       if count > 0
674         continue
675       endif
676
677       JumpDiag(diag)
678       return
679     endif
680   endfor
681
682   # If [count] exceeded the remaining diags
683   if which == 'next' && a_count > 1 && a_count != count
684     JumpDiag(diags[-1])
685     return
686   endif
687
688   # If [count] exceeded the previous diags
689   if which == 'prev' && a_count > 1 && a_count != count
690     JumpDiag(diags[0])
691     return
692   endif
693
694   if which == 'here'
695     util.WarnMsg('No more diagnostics found on this line')
696   else
697     util.WarnMsg('No more diagnostics found')
698   endif
699 enddef
700
701 # Disable the LSP diagnostics highlighting in all the buffers
702 export def DiagsHighlightDisable()
703   # turn off all diags highlight
704   opt.lspOptions.autoHighlightDiags = false
705   for binfo in getbufinfo({bufloaded: true})
706       RemoveDiagVisualsForBuffer(binfo.bufnr)
707   endfor
708 enddef
709
710 # Enable the LSP diagnostics highlighting
711 export def DiagsHighlightEnable()
712   opt.lspOptions.autoHighlightDiags = true
713   for binfo in getbufinfo({bufloaded: true})
714     DiagsRefresh(binfo.bufnr)
715   endfor
716 enddef
717
718 # vim: tabstop=8 shiftwidth=2 softtabstop=2