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