From 29e0a7dffb8d88bb04b3e20761096e0fb7933ee3 Mon Sep 17 00:00:00 2001 From: Andreas Louv Date: Mon, 20 Mar 2023 11:11:23 +0100 Subject: [PATCH] Add support for multiple diagnostics per line --- autoload/lsp/diag.vim | 113 ++++++++++++++++++++++++++----------- autoload/lsp/lsp.vim | 16 ++++-- autoload/lsp/lspserver.vim | 9 ++- 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/autoload/lsp/diag.vim b/autoload/lsp/diag.vim index 0b41236..ebe379c 100644 --- a/autoload/lsp/diag.vim +++ b/autoload/lsp/diag.vim @@ -27,14 +27,19 @@ def DiagsRefreshSigns(lspserver: dict, bnr: number) # Remove all the existing diagnostic signs sign_unplace('LSPDiag', {buffer: bnr}) - if lspserver.diagsMap[bnr]->empty() + if !lspserver.diagsMap->has_key(bnr) || + lspserver.diagsMap[bnr].sortedDiagnostics->empty() return endif var signs: list> = [] - for [lnum, diag] in lspserver.diagsMap[bnr]->items() + var diags = lspserver.diagsMap[bnr].sortedDiagnostics + for diag in diags + # TODO: prioritize most important severity if there are multiple diagnostics + # from the same line + var lnum = diag.range.start.line + 1 signs->add({id: 0, buffer: bnr, group: 'LSPDiag', - lnum: lnum->str2nr(), + lnum: lnum, name: DiagSevToSignName(diag.severity)}) endfor @@ -80,20 +85,39 @@ export def DiagNotification(lspserver: dict, uri: string, diags: listgetbufinfo()[0].linecount - var lnum: number - # store the diagnostic for each line separately - var diag_by_lnum: dict> = {} - for d in diags - lnum = d.range.start.line + 1 - if lnum > lastlnum + var diagByLnum: dict>> = {} + var diagWithinRange: list> = [] + for diag in diags + if diag.range.start.line + 1 > lastlnum # Make sure the line number is a valid buffer line number - lnum = lastlnum + diag.range.start.line = lastlnum - 1 + endif + + var lnum = diag.range.start.line + 1 + if !diagByLnum->has_key(lnum) + diagByLnum[lnum] = [] endif - diag_by_lnum[lnum] = d + diagByLnum[lnum]->add(diag) + + diagWithinRange->add(diag) endfor - lspserver.diagsMap->extend({[$'{bnr}']: diag_by_lnum}) + var sortedDiags = diagWithinRange->sort((a, b) => { + var linediff = a.range.start.line - b.range.start.line + if linediff == 0 + return a.range.start.character - b.range.start.character + endif + return linediff + }) + + lspserver.diagsMap->extend({ + [$'{bnr}']: { + sortedDiagnostics: sortedDiags, + diagnosticsByLnum: diagByLnum + } + }) + ProcessNewDiags(lspserver, bnr) # Notify user scripts that diags has been updated @@ -111,8 +135,9 @@ export def DiagsGetErrorCount(lspserver: dict): dict var bnr: number = bufnr() if lspserver.diagsMap->has_key(bnr) - for item in lspserver.diagsMap[bnr]->values() - var severity = item->get('severity', -1) + var diags = lspserver.diagsMap[bnr].sortedDiagnostics + for diag in diags + var severity = diag->get('severity', -1) if severity == 1 errCount += 1 elseif severity == 2 @@ -154,7 +179,8 @@ def DiagsUpdateLocList(lspserver: dict, bnr: number): bool LspQfId = b:LspQfId endif - if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty() + if !lspserver.diagsMap->has_key(bnr) || + lspserver.diagsMap[bnr].sortedDiagnostics->empty() if LspQfId != 0 setloclist(0, [], 'r', {id: LspQfId, items: []}) endif @@ -164,7 +190,8 @@ def DiagsUpdateLocList(lspserver: dict, bnr: number): bool var qflist: list> = [] var text: string - for [lnum, diag] in lspserver.diagsMap[bnr]->items()->sort((a, b) => a[0]->str2nr() - b[0]->str2nr()) + var diags = lspserver.diagsMap[bnr].sortedDiagnostics + for diag in diags text = diag.message->substitute("\n\\+", "\n", 'g') qflist->add({filename: fname, lnum: diag.range.start.line + 1, @@ -235,7 +262,8 @@ enddef export def ShowCurrentDiag(lspserver: dict) var bnr: number = bufnr() var lnum: number = line('.') - var diag: dict = lspserver.getDiagByLine(bnr, lnum) + var col: number = charcol('.') + var diag: dict = lspserver.getDiagByPos(bnr, lnum, col) if diag->empty() util.WarnMsg('No diagnostic messages found for current line') else @@ -253,7 +281,8 @@ enddef export def ShowCurrentDiagInStatusLine(lspserver: dict) var bnr: number = bufnr() var lnum: number = line('.') - var diag: dict = lspserver.getDiagByLine(bnr, lnum) + var col: number = charcol('.') + var diag: dict = lspserver.getDiagByPos(bnr, lnum, col) if !diag->empty() # 15 is a enough length not to cause line break var max_width = &columns - 15 @@ -268,19 +297,32 @@ export def ShowCurrentDiagInStatusLine(lspserver: dict) endif enddef -# Get the diagnostic from the LSP server for a particular line in a file -export def GetDiagByLine(lspserver: dict, bnr: number, lnum: number): dict - if lspserver.diagsMap->has_key(bnr) && - lspserver.diagsMap[bnr]->has_key(lnum) - return lspserver.diagsMap[bnr][lnum] +# Get the diagnostic from the LSP server for a particular line and character +# offset in a file +export def GetDiagByPos(lspserver: dict, bnr: number, lnum: number, col: number): dict + var diags_in_line = GetDiagsByLine(lspserver, bnr, lnum) + + for diag in diags_in_line + if col <= diag.range.start.character + 1 + return diag + endif + endfor + + if diags_in_line->len() > 0 + return diags_in_line[-1] endif + return {} enddef -# sort the diaganostics messages for a buffer by line number -def GetSortedDiagLines(lspsrv: dict, bnr: number): list> - var diags = lspsrv.diagsMap[bnr] - return diags->values()->sort((a, b) => a.range.start.line - b.range.start.line) +# Get all diagnostics from the LSP server for a particular line in a file +export def GetDiagsByLine(lspserver: dict, bnr: number, lnum: number): list> + if lspserver.diagsMap->has_key(bnr) + if lspserver.diagsMap[bnr].diagnosticsByLnum->has_key(lnum) + return lspserver.diagsMap[bnr].diagnosticsByLnum[lnum] + endif + endif + return [] enddef # jump to the next/previous/first diagnostic message in the current buffer @@ -291,26 +333,29 @@ export def LspDiagsJump(lspserver: dict, which: string): void endif var bnr: number = bufnr() - if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty() + if !lspserver.diagsMap->has_key(bnr) || + lspserver.diagsMap[bnr].sortedDiagnostics->empty() util.WarnMsg($'No diagnostic messages found for {fname}') return endif # sort the diagnostics by line number - var sortedDiags: list> = GetSortedDiagLines(lspserver, bnr) + var diags = lspserver.diagsMap[bnr].sortedDiagnostics if which == 'first' - setcursorcharpos(sortedDiags[0].range.start.line + 1, sortedDiags[0].range.start.character + 1) + setcursorcharpos(diags[0].range.start.line + 1, diags[0].range.start.character + 1) return endif # Find the entry just before the current line (binary search) var curlnum: number = line('.') - for diag in (which == 'next') ? sortedDiags : sortedDiags->reverse() + var curcol: number = charcol('.') + for diag in (which == 'next') ? diags : diags->copy()->reverse() var lnum = diag.range.start.line + 1 - if (which == 'next' && lnum > curlnum) - || (which == 'prev' && lnum < curlnum) - setcursorcharpos(diag.range.start.line + 1, diag.range.start.character + 1) + var col = diag.range.start.character + 1 + if (which == 'next' && (lnum > curlnum || lnum == curlnum && col > curcol)) + || (which == 'prev' && (lnum < curlnum || lnum == curlnum && col < curcol)) + setcursorcharpos(lnum, col) return endif endfor diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index 286e97c..097ba56 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -205,14 +205,22 @@ def g:LspDiagExpr(): any return '' endif - var diagInfo: dict = lspserver.getDiagByLine(v:beval_bufnr, - v:beval_lnum) - if diagInfo->empty() + var diagsInfo: list> = lspserver.getDiagsByLine( + v:beval_bufnr, + v:beval_lnum + ) + if diagsInfo->empty() # No diagnostic for the current cursor location return '' endif - return diagInfo.message->split("\n") + # Include all diagnostics from the current line in the message + var message: list = [] + for diag in diagsInfo + message->extend(diag.message->split("\n")) + endfor + + return message enddef # Called after leaving insert mode. Used to process diag messages (if any) diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index ca8b66c..2c638c5 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -1371,10 +1371,8 @@ def CodeAction(lspserver: dict, fname_arg: string, line1: number, params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r}) var d: list> = [] for lnum in range(line1, line2) - var diagInfo: dict = diag.GetDiagByLine(lspserver, bnr, lnum) - if !diagInfo->empty() - d->add(diagInfo) - endif + var diagsInfo: list> = diag.GetDiagsByLine(lspserver, bnr, lnum) + d->extend(diagsInfo) endfor params->extend({context: {diagnostics: d, triggerKind: 1}}) @@ -1665,7 +1663,8 @@ export def NewLspServer(path: string, args: list, isSync: bool, initiali processNotif: function(handlers.ProcessNotif, [lspserver]), processRequest: function(handlers.ProcessRequest, [lspserver]), processMessages: function(handlers.ProcessMessages, [lspserver]), - getDiagByLine: function(diag.GetDiagByLine, [lspserver]), + getDiagByPos: function(diag.GetDiagByPos, [lspserver]), + getDiagsByLine: function(diag.GetDiagsByLine, [lspserver]), textdocDidOpen: function(TextdocDidOpen, [lspserver]), textdocDidClose: function(TextdocDidClose, [lspserver]), textdocDidChange: function(TextdocDidChange, [lspserver]), -- 2.48.1