neovim / neovim

Vim-fork focused on extensibility and usability
https://neovim.io
Other
83k stars 5.69k forks source link

Related information of diagnostics is not parsed & displayed. #19649

Open BigRedEye opened 2 years ago

BigRedEye commented 2 years ago

Neovim version (nvim -v)

NVIM v0.8.0-dev-758-g711ef4eac9

Language server name/version

clangd 14.0

Operating system/version

Arch Linux

Steps to reproduce using "nvim -u minimal_init.lua"

Many servers support relatedInformation; I'm personally struggling with clagnd.

$ cat <<EOF >repro.cpp
void foo(char);
void foo(double);
void bar() {
    foo(1);
}
EOF
$ nvim -u minimal_init.lua repro.cpp
:lua require('lspconfig').clangd.setup{}
:lua vim.diagnostic.setqflist()

Expected behavior

Clangd shows three diagnostics (error + 2x info), just like standalone compiler:

repro.cpp:4:5: error: call to 'foo' is ambiguous
    foo(1);
    ^~~
repro.cpp:1:6: note: candidate function
void foo(char);
     ^
repro.cpp:2:6: note: candidate function
void foo(double);

VSCode handles this correctly: img

Actual behavior

Clangd reports one diagnostic with two items in the relatedInformation list: https://gist.github.com/BigRedEye/253629638e1d71f4df667a3e8e05a1d3.

Neovim does not parse relatedInformation and displays just one main diagnostic: img

Log file

No response

tom-anders commented 1 year ago

@glepnir Are you still working on this? I also really would like to have this feature and would like to help out with the implementation if needed!

tom-anders commented 1 year ago

In the meantime, for anyone who wants this, here's a quick and dirty implementation I added to my config:

local function getlines(location)
  local uri = location.targetUri or location.uri
  if uri == nil then
    return
  end
  local bufnr = vim.uri_to_bufnr(uri)
  if not vim.api.nvim_buf_is_loaded(bufnr) then
    vim.fn.bufload(bufnr)
  end
  local range = location.targetRange or location.range

  local lines = vim.api.nvim_buf_get_lines(bufnr, range.start.line, range['end'].line+1, false)
  return table.concat(lines, '\n')
end

vim.diagnostic.config({float = {format = function(diag) 
    local message = diag.message
    local client = vim.lsp.get_active_clients({name = message.source})[1]
    if not client then
        return diag.message
    end

    local relatedInfo = {messages = {}, locations = {}}
    for _, info in ipairs(diag.user_data.lsp.relatedInformation) do
        table.insert(relatedInfo.messages, info.message)
        table.insert(relatedInfo.locations, info.location)
    end

    for i, loc in ipairs(vim.lsp.util.locations_to_items(relatedInfo.locations, client.offset_encoding)) do
        message = string.format("%s\n%s (%s:%d):\n\t%s", message, relatedInfo.messages[i], 
                                vim.fn.fnamemodify(loc.filename, ':.'), loc.lnum,
                                getlines(relatedInfo.locations[i]))
    end

    return message
end}})
nyngwang commented 1 year ago

@tom-anders Got an error:

   Error  12:00:25 msg_show.lua_error Error detected while processing CursorHold Autocommands for "*":
12:00:25 msg_show Error executing lua callback: ...config/nvim/lua/ningwang/data/lsp/utils/common_setup.lua:57: bad argument #1 to 'ipairs' (table expected, got nil)
stack traceback:
    [C]: in function 'ipairs'
    ...config/nvim/lua/ningwang/data/lsp/utils/common_setup.lua:57: in function 'format'
    ...rapped-5bf2f4b/share/nvim/runtime/lua/vim/diagnostic.lua:144: in function 'reformat_diagnostics'
    ...rapped-5bf2f4b/share/nvim/runtime/lua/vim/diagnostic.lua:1373: in function 'open_float'
    /Users/ningwang/.config/nvim/lua/plugins/UX.lua:225: in function </Users/ningwang/.config/nvim/lua/plugins/UX.lua:219>
ayusrc commented 1 year ago

The following redefinition of the textDocument/publishDiagnostics handler may be used to get the related information in the diagnostic message as well as hint-level diagnostics for the related information (extended from https://github.com/neovim/neovim/issues/22744#issuecomment-1479318034):

local original = vim.lsp.handlers['textDocument/publishDiagnostics']
vim.lsp.handlers['textDocument/publishDiagnostics'] = function(_, result, ctx, config)
   vim.tbl_map(function(item)
      if item.relatedInformation and #item.relatedInformation > 0 then
         vim.tbl_map(function(k)
            if k.location then
               local tail = vim.fn.fnamemodify(vim.uri_to_fname(k.location.uri), ':t')
               k.message = tail ..
                   '(' .. (k.location.range.start.line + 1) .. ', ' .. (k.location.range.start.character + 1) ..
                   '): ' .. k.message

               if k.location.uri == vim.uri_from_bufnr(0) then
                  table.insert(result.diagnostics, {
                     code = item.code,
                     message = k.message,
                     range = k.location.range,
                     severity = vim.lsp.protocol.DiagnosticSeverity.Hint,
                     source = item.source,
                     relatedInformation = {},
                  })
               end
            end
            item.message = item.message .. '\n' .. k.message
         end, item.relatedInformation)
      end
   end, result.diagnostics)
   original(_, result, ctx, config)
end