neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

diagnostic_spec.lua (145115B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 
      4 local command = n.command
      5 local clear = n.clear
      6 local exec_lua = n.exec_lua
      7 local eq = t.eq
      8 local neq = t.neq
      9 local matches = t.matches
     10 local retry = t.retry
     11 local api = n.api
     12 local pcall_err = t.pcall_err
     13 local fn = n.fn
     14 
     15 describe('vim.diagnostic', function()
     16  before_each(function()
     17    clear()
     18 
     19    exec_lua(function()
     20      require('vim.diagnostic')
     21 
     22      local function make_diagnostic(msg, lnum, col, end_lnum, end_col, severity, source, code)
     23        return {
     24          lnum = lnum,
     25          col = col,
     26          end_lnum = end_lnum,
     27          end_col = end_col,
     28          message = msg,
     29          severity = severity,
     30          source = source,
     31          code = code,
     32        }
     33      end
     34 
     35      function _G.make_error(msg, lnum, col, end_lnum, end_col, source, code)
     36        return make_diagnostic(
     37          msg,
     38          lnum,
     39          col,
     40          end_lnum,
     41          end_col,
     42          vim.diagnostic.severity.ERROR,
     43          source,
     44          code
     45        )
     46      end
     47 
     48      function _G.make_warning(msg, lnum, col, end_lnum, end_col, source, code)
     49        return make_diagnostic(
     50          msg,
     51          lnum,
     52          col,
     53          end_lnum,
     54          end_col,
     55          vim.diagnostic.severity.WARN,
     56          source,
     57          code
     58        )
     59      end
     60 
     61      function _G.make_info(msg, lnum, col, end_lnum, end_col, source, code)
     62        return make_diagnostic(
     63          msg,
     64          lnum,
     65          col,
     66          end_lnum,
     67          end_col,
     68          vim.diagnostic.severity.INFO,
     69          source,
     70          code
     71        )
     72      end
     73 
     74      function _G.make_hint(msg, lnum, col, end_lnum, end_col, source, code)
     75        return make_diagnostic(
     76          msg,
     77          lnum,
     78          col,
     79          end_lnum,
     80          end_col,
     81          vim.diagnostic.severity.HINT,
     82          source,
     83          code
     84        )
     85      end
     86 
     87      function _G.count_diagnostics(bufnr, severity, namespace)
     88        return #vim.diagnostic.get(bufnr, { severity = severity, namespace = namespace })
     89      end
     90 
     91      function _G.count_extmarks(bufnr, namespace)
     92        local ns = vim.diagnostic.get_namespace(namespace)
     93        local extmarks = 0
     94        if ns.user_data.virt_text_ns then
     95          extmarks = extmarks
     96            + #vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {})
     97        end
     98        if ns.user_data.underline_ns then
     99          extmarks = extmarks
    100            + #vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {})
    101        end
    102        return extmarks
    103      end
    104 
    105      function _G.get_virt_text_extmarks(ns)
    106        ns = vim.diagnostic.get_namespace(ns)
    107        local virt_text_ns = ns.user_data.virt_text_ns
    108        return vim.api.nvim_buf_get_extmarks(
    109          _G.diagnostic_bufnr,
    110          virt_text_ns,
    111          0,
    112          -1,
    113          { details = true }
    114        )
    115      end
    116 
    117      function _G.get_virt_lines_extmarks(ns)
    118        ns = vim.diagnostic.get_namespace(ns)
    119        local virt_lines_ns = ns.user_data.virt_lines_ns
    120        return vim.api.nvim_buf_get_extmarks(
    121          _G.diagnostic_bufnr,
    122          virt_lines_ns,
    123          0,
    124          -1,
    125          { details = true }
    126        )
    127      end
    128 
    129      ---@param ns integer
    130      function _G.get_underline_extmarks(ns)
    131        ---@type integer
    132        local underline_ns = vim.diagnostic.get_namespace(ns).user_data.underline_ns
    133        return vim.api.nvim_buf_get_extmarks(
    134          _G.diagnostic_bufnr,
    135          underline_ns,
    136          0,
    137          -1,
    138          { details = true }
    139        )
    140      end
    141    end)
    142 
    143    exec_lua(function()
    144      _G.diagnostic_ns = vim.api.nvim_create_namespace('diagnostic_spec')
    145      _G.other_ns = vim.api.nvim_create_namespace('other_namespace')
    146      _G.diagnostic_bufnr = vim.api.nvim_create_buf(true, false)
    147      local lines = { '1st line of text', '2nd line of text', 'wow', 'cool', 'more', 'lines' }
    148      vim.fn.bufload(_G.diagnostic_bufnr)
    149      vim.api.nvim_buf_set_lines(_G.diagnostic_bufnr, 0, 1, false, lines)
    150    end)
    151  end)
    152 
    153  it('creates highlight groups', function()
    154    command('runtime plugin/diagnostic.vim')
    155    eq({
    156      'DiagnosticDeprecated',
    157      'DiagnosticError',
    158      'DiagnosticFloatingError',
    159      'DiagnosticFloatingHint',
    160      'DiagnosticFloatingInfo',
    161      'DiagnosticFloatingOk',
    162      'DiagnosticFloatingWarn',
    163      'DiagnosticHint',
    164      'DiagnosticInfo',
    165      'DiagnosticOk',
    166      'DiagnosticSignError',
    167      'DiagnosticSignHint',
    168      'DiagnosticSignInfo',
    169      'DiagnosticSignOk',
    170      'DiagnosticSignWarn',
    171      'DiagnosticUnderlineError',
    172      'DiagnosticUnderlineHint',
    173      'DiagnosticUnderlineInfo',
    174      'DiagnosticUnderlineOk',
    175      'DiagnosticUnderlineWarn',
    176      'DiagnosticUnnecessary',
    177      'DiagnosticVirtualLinesError',
    178      'DiagnosticVirtualLinesHint',
    179      'DiagnosticVirtualLinesInfo',
    180      'DiagnosticVirtualLinesOk',
    181      'DiagnosticVirtualLinesWarn',
    182      'DiagnosticVirtualTextError',
    183      'DiagnosticVirtualTextHint',
    184      'DiagnosticVirtualTextInfo',
    185      'DiagnosticVirtualTextOk',
    186      'DiagnosticVirtualTextWarn',
    187      'DiagnosticWarn',
    188    }, fn.getcompletion('Diagnostic', 'highlight'))
    189  end)
    190 
    191  it('retrieves diagnostics from all buffers and namespaces', function()
    192    local result = exec_lua(function()
    193      local other_bufnr = vim.api.nvim_create_buf(true, false)
    194      local lines = vim.api.nvim_buf_get_lines(_G.diagnostic_bufnr, 0, -1, true)
    195      vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines)
    196 
    197      vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    198        _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    199        _G.make_error('Diagnostic #2', 2, 1, 2, 1),
    200      })
    201      vim.diagnostic.set(_G.other_ns, other_bufnr, {
    202        _G.make_error('Diagnostic #3', 3, 1, 3, 1),
    203      })
    204      return vim.diagnostic.get()
    205    end)
    206    eq(3, #result)
    207    eq(
    208      2,
    209      exec_lua(function()
    210        return #vim.tbl_filter(function(d)
    211          return d.bufnr == _G.diagnostic_bufnr
    212        end, result)
    213      end)
    214    )
    215    eq('Diagnostic #1', result[1].message)
    216  end)
    217 
    218  it('removes diagnostics from the cache when a buffer is removed', function()
    219    eq(
    220      2,
    221      exec_lua(function()
    222        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
    223        local other_bufnr = vim.fn.bufadd('test | test')
    224        local lines = vim.api.nvim_buf_get_lines(_G.diagnostic_bufnr, 0, -1, true)
    225        vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines)
    226        vim.cmd('bunload! ' .. other_bufnr)
    227 
    228        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    229          _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    230          _G.make_error('Diagnostic #2', 2, 1, 2, 1),
    231        })
    232        vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, {
    233          _G.make_error('Diagnostic #3', 3, 1, 3, 1),
    234        })
    235        vim.api.nvim_set_current_buf(other_bufnr)
    236        vim.opt_local.buflisted = true
    237        vim.cmd('bwipeout!')
    238        return #vim.diagnostic.get()
    239      end)
    240    )
    241    eq(
    242      2,
    243      exec_lua(function()
    244        vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
    245        vim.opt_local.buflisted = false
    246        return #vim.diagnostic.get()
    247      end)
    248    )
    249    eq(
    250      0,
    251      exec_lua(function()
    252        vim.cmd('bwipeout!')
    253        return #vim.diagnostic.get()
    254      end)
    255    )
    256  end)
    257 
    258  it('removes diagnostic from stale cache on reset', function()
    259    local diagnostics = exec_lua(function()
    260      vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    261        _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    262        _G.make_error('Diagnostic #2', 2, 1, 2, 1),
    263      })
    264      vim.fn.bufadd('test | test')
    265      vim.cmd('noautocmd bwipeout! ' .. _G.diagnostic_bufnr)
    266      return vim.diagnostic.get(_G.diagnostic_bufnr)
    267    end)
    268    eq(2, #diagnostics)
    269    diagnostics = exec_lua(function()
    270      vim.diagnostic.reset()
    271      return vim.diagnostic.get()
    272    end)
    273    eq(0, #diagnostics)
    274  end)
    275 
    276  it('always returns a copy of diagnostic tables', function()
    277    local result = exec_lua(function()
    278      vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    279        _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    280      })
    281      local diag = vim.diagnostic.get()
    282      diag[1].col = 10000
    283      return vim.diagnostic.get()[1].col == 10000
    284    end)
    285    eq(false, result)
    286  end)
    287 
    288  it('resolves buffer number 0 to the current buffer', function()
    289    eq(
    290      2,
    291      exec_lua(function()
    292        vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
    293        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    294          _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    295          _G.make_error('Diagnostic #2', 2, 1, 2, 1),
    296        })
    297        return #vim.diagnostic.get(0)
    298      end)
    299    )
    300  end)
    301 
    302  it('saves and count a single error', function()
    303    eq(
    304      1,
    305      exec_lua(function()
    306        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    307          _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    308        })
    309        return _G.count_diagnostics(
    310          _G.diagnostic_bufnr,
    311          vim.diagnostic.severity.ERROR,
    312          _G.diagnostic_ns
    313        )
    314      end)
    315    )
    316  end)
    317 
    318  it('saves and count multiple errors', function()
    319    eq(
    320      2,
    321      exec_lua(function()
    322        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    323          _G.make_error('Diagnostic #1', 1, 1, 1, 1),
    324          _G.make_error('Diagnostic #2', 2, 1, 2, 1),
    325        })
    326        return _G.count_diagnostics(
    327          _G.diagnostic_bufnr,
    328          vim.diagnostic.severity.ERROR,
    329          _G.diagnostic_ns
    330        )
    331      end)
    332    )
    333  end)
    334 
    335  it('saves and count from multiple namespaces', function()
    336    eq(
    337      { 1, 1, 2 },
    338      exec_lua(function()
    339        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    340          _G.make_error('Diagnostic From Server 1', 1, 1, 1, 1),
    341        })
    342        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, {
    343          _G.make_error('Diagnostic From Server 2', 1, 1, 1, 1),
    344        })
    345        return {
    346          -- First namespace
    347          _G.count_diagnostics(
    348            _G.diagnostic_bufnr,
    349            vim.diagnostic.severity.ERROR,
    350            _G.diagnostic_ns
    351          ),
    352          -- Second namespace
    353          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.other_ns),
    354          -- All namespaces
    355          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR),
    356        }
    357      end)
    358    )
    359  end)
    360 
    361  it('saves and count from multiple namespaces with respect to severity', function()
    362    eq(
    363      { 3, 0, 3 },
    364      exec_lua(function()
    365        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    366          _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
    367          _G.make_error('Diagnostic From Server 1:2', 2, 2, 2, 2),
    368          _G.make_error('Diagnostic From Server 1:3', 2, 3, 3, 2),
    369        })
    370        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, {
    371          _G.make_warning('Warning From Server 2', 3, 3, 3, 3),
    372        })
    373        return {
    374          -- Namespace 1
    375          _G.count_diagnostics(
    376            _G.diagnostic_bufnr,
    377            vim.diagnostic.severity.ERROR,
    378            _G.diagnostic_ns
    379          ),
    380          -- Namespace 2
    381          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.other_ns),
    382          -- All namespaces
    383          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR),
    384        }
    385      end)
    386    )
    387  end)
    388 
    389  it('handles one namespace clearing highlights while the other still has highlights', function()
    390    exec_lua(function()
    391      vim.diagnostic.config({ virtual_text = true })
    392    end)
    393    -- 1 Error (1)
    394    -- 1 Warning (2)
    395    -- 1 Warning (2) + 1 Warning (1)
    396    -- 2 highlights and 2 underlines (since error)
    397    -- 1 highlight + 1 underline
    398    local all_highlights = { 1, 1, 2, 4, 2 }
    399    eq(
    400      all_highlights,
    401      exec_lua(function()
    402        local ns_1_diags = {
    403          _G.make_error('Error 1', 1, 1, 1, 5),
    404          _G.make_warning('Warning on Server 1', 2, 1, 2, 3),
    405        }
    406        local ns_2_diags = {
    407          _G.make_warning('Warning 1', 2, 1, 2, 3),
    408        }
    409 
    410        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    411        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    412 
    413        return {
    414          _G.count_diagnostics(
    415            _G.diagnostic_bufnr,
    416            vim.diagnostic.severity.ERROR,
    417            _G.diagnostic_ns
    418          ),
    419          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns),
    420          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN),
    421          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    422          _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    423        }
    424      end)
    425    )
    426 
    427    -- Clear diagnostics from namespace 1, and make sure we have the right amount of stuff for namespace 2
    428    eq(
    429      { 1, 1, 2, 0, 2 },
    430      exec_lua(function()
    431        vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
    432        return {
    433          _G.count_diagnostics(
    434            _G.diagnostic_bufnr,
    435            vim.diagnostic.severity.ERROR,
    436            _G.diagnostic_ns
    437          ),
    438          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns),
    439          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN),
    440          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    441          _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    442        }
    443      end)
    444    )
    445 
    446    -- Show diagnostics from namespace 1 again
    447    eq(
    448      all_highlights,
    449      exec_lua(function()
    450        vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
    451        return {
    452          _G.count_diagnostics(
    453            _G.diagnostic_bufnr,
    454            vim.diagnostic.severity.ERROR,
    455            _G.diagnostic_ns
    456          ),
    457          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns),
    458          _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN),
    459          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    460          _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    461        }
    462      end)
    463    )
    464  end)
    465 
    466  it('does not display diagnostics when disabled', function()
    467    exec_lua(function()
    468      vim.diagnostic.config({ virtual_text = true })
    469    end)
    470 
    471    eq(
    472      { 0, 2 },
    473      exec_lua(function()
    474        local ns_1_diags = {
    475          _G.make_error('Error 1', 1, 1, 1, 5),
    476          _G.make_warning('Warning on Server 1', 2, 1, 2, 3),
    477        }
    478        local ns_2_diags = {
    479          _G.make_warning('Warning 1', 2, 1, 2, 3),
    480        }
    481 
    482        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    483        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    484 
    485        vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
    486 
    487        return {
    488          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    489          _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    490        }
    491      end)
    492    )
    493 
    494    eq(
    495      { 4, 0 },
    496      exec_lua(function()
    497        vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
    498        vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.other_ns })
    499 
    500        return {
    501          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    502          _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    503        }
    504      end)
    505    )
    506  end)
    507 
    508  describe('show() and hide()', function()
    509    it('works', function()
    510      local result = exec_lua(function()
    511        local other_bufnr = vim.api.nvim_create_buf(true, false)
    512 
    513        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
    514 
    515        local result = {}
    516 
    517        vim.diagnostic.config({ underline = false, virtual_text = true })
    518 
    519        local ns_1_diags = {
    520          _G.make_error('Error 1', 1, 1, 1, 5),
    521          _G.make_warning('Warning on Server 1', 2, 1, 2, 5),
    522        }
    523        local ns_2_diags = {
    524          _G.make_warning('Warning 1', 2, 1, 2, 5),
    525        }
    526        local other_buffer_diags = {
    527          _G.make_info('This is interesting', 0, 0, 0, 0),
    528        }
    529 
    530        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    531        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    532        vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags)
    533 
    534        -- All buffers and namespaces
    535        table.insert(
    536          result,
    537          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    538            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    539            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    540        )
    541 
    542        -- Hide one namespace
    543        vim.diagnostic.hide(_G.diagnostic_ns)
    544        table.insert(
    545          result,
    546          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    547            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    548            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    549        )
    550 
    551        -- Show one namespace
    552        vim.diagnostic.show(_G.diagnostic_ns)
    553        table.insert(
    554          result,
    555          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    556            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    557            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    558        )
    559 
    560        -- Hide one buffer
    561        vim.diagnostic.hide(nil, other_bufnr)
    562        table.insert(
    563          result,
    564          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    565            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    566            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    567        )
    568 
    569        -- Hide everything
    570        vim.diagnostic.hide()
    571        table.insert(
    572          result,
    573          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    574            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    575            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    576        )
    577 
    578        -- Show one buffer
    579        vim.diagnostic.show(nil, _G.diagnostic_bufnr)
    580        table.insert(
    581          result,
    582          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    583            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    584            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    585        )
    586 
    587        return result
    588      end)
    589 
    590      eq(4, result[1])
    591      eq(1, result[2])
    592      eq(4, result[3])
    593      eq(3, result[4])
    594      eq(0, result[5])
    595      eq(3, result[6])
    596    end)
    597 
    598    it("doesn't error after bwipeout on buffer", function()
    599      exec_lua(function()
    600        vim.diagnostic.set(
    601          _G.diagnostic_ns,
    602          _G.diagnostic_bufnr,
    603          { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } }
    604        )
    605        vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr)
    606 
    607        vim.diagnostic.show(_G.diagnostic_ns)
    608        vim.diagnostic.hide(_G.diagnostic_ns)
    609      end)
    610    end)
    611 
    612    it('handles diagnostics without extmark_id', function()
    613      exec_lua(function()
    614        vim.diagnostic.config({ virtual_text = true })
    615 
    616        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
    617          _G.make_error('Error message', 0, 0, 0, 5),
    618        })
    619 
    620        local diags = vim.diagnostic.get(_G.diagnostic_bufnr, { namespace = _G.diagnostic_ns })
    621        diags[1]._extmark_id = nil
    622 
    623        vim.diagnostic.show(_G.diagnostic_ns, _G.diagnostic_bufnr, diags)
    624      end)
    625    end)
    626  end)
    627 
    628  describe('enable() and disable()', function()
    629    it('validation', function()
    630      matches('expected boolean, got table', pcall_err(exec_lua, [[vim.diagnostic.enable({})]]))
    631      matches(
    632        'filter: expected table, got string',
    633        pcall_err(exec_lua, [[vim.diagnostic.enable(false, '')]])
    634      )
    635      matches(
    636        'Invalid buffer id: 42',
    637        pcall_err(exec_lua, [[vim.diagnostic.enable(true, { bufnr = 42 })]])
    638      )
    639      matches(
    640        'expected boolean, got number',
    641        pcall_err(exec_lua, [[vim.diagnostic.enable(42, {})]])
    642      )
    643      matches('expected boolean, got table', pcall_err(exec_lua, [[vim.diagnostic.enable({}, 42)]]))
    644    end)
    645 
    646    it('without arguments', function()
    647      local result = exec_lua(function()
    648        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
    649 
    650        local result = {}
    651 
    652        vim.diagnostic.config({ underline = false, virtual_text = true })
    653 
    654        local ns_1_diags = {
    655          _G.make_error('Error 1', 1, 1, 1, 5),
    656          _G.make_warning('Warning on Server 1', 2, 1, 2, 5),
    657        }
    658        local ns_2_diags = {
    659          _G.make_warning('Warning 1', 2, 1, 2, 5),
    660        }
    661 
    662        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    663        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    664 
    665        table.insert(
    666          result,
    667          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    668            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    669        )
    670 
    671        vim.diagnostic.enable(false)
    672 
    673        table.insert(
    674          result,
    675          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    676            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    677        )
    678 
    679        -- Create a new buffer
    680        local other_bufnr = vim.api.nvim_create_buf(true, false)
    681        local other_buffer_diags = {
    682          _G.make_info('This is interesting', 0, 0, 0, 0),
    683        }
    684 
    685        vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags)
    686 
    687        table.insert(
    688          result,
    689          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    690            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    691            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    692        )
    693 
    694        vim.diagnostic.enable()
    695 
    696        table.insert(
    697          result,
    698          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    699            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    700            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    701        )
    702 
    703        return result
    704      end)
    705 
    706      eq(3, result[1])
    707      eq(0, result[2])
    708      eq(0, result[3])
    709      eq(4, result[4])
    710    end)
    711 
    712    it('with buffer argument', function()
    713      local result = exec_lua(function()
    714        local other_bufnr = vim.api.nvim_create_buf(true, false)
    715 
    716        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
    717 
    718        local result = {}
    719 
    720        vim.diagnostic.config({ underline = false, virtual_text = true })
    721 
    722        local ns_1_diags = {
    723          _G.make_error('Error 1', 1, 1, 1, 5),
    724          _G.make_warning('Warning on Server 1', 2, 1, 2, 5),
    725        }
    726        local ns_2_diags = {
    727          _G.make_warning('Warning 1', 2, 1, 2, 5),
    728        }
    729        local other_buffer_diags = {
    730          _G.make_info('This is interesting', 0, 0, 0, 0),
    731        }
    732 
    733        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    734        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    735        vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags)
    736 
    737        table.insert(
    738          result,
    739          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    740            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    741            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    742        )
    743 
    744        vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr })
    745 
    746        table.insert(
    747          result,
    748          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    749            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    750            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    751        )
    752 
    753        vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr })
    754 
    755        table.insert(
    756          result,
    757          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    758            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    759            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    760        )
    761 
    762        vim.diagnostic.enable(false, { bufnr = other_bufnr })
    763 
    764        table.insert(
    765          result,
    766          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    767            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    768            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    769        )
    770 
    771        return result
    772      end)
    773 
    774      eq(4, result[1])
    775      eq(1, result[2])
    776      eq(4, result[3])
    777      eq(3, result[4])
    778    end)
    779 
    780    it('with a namespace argument', function()
    781      local result = exec_lua(function()
    782        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
    783 
    784        local result = {}
    785 
    786        vim.diagnostic.config({ underline = false, virtual_text = true })
    787 
    788        local ns_1_diags = {
    789          _G.make_error('Error 1', 1, 1, 1, 5),
    790          _G.make_warning('Warning on Server 1', 2, 1, 2, 5),
    791        }
    792        local ns_2_diags = {
    793          _G.make_warning('Warning 1', 2, 1, 2, 5),
    794        }
    795 
    796        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    797        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    798 
    799        table.insert(
    800          result,
    801          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    802            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    803        )
    804 
    805        vim.diagnostic.enable(false, { ns_id = _G.diagnostic_ns })
    806 
    807        table.insert(
    808          result,
    809          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    810            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    811        )
    812 
    813        vim.diagnostic.enable(true, { ns_id = _G.diagnostic_ns })
    814 
    815        table.insert(
    816          result,
    817          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    818            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    819        )
    820 
    821        vim.diagnostic.enable(false, { ns_id = _G.other_ns })
    822 
    823        table.insert(
    824          result,
    825          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    826            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    827        )
    828 
    829        return result
    830      end)
    831 
    832      eq(3, result[1])
    833      eq(1, result[2])
    834      eq(3, result[3])
    835      eq(2, result[4])
    836    end)
    837 
    838    --- @return table
    839    local function test_enable()
    840      return exec_lua(function()
    841        local other_bufnr = vim.api.nvim_create_buf(true, false)
    842 
    843        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
    844 
    845        local result = {}
    846 
    847        vim.diagnostic.config({ underline = false, virtual_text = true })
    848 
    849        local ns_1_diags = {
    850          _G.make_error('Error 1', 1, 1, 1, 5),
    851          _G.make_warning('Warning on Server 1', 2, 1, 2, 5),
    852        }
    853        local ns_2_diags = {
    854          _G.make_warning('Warning 1', 2, 1, 2, 5),
    855        }
    856        local other_buffer_diags = {
    857          _G.make_info('This is interesting', 0, 0, 0, 0),
    858        }
    859 
    860        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    861        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    862        vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags)
    863 
    864        table.insert(
    865          result,
    866          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    867            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    868            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    869        )
    870 
    871        vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
    872 
    873        table.insert(
    874          result,
    875          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    876            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    877            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    878        )
    879 
    880        vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.other_ns })
    881 
    882        table.insert(
    883          result,
    884          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    885            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    886            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    887        )
    888 
    889        vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
    890 
    891        table.insert(
    892          result,
    893          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    894            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    895            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    896        )
    897 
    898        -- Should have no effect
    899        vim.diagnostic.enable(false, { bufnr = other_bufnr, ns_id = _G.other_ns })
    900 
    901        table.insert(
    902          result,
    903          _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
    904            + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns)
    905            + _G.count_extmarks(other_bufnr, _G.diagnostic_ns)
    906        )
    907 
    908        return result
    909      end)
    910    end
    911 
    912    it('with both buffer and namespace arguments', function()
    913      local result = test_enable()
    914      eq(4, result[1])
    915      eq(2, result[2])
    916      eq(1, result[3])
    917      eq(3, result[4])
    918      eq(3, result[5])
    919    end)
    920  end)
    921 
    922  describe('reset()', function()
    923    it('diagnostic count is 0 and displayed diagnostics are 0 after call', function()
    924      exec_lua(function()
    925        vim.diagnostic.config({ virtual_text = true })
    926      end)
    927 
    928      -- 1 Error (1)
    929      -- 1 Warning (2)
    930      -- 1 Warning (2) + 1 Warning (1)
    931      -- 2 highlights and 2 underlines (since error)
    932      -- 1 highlight + 1 underline
    933      local all_highlights = { 1, 1, 2, 4, 2 }
    934      eq(
    935        all_highlights,
    936        exec_lua(function()
    937          local ns_1_diags = {
    938            _G.make_error('Error 1', 1, 1, 1, 5),
    939            _G.make_warning('Warning on Server 1', 2, 1, 2, 3),
    940          }
    941          local ns_2_diags = {
    942            _G.make_warning('Warning 1', 2, 1, 2, 3),
    943          }
    944 
    945          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags)
    946          vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags)
    947 
    948          return {
    949            _G.count_diagnostics(
    950              _G.diagnostic_bufnr,
    951              vim.diagnostic.severity.ERROR,
    952              _G.diagnostic_ns
    953            ),
    954            _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns),
    955            _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN),
    956            _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    957            _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    958          }
    959        end)
    960      )
    961 
    962      -- Reset diagnostics from namespace 1
    963      exec_lua([[ vim.diagnostic.reset( _G.diagnostic_ns) ]])
    964 
    965      -- Make sure we have the right diagnostic count
    966      eq(
    967        { 0, 1, 1, 0, 2 },
    968        exec_lua(function()
    969          local diagnostic_count = {}
    970          vim.wait(100, function()
    971            diagnostic_count = {
    972              _G.count_diagnostics(
    973                _G.diagnostic_bufnr,
    974                vim.diagnostic.severity.ERROR,
    975                _G.diagnostic_ns
    976              ),
    977              _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns),
    978              _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN),
    979              _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
    980              _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
    981            }
    982          end)
    983          return diagnostic_count
    984        end)
    985      )
    986 
    987      -- Reset diagnostics from namespace 2
    988      exec_lua([[ vim.diagnostic.reset(_G.other_ns) ]])
    989 
    990      -- Make sure we have the right diagnostic count
    991      eq(
    992        { 0, 0, 0, 0, 0 },
    993        exec_lua(function()
    994          local diagnostic_count = {}
    995          vim.wait(100, function()
    996            diagnostic_count = {
    997              _G.count_diagnostics(
    998                _G.diagnostic_bufnr,
    999                vim.diagnostic.severity.ERROR,
   1000                _G.diagnostic_ns
   1001              ),
   1002              _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns),
   1003              _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN),
   1004              _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns),
   1005              _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns),
   1006            }
   1007          end)
   1008          return diagnostic_count
   1009        end)
   1010      )
   1011    end)
   1012 
   1013    it("doesn't error after bwipeout called on buffer", function()
   1014      exec_lua(function()
   1015        vim.diagnostic.set(
   1016          _G.diagnostic_ns,
   1017          _G.diagnostic_bufnr,
   1018          { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } }
   1019        )
   1020        vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr)
   1021 
   1022        vim.diagnostic.reset(_G.diagnostic_ns)
   1023      end)
   1024    end)
   1025  end)
   1026 
   1027  describe('get_next()', function()
   1028    it('can find the next pos with only one namespace', function()
   1029      eq(
   1030        { 1, 1 },
   1031        exec_lua(function()
   1032          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1033            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   1034          })
   1035          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1036          local next = vim.diagnostic.get_next()
   1037          return { next.lnum, next.col }
   1038        end)
   1039      )
   1040    end)
   1041 
   1042    it('can find next pos with two errors', function()
   1043      eq(
   1044        { 4, 4 },
   1045        exec_lua(function()
   1046          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1047            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   1048            _G.make_error('Diagnostic #2', 4, 4, 4, 4),
   1049          })
   1050          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1051          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1052          local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns })
   1053          return { next.lnum, next.col }
   1054        end)
   1055      )
   1056    end)
   1057 
   1058    it('can cycle when position is past error', function()
   1059      eq(
   1060        { 1, 1 },
   1061        exec_lua(function()
   1062          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1063            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   1064          })
   1065          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1066          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1067          local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns })
   1068          return { next.lnum, next.col }
   1069        end)
   1070      )
   1071    end)
   1072 
   1073    it('will not cycle when wrap is off', function()
   1074      eq(
   1075        nil,
   1076        exec_lua(function()
   1077          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1078            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   1079          })
   1080          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1081          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1082          local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns, wrap = false })
   1083          return next
   1084        end)
   1085      )
   1086    end)
   1087 
   1088    it('can cycle even from the last line', function()
   1089      eq(
   1090        { 4, 4 },
   1091        exec_lua(function()
   1092          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1093            _G.make_error('Diagnostic #2', 4, 4, 4, 4),
   1094          })
   1095          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1096          vim.api.nvim_win_set_cursor(0, { vim.api.nvim_buf_line_count(0), 1 })
   1097          local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns })
   1098          return { prev.lnum, prev.col }
   1099        end)
   1100      )
   1101    end)
   1102 
   1103    it('works with diagnostics past the end of the line #16349', function()
   1104      eq(
   1105        { 4, 0 },
   1106        exec_lua(function()
   1107          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1108            _G.make_error('Diagnostic #1', 3, 9001, 3, 9001),
   1109            _G.make_error('Diagnostic #2', 4, 0, 4, 0),
   1110          })
   1111          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1112          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   1113          vim.diagnostic.jump({ count = 1 })
   1114          local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns })
   1115          return { next.lnum, next.col }
   1116        end)
   1117      )
   1118    end)
   1119 
   1120    it('works with diagnostics before the start of the line', function()
   1121      eq(
   1122        { 4, 0 },
   1123        exec_lua(function()
   1124          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1125            _G.make_error('Diagnostic #1', 3, 9001, 3, 9001),
   1126            _G.make_error('Diagnostic #2', 4, -1, 4, -1),
   1127          })
   1128          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1129          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   1130          vim.diagnostic.jump({ count = 1 })
   1131          local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns })
   1132          return { next.lnum, next.col }
   1133        end)
   1134      )
   1135    end)
   1136 
   1137    it('jumps to diagnostic with highest severity', function()
   1138      exec_lua(function()
   1139        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1140          _G.make_info('Info', 1, 0, 1, 1),
   1141          _G.make_error('Error', 2, 0, 2, 1),
   1142          _G.make_warning('Warning', 3, 0, 3, 1),
   1143          _G.make_error('Error', 4, 0, 4, 1),
   1144        })
   1145 
   1146        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1147        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1148      end)
   1149 
   1150      eq(
   1151        { 3, 0 },
   1152        exec_lua(function()
   1153          vim.diagnostic.jump({ count = 1, _highest = true })
   1154          return vim.api.nvim_win_get_cursor(0)
   1155        end)
   1156      )
   1157 
   1158      eq(
   1159        { 5, 0 },
   1160        exec_lua(function()
   1161          vim.diagnostic.jump({ count = 1, _highest = true })
   1162          return vim.api.nvim_win_get_cursor(0)
   1163        end)
   1164      )
   1165 
   1166      exec_lua(function()
   1167        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1168          _G.make_info('Info', 1, 0, 1, 1),
   1169          _G.make_hint('Hint', 2, 0, 2, 1),
   1170          _G.make_warning('Warning', 3, 0, 3, 1),
   1171          _G.make_hint('Hint', 4, 0, 4, 1),
   1172          _G.make_warning('Warning', 5, 0, 5, 1),
   1173        })
   1174 
   1175        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1176        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1177      end)
   1178 
   1179      eq(
   1180        { 4, 0 },
   1181        exec_lua(function()
   1182          vim.diagnostic.jump({ count = 1, _highest = true })
   1183          return vim.api.nvim_win_get_cursor(0)
   1184        end)
   1185      )
   1186 
   1187      eq(
   1188        { 6, 0 },
   1189        exec_lua(function()
   1190          vim.diagnostic.jump({ count = 1, _highest = true })
   1191          return vim.api.nvim_win_get_cursor(0)
   1192        end)
   1193      )
   1194    end)
   1195 
   1196    it('jumps to next diagnostic if severity is non-nil', function()
   1197      exec_lua(function()
   1198        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1199          _G.make_info('Info', 1, 0, 1, 1),
   1200          _G.make_error('Error', 2, 0, 2, 1),
   1201          _G.make_warning('Warning', 3, 0, 3, 1),
   1202          _G.make_error('Error', 4, 0, 4, 1),
   1203        })
   1204 
   1205        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1206        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1207      end)
   1208 
   1209      eq(
   1210        { 2, 0 },
   1211        exec_lua(function()
   1212          vim.diagnostic.jump({ count = 1 })
   1213          return vim.api.nvim_win_get_cursor(0)
   1214        end)
   1215      )
   1216 
   1217      eq(
   1218        { 3, 0 },
   1219        exec_lua(function()
   1220          vim.diagnostic.jump({ count = 1 })
   1221          return vim.api.nvim_win_get_cursor(0)
   1222        end)
   1223      )
   1224 
   1225      eq(
   1226        { 4, 0 },
   1227        exec_lua(function()
   1228          vim.diagnostic.jump({ count = 1 })
   1229          return vim.api.nvim_win_get_cursor(0)
   1230        end)
   1231      )
   1232    end)
   1233  end)
   1234 
   1235  describe('get_prev()', function()
   1236    it('can find the previous diagnostic with only one namespace', function()
   1237      eq(
   1238        { 1, 1 },
   1239        exec_lua(function()
   1240          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1241            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   1242          })
   1243          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1244          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1245          local prev = vim.diagnostic.get_prev()
   1246          return { prev.lnum, prev.col }
   1247        end)
   1248      )
   1249    end)
   1250 
   1251    it('can find the previous diagnostic with two errors', function()
   1252      eq(
   1253        { 1, 1 },
   1254        exec_lua(function()
   1255          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1256            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   1257            _G.make_error('Diagnostic #2', 4, 4, 4, 4),
   1258          })
   1259          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1260          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1261          local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns })
   1262          return { prev.lnum, prev.col }
   1263        end)
   1264      )
   1265    end)
   1266 
   1267    it('can cycle when position is past error', function()
   1268      eq(
   1269        { 4, 4 },
   1270        exec_lua(function()
   1271          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1272            _G.make_error('Diagnostic #2', 4, 4, 4, 4),
   1273          })
   1274          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1275          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1276          local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns })
   1277          return { prev.lnum, prev.col }
   1278        end)
   1279      )
   1280    end)
   1281 
   1282    it('respects wrap parameter', function()
   1283      eq(
   1284        nil,
   1285        exec_lua(function()
   1286          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1287            _G.make_error('Diagnostic #2', 4, 4, 4, 4),
   1288          })
   1289          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1290          vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1291          local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns, wrap = false })
   1292          return prev
   1293        end)
   1294      )
   1295    end)
   1296 
   1297    it('works on blank line #28397', function()
   1298      eq(
   1299        { 0, 2 },
   1300        exec_lua(function()
   1301          local test_bufnr = vim.api.nvim_create_buf(true, false)
   1302          vim.api.nvim_buf_set_lines(test_bufnr, 0, -1, false, {
   1303            'first line',
   1304            '',
   1305            '',
   1306            'end line',
   1307          })
   1308          vim.diagnostic.set(_G.diagnostic_ns, test_bufnr, {
   1309            _G.make_info('Diagnostic #1', 0, 2, 0, 2),
   1310            _G.make_info('Diagnostic #2', 2, 0, 2, 0),
   1311            _G.make_info('Diagnostic #3', 2, 0, 2, 0),
   1312          })
   1313          vim.api.nvim_win_set_buf(0, test_bufnr)
   1314          vim.api.nvim_win_set_cursor(0, { 3, 0 })
   1315          return vim.diagnostic.get_prev_pos { namespace = _G.diagnostic_ns }
   1316        end)
   1317      )
   1318    end)
   1319  end)
   1320 
   1321  describe('jump()', function()
   1322    before_each(function()
   1323      exec_lua(function()
   1324        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1325          _G.make_error('Diagnostic #1', 0, 0, 0, 2),
   1326          _G.make_error('Diagnostic #2', 1, 1, 1, 4),
   1327          _G.make_warning('Diagnostic #3', 2, -1, 2, -1),
   1328          _G.make_info('Diagnostic #4', 3, 0, 3, 3),
   1329        })
   1330        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   1331      end)
   1332    end)
   1333 
   1334    it('can move forward', function()
   1335      eq(
   1336        { 2, 1 },
   1337        exec_lua(function()
   1338          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1339          vim.diagnostic.jump({ count = 1 })
   1340          return vim.api.nvim_win_get_cursor(0)
   1341        end)
   1342      )
   1343 
   1344      eq(
   1345        { 4, 0 },
   1346        exec_lua(function()
   1347          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1348          vim.diagnostic.jump({ count = 3 })
   1349          return vim.api.nvim_win_get_cursor(0)
   1350        end)
   1351      )
   1352 
   1353      eq(
   1354        { 4, 0 },
   1355        exec_lua(function()
   1356          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1357          vim.diagnostic.jump({ count = math.huge, wrap = false })
   1358          return vim.api.nvim_win_get_cursor(0)
   1359        end)
   1360      )
   1361    end)
   1362 
   1363    it('can move backward', function()
   1364      eq(
   1365        { 3, 0 },
   1366        exec_lua(function()
   1367          vim.api.nvim_win_set_cursor(0, { 4, 0 })
   1368          vim.diagnostic.jump({ count = -1 })
   1369          return vim.api.nvim_win_get_cursor(0)
   1370        end)
   1371      )
   1372 
   1373      eq(
   1374        { 1, 0 },
   1375        exec_lua(function()
   1376          vim.api.nvim_win_set_cursor(0, { 4, 0 })
   1377          vim.diagnostic.jump({ count = -3 })
   1378          return vim.api.nvim_win_get_cursor(0)
   1379        end)
   1380      )
   1381 
   1382      eq(
   1383        { 1, 0 },
   1384        exec_lua(function()
   1385          vim.api.nvim_win_set_cursor(0, { 4, 0 })
   1386          vim.diagnostic.jump({ count = -math.huge, wrap = false })
   1387          return vim.api.nvim_win_get_cursor(0)
   1388        end)
   1389      )
   1390    end)
   1391 
   1392    it('can filter by severity', function()
   1393      eq(
   1394        { 3, 0 },
   1395        exec_lua(function()
   1396          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1397          vim.diagnostic.jump({ count = 1, severity = vim.diagnostic.severity.WARN })
   1398          return vim.api.nvim_win_get_cursor(0)
   1399        end)
   1400      )
   1401 
   1402      eq(
   1403        { 3, 0 },
   1404        exec_lua(function()
   1405          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1406          vim.diagnostic.jump({ count = 9999, severity = vim.diagnostic.severity.WARN })
   1407          return vim.api.nvim_win_get_cursor(0)
   1408        end)
   1409      )
   1410    end)
   1411 
   1412    it('can wrap', function()
   1413      eq(
   1414        { 1, 0 },
   1415        exec_lua(function()
   1416          vim.api.nvim_win_set_cursor(0, { 4, 0 })
   1417          vim.diagnostic.jump({ count = 1, wrap = true })
   1418          return vim.api.nvim_win_get_cursor(0)
   1419        end)
   1420      )
   1421 
   1422      eq(
   1423        { 4, 0 },
   1424        exec_lua(function()
   1425          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1426          vim.diagnostic.jump({ count = -1, wrap = true })
   1427          return vim.api.nvim_win_get_cursor(0)
   1428        end)
   1429      )
   1430    end)
   1431 
   1432    it('supports on_jump() handler', function()
   1433      exec_lua(function()
   1434        _G.jumped = false
   1435 
   1436        vim.diagnostic.jump({
   1437          count = 1,
   1438          on_jump = function()
   1439            _G.jumped = true
   1440          end,
   1441        })
   1442      end)
   1443 
   1444      retry(nil, nil, function()
   1445        eq(true, exec_lua('return _G.jumped'))
   1446      end)
   1447    end)
   1448 
   1449    describe('after inserting text before diagnostic position', function()
   1450      before_each(function()
   1451        api.nvim_buf_set_text(0, 3, 0, 3, 0, { 'new line', 'new ' })
   1452      end)
   1453 
   1454      it('finds next diagnostic at a logical location', function()
   1455        eq(
   1456          { 5, 4 },
   1457          exec_lua(function()
   1458            vim.api.nvim_win_set_cursor(0, { 3, 1 })
   1459            vim.diagnostic.jump({ count = 1 })
   1460            return vim.api.nvim_win_get_cursor(0)
   1461          end)
   1462        )
   1463      end)
   1464 
   1465      it('finds previous diagnostic at a logical location', function()
   1466        eq(
   1467          { 5, 4 },
   1468          exec_lua(function()
   1469            vim.api.nvim_win_set_cursor(0, { 6, 4 })
   1470            vim.diagnostic.jump({ count = -1 })
   1471            return vim.api.nvim_win_get_cursor(0)
   1472          end)
   1473        )
   1474      end)
   1475    end)
   1476 
   1477    describe('if diagnostic is set after last character in line', function()
   1478      before_each(function()
   1479        exec_lua(function()
   1480          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1481            _G.make_error('Diagnostic #1', 2, 3, 3, 4),
   1482          })
   1483        end)
   1484      end)
   1485 
   1486      it('finds next diagnostic at the end of the line', function()
   1487        eq(
   1488          { 3, 2 },
   1489          exec_lua(function()
   1490            vim.api.nvim_win_set_cursor(0, { 3, 0 })
   1491            vim.diagnostic.jump({ count = 1 })
   1492            return vim.api.nvim_win_get_cursor(0)
   1493          end)
   1494        )
   1495      end)
   1496 
   1497      it('finds previous diagnostic at the end of the line', function()
   1498        eq(
   1499          { 3, 2 },
   1500          exec_lua(function()
   1501            vim.api.nvim_win_set_cursor(0, { 4, 2 })
   1502            vim.diagnostic.jump({ count = -1 })
   1503            return vim.api.nvim_win_get_cursor(0)
   1504          end)
   1505        )
   1506      end)
   1507    end)
   1508 
   1509    describe('after entire text range with a diagnostic was deleted', function()
   1510      before_each(function()
   1511        api.nvim_buf_set_text(0, 1, 1, 1, 4, {})
   1512      end)
   1513 
   1514      it('does not find next diagnostic inside the deleted range', function()
   1515        eq(
   1516          { 3, 0 },
   1517          exec_lua(function()
   1518            vim.api.nvim_win_set_cursor(0, { 1, 0 })
   1519            vim.diagnostic.jump({ count = 1 })
   1520            return vim.api.nvim_win_get_cursor(0)
   1521          end)
   1522        )
   1523      end)
   1524 
   1525      it('does not find previous diagnostic inside the deleted range', function()
   1526        eq(
   1527          { 1, 0 },
   1528          exec_lua(function()
   1529            vim.api.nvim_win_set_cursor(0, { 3, 0 })
   1530            vim.diagnostic.jump({ count = -1 })
   1531            return vim.api.nvim_win_get_cursor(0)
   1532          end)
   1533        )
   1534      end)
   1535    end)
   1536  end)
   1537 
   1538  describe('get()', function()
   1539    it('returns an empty table when no diagnostics are present', function()
   1540      eq(
   1541        {},
   1542        exec_lua [[return vim.diagnostic.get( _G.diagnostic_bufnr, {namespace=diagnostic_ns})]]
   1543      )
   1544    end)
   1545 
   1546    it('returns all diagnostics when no severity is supplied', function()
   1547      eq(
   1548        2,
   1549        exec_lua(function()
   1550          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1551            _G.make_error('Error 1', 1, 1, 1, 5),
   1552            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1553          })
   1554 
   1555          return #vim.diagnostic.get(_G.diagnostic_bufnr)
   1556        end)
   1557      )
   1558    end)
   1559 
   1560    it('returns only requested diagnostics when severity range is supplied', function()
   1561      eq(
   1562        { 2, 3, 2 },
   1563        exec_lua(function()
   1564          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1565            _G.make_error('Error 1', 1, 1, 1, 5),
   1566            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1567            _G.make_info('Ignored information', 1, 1, 2, 3),
   1568            _G.make_hint("Here's a hint", 1, 1, 2, 3),
   1569          })
   1570 
   1571          return {
   1572            #vim.diagnostic.get(
   1573              _G.diagnostic_bufnr,
   1574              { severity = { min = vim.diagnostic.severity.WARN } }
   1575            ),
   1576            #vim.diagnostic.get(
   1577              _G.diagnostic_bufnr,
   1578              { severity = { max = vim.diagnostic.severity.WARN } }
   1579            ),
   1580            #vim.diagnostic.get(_G.diagnostic_bufnr, {
   1581              severity = {
   1582                min = vim.diagnostic.severity.INFO,
   1583                max = vim.diagnostic.severity.WARN,
   1584              },
   1585            }),
   1586          }
   1587        end)
   1588      )
   1589    end)
   1590 
   1591    it('returns only requested diagnostics when severities are supplied', function()
   1592      eq(
   1593        { 1, 1, 2 },
   1594        exec_lua(function()
   1595          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1596            _G.make_error('Error 1', 1, 1, 1, 5),
   1597            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1598            _G.make_info('Ignored information', 1, 1, 2, 3),
   1599            _G.make_hint("Here's a hint", 1, 1, 2, 3),
   1600          })
   1601 
   1602          return {
   1603            #vim.diagnostic.get(
   1604              _G.diagnostic_bufnr,
   1605              { severity = { vim.diagnostic.severity.WARN } }
   1606            ),
   1607            #vim.diagnostic.get(
   1608              _G.diagnostic_bufnr,
   1609              { severity = { vim.diagnostic.severity.ERROR } }
   1610            ),
   1611            #vim.diagnostic.get(_G.diagnostic_bufnr, {
   1612              severity = {
   1613                vim.diagnostic.severity.INFO,
   1614                vim.diagnostic.severity.WARN,
   1615              },
   1616            }),
   1617          }
   1618        end)
   1619      )
   1620    end)
   1621 
   1622    it('allows filtering by line', function()
   1623      eq(
   1624        2,
   1625        exec_lua(function()
   1626          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1627            _G.make_error('Error 1', 1, 1, 1, 5),
   1628            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1629            _G.make_info('Ignored information', 1, 1, 2, 3),
   1630            _G.make_error('Error On Other Line', 3, 1, 3, 5),
   1631          })
   1632 
   1633          return #vim.diagnostic.get(_G.diagnostic_bufnr, { lnum = 2 })
   1634        end)
   1635      )
   1636    end)
   1637 
   1638    it('allows filtering by enablement', function()
   1639      eq(
   1640        { 3, 1, 2 },
   1641        exec_lua(function()
   1642          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1643            _G.make_error('Error 1', 1, 1, 1, 5),
   1644          })
   1645          vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, {
   1646            _G.make_error('Error 2', 1, 1, 1, 5),
   1647            _G.make_error('Error 3', 3, 1, 3, 5),
   1648          })
   1649 
   1650          vim.diagnostic.enable(false, { ns_id = _G.other_ns })
   1651 
   1652          return {
   1653            #vim.diagnostic.get(_G.diagnostic_bufnr),
   1654            #vim.diagnostic.get(_G.diagnostic_bufnr, { enabled = true }),
   1655            #vim.diagnostic.get(_G.diagnostic_bufnr, { enabled = false }),
   1656          }
   1657        end)
   1658      )
   1659    end)
   1660  end)
   1661 
   1662  describe('count', function()
   1663    it('returns actually present severity counts', function()
   1664      eq(
   1665        exec_lua(function()
   1666          return {
   1667            [vim.diagnostic.severity.ERROR] = 4,
   1668            [vim.diagnostic.severity.WARN] = 3,
   1669            [vim.diagnostic.severity.INFO] = 2,
   1670            [vim.diagnostic.severity.HINT] = 1,
   1671          }
   1672        end),
   1673        exec_lua(function()
   1674          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1675            _G.make_error('Error 1', 1, 1, 1, 2),
   1676            _G.make_error('Error 2', 1, 3, 1, 4),
   1677            _G.make_error('Error 3', 1, 5, 1, 6),
   1678            _G.make_error('Error 4', 1, 7, 1, 8),
   1679            _G.make_warning('Warning 1', 2, 1, 2, 2),
   1680            _G.make_warning('Warning 2', 2, 3, 2, 4),
   1681            _G.make_warning('Warning 3', 2, 5, 2, 6),
   1682            _G.make_info('Info 1', 3, 1, 3, 2),
   1683            _G.make_info('Info 2', 3, 3, 3, 4),
   1684            _G.make_hint('Hint 1', 4, 1, 4, 2),
   1685          })
   1686          return vim.diagnostic.count(_G.diagnostic_bufnr)
   1687        end)
   1688      )
   1689      eq(
   1690        exec_lua(function()
   1691          return {
   1692            [vim.diagnostic.severity.ERROR] = 2,
   1693            [vim.diagnostic.severity.INFO] = 1,
   1694          }
   1695        end),
   1696        exec_lua(function()
   1697          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1698            _G.make_error('Error 1', 1, 1, 1, 2),
   1699            _G.make_error('Error 2', 1, 3, 1, 4),
   1700            _G.make_info('Info 1', 3, 1, 3, 2),
   1701          })
   1702          return vim.diagnostic.count(_G.diagnostic_bufnr)
   1703        end)
   1704      )
   1705    end)
   1706 
   1707    it('returns only requested diagnostics count when severity range is supplied', function()
   1708      eq(
   1709        exec_lua(function()
   1710          return {
   1711            { [vim.diagnostic.severity.ERROR] = 1, [vim.diagnostic.severity.WARN] = 1 },
   1712            {
   1713              [vim.diagnostic.severity.WARN] = 1,
   1714              [vim.diagnostic.severity.INFO] = 1,
   1715              [vim.diagnostic.severity.HINT] = 1,
   1716            },
   1717            { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1 },
   1718          }
   1719        end),
   1720        exec_lua(function()
   1721          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1722            _G.make_error('Error 1', 1, 1, 1, 5),
   1723            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1724            _G.make_info('Ignored information', 1, 1, 2, 3),
   1725            _G.make_hint("Here's a hint", 1, 1, 2, 3),
   1726          })
   1727 
   1728          return {
   1729            vim.diagnostic.count(
   1730              _G.diagnostic_bufnr,
   1731              { severity = { min = vim.diagnostic.severity.WARN } }
   1732            ),
   1733            vim.diagnostic.count(
   1734              _G.diagnostic_bufnr,
   1735              { severity = { max = vim.diagnostic.severity.WARN } }
   1736            ),
   1737            vim.diagnostic.count(_G.diagnostic_bufnr, {
   1738              severity = {
   1739                min = vim.diagnostic.severity.INFO,
   1740                max = vim.diagnostic.severity.WARN,
   1741              },
   1742            }),
   1743          }
   1744        end)
   1745      )
   1746    end)
   1747 
   1748    it('returns only requested diagnostics when severities are supplied', function()
   1749      eq(
   1750        exec_lua(function()
   1751          return {
   1752            { [vim.diagnostic.severity.WARN] = 1 },
   1753            { [vim.diagnostic.severity.ERROR] = 1 },
   1754            { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1 },
   1755          }
   1756        end),
   1757        exec_lua(function()
   1758          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1759            _G.make_error('Error 1', 1, 1, 1, 5),
   1760            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1761            _G.make_info('Ignored information', 1, 1, 2, 3),
   1762            _G.make_hint("Here's a hint", 1, 1, 2, 3),
   1763          })
   1764 
   1765          return {
   1766            vim.diagnostic.count(
   1767              _G.diagnostic_bufnr,
   1768              { severity = { vim.diagnostic.severity.WARN } }
   1769            ),
   1770            vim.diagnostic.count(
   1771              _G.diagnostic_bufnr,
   1772              { severity = { vim.diagnostic.severity.ERROR } }
   1773            ),
   1774            vim.diagnostic.count(_G.diagnostic_bufnr, {
   1775              severity = {
   1776                vim.diagnostic.severity.INFO,
   1777                vim.diagnostic.severity.WARN,
   1778              },
   1779            }),
   1780          }
   1781        end)
   1782      )
   1783    end)
   1784 
   1785    it('allows filtering by line', function()
   1786      eq(
   1787        exec_lua(function()
   1788          return {
   1789            [vim.diagnostic.severity.WARN] = 1,
   1790            [vim.diagnostic.severity.INFO] = 1,
   1791          }
   1792        end),
   1793        exec_lua(function()
   1794          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1795            _G.make_error('Error 1', 1, 1, 1, 5),
   1796            _G.make_warning('Warning on Server 1', 1, 1, 2, 3),
   1797            _G.make_info('Ignored information', 1, 1, 2, 3),
   1798            _G.make_error('Error On Other Line', 3, 1, 3, 5),
   1799          })
   1800 
   1801          return vim.diagnostic.count(_G.diagnostic_bufnr, { lnum = 2 })
   1802        end)
   1803      )
   1804    end)
   1805  end)
   1806 
   1807  describe('config()', function()
   1808    it('works with global, namespace, and ephemeral options', function()
   1809      eq(
   1810        1,
   1811        exec_lua(function()
   1812          vim.diagnostic.config({
   1813            virtual_text = false,
   1814          })
   1815 
   1816          vim.diagnostic.config({
   1817            virtual_text = true,
   1818            underline = false,
   1819          }, _G.diagnostic_ns)
   1820 
   1821          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1822            _G.make_error('Some Error', 4, 4, 4, 4),
   1823          })
   1824 
   1825          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   1826        end)
   1827      )
   1828 
   1829      eq(
   1830        1,
   1831        exec_lua(function()
   1832          vim.diagnostic.config({
   1833            virtual_text = false,
   1834          })
   1835 
   1836          vim.diagnostic.config({
   1837            virtual_text = false,
   1838            underline = false,
   1839          }, _G.diagnostic_ns)
   1840 
   1841          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1842            _G.make_error('Some Error', 4, 4, 4, 4),
   1843          }, { virtual_text = true })
   1844 
   1845          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   1846        end)
   1847      )
   1848 
   1849      eq(
   1850        0,
   1851        exec_lua(function()
   1852          vim.diagnostic.config({
   1853            virtual_text = false,
   1854          })
   1855 
   1856          vim.diagnostic.config({
   1857            virtual_text = { severity = vim.diagnostic.severity.ERROR },
   1858            underline = false,
   1859          }, _G.diagnostic_ns)
   1860 
   1861          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1862            _G.make_warning('Some Warning', 4, 4, 4, 4),
   1863          }, { virtual_text = true })
   1864 
   1865          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   1866        end)
   1867      )
   1868 
   1869      eq(
   1870        1,
   1871        exec_lua(function()
   1872          vim.diagnostic.config({
   1873            virtual_text = false,
   1874          })
   1875 
   1876          vim.diagnostic.config({
   1877            virtual_text = { severity = vim.diagnostic.severity.ERROR },
   1878            underline = false,
   1879          }, _G.diagnostic_ns)
   1880 
   1881          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1882            _G.make_warning('Some Warning', 4, 4, 4, 4),
   1883          }, {
   1884            virtual_text = {}, -- An empty table uses default values
   1885          })
   1886 
   1887          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   1888        end)
   1889      )
   1890    end)
   1891 
   1892    it('can use functions for config values', function()
   1893      exec_lua(function()
   1894        vim.diagnostic.config({
   1895          virtual_text = function()
   1896            return true
   1897          end,
   1898        }, _G.diagnostic_ns)
   1899        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1900          _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
   1901        })
   1902      end)
   1903 
   1904      eq(
   1905        1,
   1906        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   1907      )
   1908      eq(2, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   1909 
   1910      -- Now, don't enable virtual text.
   1911      -- We should have one less extmark displayed.
   1912      exec_lua(function()
   1913        vim.diagnostic.config({
   1914          virtual_text = function()
   1915            return false
   1916          end,
   1917        }, _G.diagnostic_ns)
   1918      end)
   1919 
   1920      eq(
   1921        1,
   1922        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   1923      )
   1924      eq(1, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   1925    end)
   1926 
   1927    it('allows filtering by severity', function()
   1928      local get_extmark_count_with_severity = function(min_severity)
   1929        return exec_lua(function()
   1930          vim.diagnostic.config({
   1931            underline = false,
   1932            virtual_text = {
   1933              severity = { min = min_severity },
   1934            },
   1935          })
   1936 
   1937          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1938            _G.make_warning('Delayed Diagnostic', 4, 4, 4, 4),
   1939          })
   1940 
   1941          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   1942        end)
   1943      end
   1944 
   1945      -- No messages with Error or higher
   1946      eq(0, get_extmark_count_with_severity('ERROR'))
   1947 
   1948      -- But now we don't filter it
   1949      eq(1, get_extmark_count_with_severity('WARN'))
   1950      eq(1, get_extmark_count_with_severity('HINT'))
   1951    end)
   1952 
   1953    it('allows sorting by severity', function()
   1954      exec_lua(function()
   1955        vim.diagnostic.config({
   1956          underline = false,
   1957          signs = true,
   1958          virtual_text = true,
   1959        })
   1960 
   1961        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   1962          _G.make_warning('Warning', 4, 4, 4, 4),
   1963          _G.make_error('Error', 4, 4, 4, 4),
   1964          _G.make_info('Info', 4, 4, 4, 4),
   1965        })
   1966 
   1967        function _G.get_highest_underline_hl(severity_sort)
   1968          vim.diagnostic.config({
   1969            underline = true,
   1970            severity_sort = severity_sort,
   1971          })
   1972 
   1973          local extmarks = _G.get_underline_extmarks(_G.diagnostic_ns)
   1974 
   1975          table.sort(extmarks, function(a, b)
   1976            return a[4].priority > b[4].priority
   1977          end)
   1978 
   1979          return extmarks[1][4].hl_group
   1980        end
   1981 
   1982        function _G.get_virt_text_and_signs(severity_sort)
   1983          vim.diagnostic.config({
   1984            severity_sort = severity_sort,
   1985          })
   1986 
   1987          local virt_text = _G.get_virt_text_extmarks(_G.diagnostic_ns)[1][4].virt_text
   1988 
   1989          local virt_texts = {}
   1990          for i = 2, #virt_text - 1 do
   1991            table.insert(virt_texts, (string.gsub(virt_text[i][2], 'DiagnosticVirtualText', '')))
   1992          end
   1993 
   1994          local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns)
   1995          local sign_ns = ns.user_data.sign_ns
   1996          local signs = {}
   1997          local all_signs = vim.api.nvim_buf_get_extmarks(
   1998            _G.diagnostic_bufnr,
   1999            sign_ns,
   2000            0,
   2001            -1,
   2002            { type = 'sign', details = true }
   2003          )
   2004          table.sort(all_signs, function(a, b)
   2005            return a[1] > b[1]
   2006          end)
   2007 
   2008          for _, v in ipairs(all_signs) do
   2009            local s = v[4].sign_hl_group:gsub('DiagnosticSign', '')
   2010            if not vim.tbl_contains(signs, s) then
   2011              signs[#signs + 1] = s
   2012            end
   2013          end
   2014 
   2015          return { virt_texts, signs }
   2016        end
   2017      end)
   2018 
   2019      local result = exec_lua [[return _G.get_virt_text_and_signs(false)]]
   2020 
   2021      -- Virt texts are defined lowest priority to highest, signs from
   2022      -- highest to lowest
   2023      eq({ 'Warn', 'Error', 'Info' }, result[1])
   2024      eq({ 'Info', 'Error', 'Warn' }, result[2])
   2025 
   2026      result = exec_lua [[return _G.get_virt_text_and_signs(true)]]
   2027      eq({ 'Info', 'Warn', 'Error' }, result[1])
   2028      eq({ 'Error', 'Warn', 'Info' }, result[2])
   2029 
   2030      result = exec_lua [[return _G.get_virt_text_and_signs({ reverse = true })]]
   2031      eq({ 'Error', 'Warn', 'Info' }, result[1])
   2032      eq({ 'Info', 'Warn', 'Error' }, result[2])
   2033 
   2034      local underline_hl = exec_lua [[return _G.get_highest_underline_hl(true)]]
   2035      eq('DiagnosticUnderlineError', underline_hl)
   2036 
   2037      underline_hl = exec_lua [[return _G.get_highest_underline_hl({ reverse = true })]]
   2038      eq('DiagnosticUnderlineInfo', underline_hl)
   2039    end)
   2040 
   2041    it(
   2042      'shows deprecated and unnecessary highlights in addition to severity-based highlights',
   2043      function()
   2044        ---@type string[]
   2045        local result = exec_lua(function()
   2046          local diagnostic = _G.make_error('Some error', 0, 0, 0, 0, 'source x')
   2047          diagnostic._tags = {
   2048            deprecated = true,
   2049            unnecessary = true,
   2050          }
   2051 
   2052          local diagnostics = { diagnostic }
   2053          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2054 
   2055          local extmarks = _G.get_underline_extmarks(_G.diagnostic_ns)
   2056          local hl_groups = vim.tbl_map(function(extmark)
   2057            return extmark[4].hl_group
   2058          end, extmarks)
   2059          return hl_groups
   2060        end)
   2061 
   2062        eq({
   2063          'DiagnosticDeprecated',
   2064          'DiagnosticUnnecessary',
   2065          'DiagnosticUnderlineError',
   2066        }, result)
   2067      end
   2068    )
   2069 
   2070    it('can show diagnostic sources in virtual text', function()
   2071      local result = exec_lua(function()
   2072        local diagnostics = {
   2073          _G.make_error('Some error', 0, 0, 0, 0, 'source x'),
   2074        }
   2075 
   2076        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {
   2077          underline = false,
   2078          virtual_text = {
   2079            prefix = '',
   2080            source = 'always',
   2081          },
   2082        })
   2083 
   2084        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2085        local virt_text = extmarks[1][4].virt_text[3][1]
   2086        return virt_text
   2087      end)
   2088      eq(' source x: Some error', result)
   2089 
   2090      result = exec_lua(function()
   2091        vim.diagnostic.config({
   2092          underline = false,
   2093          virtual_text = {
   2094            prefix = '',
   2095            source = 'if_many',
   2096          },
   2097        }, _G.diagnostic_ns)
   2098 
   2099        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2100        local virt_text = extmarks[1][4].virt_text[3][1]
   2101        return virt_text
   2102      end)
   2103      eq(' Some error', result)
   2104 
   2105      result = exec_lua(function()
   2106        local diagnostics = {
   2107          _G.make_error('Some error', 0, 0, 0, 0, 'source x'),
   2108          _G.make_error('Another error', 1, 1, 1, 1, 'source y'),
   2109        }
   2110 
   2111        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {
   2112          underline = false,
   2113          virtual_text = {
   2114            prefix = '',
   2115            source = 'if_many',
   2116          },
   2117        })
   2118 
   2119        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2120        local virt_text = { extmarks[1][4].virt_text[3][1], extmarks[2][4].virt_text[3][1] }
   2121        return virt_text
   2122      end)
   2123      eq(' source x: Some error', result[1])
   2124      eq(' source y: Another error', result[2])
   2125    end)
   2126 
   2127    it('supports a format function for diagnostic messages', function()
   2128      local result = exec_lua(function()
   2129        vim.diagnostic.config({
   2130          underline = false,
   2131          virtual_text = {
   2132            prefix = '',
   2133            format = function(diagnostic)
   2134              if diagnostic.severity == vim.diagnostic.severity.ERROR then
   2135                return string.format('🔥 %s', diagnostic.message)
   2136              end
   2137              return string.format('👀 %s', diagnostic.message)
   2138            end,
   2139          },
   2140        })
   2141 
   2142        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2143          _G.make_warning('Warning', 0, 0, 0, 0),
   2144          _G.make_error('Error', 1, 0, 1, 0),
   2145        })
   2146 
   2147        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2148        return { extmarks[1][4].virt_text, extmarks[2][4].virt_text }
   2149      end)
   2150      eq(' 👀 Warning', result[1][3][1])
   2151      eq(' 🔥 Error', result[2][3][1])
   2152    end)
   2153 
   2154    it('includes source for formatted diagnostics', function()
   2155      local result = exec_lua(function()
   2156        vim.diagnostic.config({
   2157          underline = false,
   2158          virtual_text = {
   2159            prefix = '',
   2160            source = 'always',
   2161            format = function(diagnostic)
   2162              if diagnostic.severity == vim.diagnostic.severity.ERROR then
   2163                return string.format('🔥 %s', diagnostic.message)
   2164              end
   2165              return string.format('👀 %s', diagnostic.message)
   2166            end,
   2167          },
   2168        })
   2169 
   2170        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2171          _G.make_warning('Warning', 0, 0, 0, 0, 'some_linter'),
   2172          _G.make_error('Error', 1, 0, 1, 0, 'another_linter'),
   2173        })
   2174 
   2175        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2176        return { extmarks[1][4].virt_text, extmarks[2][4].virt_text }
   2177      end)
   2178      eq(' some_linter: 👀 Warning', result[1][3][1])
   2179      eq(' another_linter: 🔥 Error', result[2][3][1])
   2180    end)
   2181 
   2182    it('can add a prefix to virtual text', function()
   2183      eq(
   2184        'E Some error',
   2185        exec_lua(function()
   2186          local diagnostics = {
   2187            _G.make_error('Some error', 0, 0, 0, 0),
   2188          }
   2189 
   2190          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {
   2191            underline = false,
   2192            virtual_text = {
   2193              prefix = 'E',
   2194              suffix = '',
   2195            },
   2196          })
   2197 
   2198          local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2199          local prefix = extmarks[1][4].virt_text[2][1]
   2200          local message = extmarks[1][4].virt_text[3][1]
   2201          return prefix .. message
   2202        end)
   2203      )
   2204 
   2205      eq(
   2206        '[(1/1) err-code] Some error',
   2207        exec_lua(function()
   2208          local diagnostics = {
   2209            _G.make_error('Some error', 0, 0, 0, 0, nil, 'err-code'),
   2210          }
   2211 
   2212          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {
   2213            underline = false,
   2214            virtual_text = {
   2215              prefix = function(diag, i, total)
   2216                return string.format('[(%d/%d) %s]', i, total, diag.code)
   2217              end,
   2218              suffix = '',
   2219            },
   2220          })
   2221 
   2222          local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2223          local prefix = extmarks[1][4].virt_text[2][1]
   2224          local message = extmarks[1][4].virt_text[3][1]
   2225          return prefix .. message
   2226        end)
   2227      )
   2228    end)
   2229 
   2230    it('can add a suffix to virtual text', function()
   2231      eq(
   2232        ' Some error ✘',
   2233        exec_lua(function()
   2234          local diagnostics = {
   2235            _G.make_error('Some error', 0, 0, 0, 0),
   2236          }
   2237 
   2238          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {
   2239            underline = false,
   2240            virtual_text = {
   2241              prefix = '',
   2242              suffix = ' ✘',
   2243            },
   2244          })
   2245 
   2246          local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2247          local virt_text = extmarks[1][4].virt_text[3][1]
   2248          return virt_text
   2249        end)
   2250      )
   2251 
   2252      eq(
   2253        ' Some error [err-code]',
   2254        exec_lua(function()
   2255          local diagnostics = {
   2256            _G.make_error('Some error', 0, 0, 0, 0, nil, 'err-code'),
   2257          }
   2258 
   2259          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {
   2260            underline = false,
   2261            virtual_text = {
   2262              prefix = '',
   2263              suffix = function(diag)
   2264                return string.format(' [%s]', diag.code)
   2265              end,
   2266            },
   2267          })
   2268 
   2269          local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2270          local virt_text = extmarks[1][4].virt_text[3][1]
   2271          return virt_text
   2272        end)
   2273      )
   2274    end)
   2275 
   2276    it('can filter diagnostics by returning nil when formatting', function()
   2277      local result = exec_lua(function()
   2278        vim.diagnostic.config {
   2279          virtual_text = {
   2280            format = function(diagnostic)
   2281              if diagnostic.code == 'foo_err' then
   2282                return nil
   2283              end
   2284              return diagnostic.message
   2285            end,
   2286          },
   2287        }
   2288 
   2289        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2290          _G.make_error('An error here!', 0, 0, 0, 0, 'foo_server', 'foo_err'),
   2291          _G.make_error('An error there!', 1, 1, 1, 1, 'bar_server', 'bar_err'),
   2292        })
   2293 
   2294        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2295        return extmarks
   2296      end)
   2297 
   2298      eq(1, #result)
   2299      eq(' An error there!', result[1][4].virt_text[3][1])
   2300    end)
   2301 
   2302    it('can only show virtual_text for the current line', function()
   2303      local result = exec_lua(function()
   2304        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   2305 
   2306        vim.diagnostic.config({ virtual_text = { current_line = true } })
   2307 
   2308        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2309          _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'),
   2310          _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'),
   2311        })
   2312 
   2313        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2314        return extmarks
   2315      end)
   2316 
   2317      eq(1, #result)
   2318      eq(' Error here!', result[1][4].virt_text[3][1])
   2319    end)
   2320 
   2321    it('can hide virtual_text for the current line', function()
   2322      local result = exec_lua(function()
   2323        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   2324 
   2325        vim.diagnostic.config({ virtual_text = { current_line = false } })
   2326 
   2327        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2328          _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'),
   2329          _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'),
   2330        })
   2331 
   2332        local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2333        return extmarks
   2334      end)
   2335 
   2336      eq(1, #result)
   2337      eq(' Another error there!', result[1][4].virt_text[3][1])
   2338    end)
   2339 
   2340    it('only renders virtual_line diagnostics within buffer length', function()
   2341      local result = exec_lua(function()
   2342        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   2343 
   2344        vim.diagnostic.config({ virtual_text = { current_line = false } })
   2345 
   2346        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2347          _G.make_error('Hidden Error here!', 0, 0, 0, 0, 'foo_server'),
   2348          _G.make_error('First Error here!', 1, 0, 1, 0, 'foo_server'),
   2349          _G.make_error('Second Error here!', 2, 0, 2, 0, 'foo_server'),
   2350          _G.make_error('First Ignored Error here!', 3, 0, 3, 0, 'foo_server'),
   2351          _G.make_error('Second Ignored Error here!', 6, 0, 6, 0, 'foo_server'),
   2352          _G.make_error('Third Ignored Error here!', 8, 0, 8, 0, 'foo_server'),
   2353        })
   2354 
   2355        vim.api.nvim_buf_set_lines(_G.diagnostic_bufnr, 2, 5, false, {})
   2356        vim.api.nvim_exec_autocmds('CursorMoved', { buffer = _G.diagnostic_bufnr })
   2357        return _G.get_virt_text_extmarks(_G.diagnostic_ns)
   2358      end)
   2359 
   2360      eq(2, #result)
   2361      eq(' First Error here!', result[1][4].virt_text[3][1])
   2362      eq(' Second Error here!', result[2][4].virt_text[3][1])
   2363    end)
   2364  end)
   2365 
   2366  describe('handlers.virtual_lines', function()
   2367    it('includes diagnostic code and message', function()
   2368      local result = exec_lua(function()
   2369        vim.diagnostic.config({ virtual_lines = true })
   2370 
   2371        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2372          _G.make_error('Missed symbol `,`', 0, 0, 0, 0, 'lua_ls', 'miss-symbol'),
   2373        })
   2374 
   2375        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2376        return extmarks[1][4].virt_lines
   2377      end)
   2378 
   2379      eq('miss-symbol: Missed symbol `,`', result[1][3][1])
   2380    end)
   2381 
   2382    it('adds space to the left of the diagnostic', function()
   2383      local error_offset = 5
   2384      local result = exec_lua(function()
   2385        vim.diagnostic.config({ virtual_lines = true })
   2386 
   2387        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2388          _G.make_error('Error here!', 0, error_offset, 0, error_offset, 'foo_server'),
   2389        })
   2390 
   2391        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2392        return extmarks[1][4].virt_lines
   2393      end)
   2394 
   2395      eq(error_offset, result[1][1][1]:len())
   2396    end)
   2397 
   2398    it('highlights diagnostics in multiple lines by default', function()
   2399      local result = exec_lua(function()
   2400        vim.diagnostic.config({ virtual_lines = true })
   2401 
   2402        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2403          _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'),
   2404          _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'),
   2405        })
   2406 
   2407        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2408        return extmarks
   2409      end)
   2410 
   2411      eq(2, #result)
   2412      eq('Error here!', result[1][4].virt_lines[1][3][1])
   2413      eq('Another error there!', result[2][4].virt_lines[1][3][1])
   2414    end)
   2415 
   2416    it('highlights multiple diagnostics in a single line by default', function()
   2417      local result = exec_lua(function()
   2418        vim.api.nvim_buf_set_lines(
   2419          _G.diagnostic_bufnr,
   2420          0,
   2421          -1,
   2422          false,
   2423          { 'def foo(x: int, /, y: str, *, z: float) -> None: ...' }
   2424        )
   2425 
   2426        vim.diagnostic.config({ virtual_lines = true })
   2427 
   2428        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2429          _G.make_error('Error here!', 0, 8, 0, 9, 'foo_server'),
   2430          _G.make_error('Another error there!', 0, 19, 0, 20, 'foo_server'),
   2431          _G.make_error('And another one!', 0, 30, 0, 31, 'foo_server'),
   2432        })
   2433 
   2434        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2435        return extmarks
   2436      end)
   2437 
   2438      --[[
   2439      |def foo(x: int, /, y: str, *, z: float) -> None: ...
   2440      |        │          │          └──── And another one!
   2441      |        │          └──── Another error there!
   2442      |        └──── Error here!
   2443      |        ^ col=8
   2444      |                   ^ col=19
   2445      |                              ^ col=30
   2446 
   2447      11 cols between each diagnostic after the first one (10 spaces + |)
   2448      ]]
   2449 
   2450      eq(1, #result)
   2451      local virt_lines = result[1][4].virt_lines
   2452      eq(8, virt_lines[1][1][1]:len()) -- first space
   2453      eq(10, virt_lines[1][3][1]:len()) -- second space
   2454      eq(10, virt_lines[1][5][1]:len()) -- third space
   2455      eq('And another one!', virt_lines[1][7][1])
   2456      eq(8, virt_lines[2][1][1]:len()) -- first space
   2457      eq(10, virt_lines[2][3][1]:len()) -- second space
   2458      eq('Another error there!', virt_lines[2][5][1])
   2459      eq(8, virt_lines[3][1][1]:len()) -- first space
   2460      eq('Error here!', virt_lines[3][3][1])
   2461    end)
   2462 
   2463    it('highlights overlapping diagnostics on a single line', function()
   2464      local result = exec_lua(function()
   2465        vim.diagnostic.config({ virtual_lines = true })
   2466 
   2467        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2468          _G.make_error('Error here!', 0, 10, 0, 11, 'foo_server'),
   2469          _G.make_error('Another error here!', 0, 10, 0, 11, 'foo_server'),
   2470        })
   2471 
   2472        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2473        return extmarks
   2474      end)
   2475 
   2476      --[[
   2477      |1234567890x
   2478      |          ├──── Another error here!
   2479      |          └──── Error here!
   2480      ]]
   2481 
   2482      eq(1, #result)
   2483      local virt_lines = result[1][4].virt_lines
   2484      eq(10, virt_lines[1][1][1]:len()) -- first space
   2485      eq('├──── ', virt_lines[1][2][1])
   2486      eq('Another error here!', virt_lines[1][3][1])
   2487      eq(10, virt_lines[2][1][1]:len()) -- second space
   2488      eq('└──── ', virt_lines[2][2][1])
   2489      eq('Error here!', virt_lines[2][3][1])
   2490    end)
   2491 
   2492    it('handles multi-line diagnostic message', function()
   2493      local result = exec_lua(function()
   2494        vim.diagnostic.config({ virtual_lines = true })
   2495 
   2496        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2497          _G.make_error('Error here!\ngot another line', 0, 10, 0, 11, 'foo_server'),
   2498        })
   2499 
   2500        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2501        return extmarks
   2502      end)
   2503 
   2504      --[[
   2505      |1234567890x
   2506      |          └──── Error here!
   2507      |                got another line
   2508      ]]
   2509 
   2510      eq(1, #result)
   2511      local virt_lines = result[1][4].virt_lines
   2512      eq(10, virt_lines[1][1][1]:len()) -- first space
   2513      eq('└──── ', virt_lines[1][2][1])
   2514      eq('Error here!', virt_lines[1][3][1])
   2515      eq(10, virt_lines[2][1][1]:len()) -- second line space
   2516      eq(6, virt_lines[2][2][1]:len()) -- extra padding
   2517      eq('got another line', virt_lines[2][3][1])
   2518    end)
   2519 
   2520    it('can highlight diagnostics only in the current line', function()
   2521      local result = exec_lua(function()
   2522        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   2523 
   2524        vim.diagnostic.config({ virtual_lines = { current_line = true } })
   2525 
   2526        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2527          _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'),
   2528          _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'),
   2529        })
   2530 
   2531        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2532        return extmarks
   2533      end)
   2534 
   2535      eq(1, #result)
   2536      eq('Error here!', result[1][4].virt_lines[1][3][1])
   2537    end)
   2538 
   2539    it('supports a format function for diagnostic messages', function()
   2540      local result = exec_lua(function()
   2541        vim.diagnostic.config({
   2542          virtual_lines = {
   2543            format = function()
   2544              return 'Error here!'
   2545            end,
   2546          },
   2547        })
   2548        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2549          _G.make_error('Invalid syntax', 0, 0, 0, 0),
   2550        })
   2551        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2552        return extmarks[1][4].virt_lines
   2553      end)
   2554      eq('Error here!', result[1][3][1])
   2555    end)
   2556 
   2557    it('sorts by severity with stable tiebreaker #37137', function()
   2558      local result = exec_lua(function()
   2559        vim.diagnostic.config({ severity_sort = true, virtual_lines = { current_line = true } })
   2560        local m = 100
   2561        local diagnostics = {
   2562          { end_col = m, lnum = 0, message = 'a', severity = 2 },
   2563          { end_col = m, lnum = 0, message = 'b', severity = 2 },
   2564          { end_col = m, lnum = 0, message = 'c', severity = 2 },
   2565          { end_col = m, lnum = 2, message = 'd', severity = 2 },
   2566          { end_col = m, lnum = 2, message = 'e', severity = 2 },
   2567          { end_col = m, lnum = 2, message = 'f', severity = 2 },
   2568        }
   2569        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, {})
   2570        vim.diagnostic.show(_G.diagnostic_ns, _G.diagnostic_bufnr)
   2571        vim.api.nvim_win_set_cursor(0, { 1, 0 })
   2572        local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns)
   2573        local result = {}
   2574        for _, d in ipairs(extmarks[1][4].virt_lines) do
   2575          table.insert(result, d[3][1])
   2576        end
   2577        return result
   2578      end)
   2579      eq({ 'c', 'b', 'a' }, result)
   2580    end)
   2581  end)
   2582 
   2583  describe('set()', function()
   2584    it('validation', function()
   2585      matches(
   2586        'expected a list of diagnostics',
   2587        pcall_err(exec_lua, [[vim.diagnostic.set(1, 0, {lnum = 1, col = 2})]])
   2588      )
   2589    end)
   2590 
   2591    it('can perform updates after insert_leave', function()
   2592      exec_lua(function()
   2593        vim.diagnostic.config({ virtual_text = true })
   2594        vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
   2595      end)
   2596 
   2597      api.nvim_input('o')
   2598      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2599 
   2600      -- Save the diagnostics
   2601      exec_lua(function()
   2602        vim.diagnostic.config({
   2603          update_in_insert = false,
   2604        })
   2605        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2606          _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
   2607        })
   2608      end)
   2609 
   2610      -- No diagnostics displayed yet.
   2611      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2612      eq(
   2613        1,
   2614        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2615      )
   2616      eq(0, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2617 
   2618      api.nvim_input('<esc>')
   2619      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2620 
   2621      eq(
   2622        1,
   2623        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2624      )
   2625      eq(2, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2626    end)
   2627 
   2628    it('does not perform updates when not needed', function()
   2629      exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]]
   2630      api.nvim_input('o')
   2631      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2632 
   2633      -- Save the diagnostics
   2634      exec_lua(function()
   2635        vim.diagnostic.config({
   2636          update_in_insert = false,
   2637          virtual_text = true,
   2638        })
   2639 
   2640        _G.DisplayCount = 0
   2641        local set_virtual_text = vim.diagnostic.handlers.virtual_text.show
   2642        vim.diagnostic.handlers.virtual_text.show = function(...)
   2643          _G.DisplayCount = _G.DisplayCount + 1
   2644          return set_virtual_text(...)
   2645        end
   2646 
   2647        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2648          _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
   2649        })
   2650      end)
   2651 
   2652      -- No diagnostics displayed yet.
   2653      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2654      eq(
   2655        1,
   2656        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2657      )
   2658      eq(0, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2659      eq(0, exec_lua [[return _G.DisplayCount]])
   2660 
   2661      api.nvim_input('<esc>')
   2662      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2663 
   2664      eq(
   2665        1,
   2666        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2667      )
   2668      eq(2, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2669      eq(1, exec_lua [[return _G.DisplayCount]])
   2670 
   2671      -- Go in and out of insert mode one more time.
   2672      api.nvim_input('o')
   2673      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2674 
   2675      api.nvim_input('<esc>')
   2676      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2677 
   2678      -- Should not have set the virtual text again.
   2679      eq(1, exec_lua [[return _G.DisplayCount]])
   2680    end)
   2681 
   2682    it('never sets virtual text, in combination with insert leave', function()
   2683      exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]]
   2684      api.nvim_input('o')
   2685      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2686 
   2687      -- Save the diagnostics
   2688      exec_lua(function()
   2689        vim.diagnostic.config({
   2690          update_in_insert = false,
   2691          virtual_text = false,
   2692        })
   2693 
   2694        _G.DisplayCount = 0
   2695        local set_virtual_text = vim.diagnostic.handlers.virtual_text.show
   2696        vim.diagnostic.handlers.virtual_text.show = function(...)
   2697          _G.DisplayCount = _G.DisplayCount + 1
   2698          return set_virtual_text(...)
   2699        end
   2700 
   2701        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2702          _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
   2703        })
   2704      end)
   2705 
   2706      -- No diagnostics displayed yet.
   2707      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2708      eq(
   2709        1,
   2710        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2711      )
   2712      eq(0, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2713      eq(0, exec_lua [[return _G.DisplayCount]])
   2714 
   2715      api.nvim_input('<esc>')
   2716      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2717 
   2718      eq(
   2719        1,
   2720        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2721      )
   2722      eq(1, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2723      eq(0, exec_lua [[return _G.DisplayCount]])
   2724 
   2725      -- Go in and out of insert mode one more time.
   2726      api.nvim_input('o')
   2727      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2728 
   2729      api.nvim_input('<esc>')
   2730      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2731 
   2732      -- Should not have set the virtual text still.
   2733      eq(0, exec_lua [[return _G.DisplayCount]])
   2734    end)
   2735 
   2736    it('can perform updates while in insert mode, if desired', function()
   2737      exec_lua(function()
   2738        vim.diagnostic.config({ virtual_text = true })
   2739        vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
   2740      end)
   2741      api.nvim_input('o')
   2742      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2743 
   2744      -- Save the diagnostics
   2745      exec_lua(function()
   2746        vim.diagnostic.config({
   2747          update_in_insert = true,
   2748        })
   2749 
   2750        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2751          _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
   2752        })
   2753      end)
   2754 
   2755      -- Diagnostics are displayed, because the user wanted them that way!
   2756      eq({ mode = 'i', blocking = false }, api.nvim_get_mode())
   2757      eq(
   2758        1,
   2759        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2760      )
   2761      eq(2, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2762 
   2763      api.nvim_input('<esc>')
   2764      eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
   2765 
   2766      eq(
   2767        1,
   2768        exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR,  _G.diagnostic_ns)]]
   2769      )
   2770      eq(2, exec_lua [[return  _G.count_extmarks( _G.diagnostic_bufnr,  _G.diagnostic_ns)]])
   2771    end)
   2772 
   2773    it('can set diagnostics without displaying them', function()
   2774      exec_lua(function()
   2775        vim.diagnostic.config({ virtual_text = true })
   2776      end)
   2777 
   2778      eq(
   2779        0,
   2780        exec_lua(function()
   2781          vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
   2782          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2783            _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
   2784          })
   2785          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   2786        end)
   2787      )
   2788 
   2789      eq(
   2790        2,
   2791        exec_lua(function()
   2792          vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns })
   2793          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   2794        end)
   2795      )
   2796    end)
   2797 
   2798    it('can set display options', function()
   2799      eq(
   2800        0,
   2801        exec_lua(function()
   2802          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2803            _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
   2804          }, { virtual_text = false, underline = false })
   2805          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   2806        end)
   2807      )
   2808 
   2809      eq(
   2810        1,
   2811        exec_lua(function()
   2812          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   2813            _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
   2814          }, { virtual_text = true, underline = false })
   2815          return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns)
   2816        end)
   2817      )
   2818    end)
   2819 
   2820    it('sets and clears signs #26193 #26555', function()
   2821      do
   2822        local result = exec_lua(function()
   2823          vim.diagnostic.config({
   2824            signs = true,
   2825          })
   2826 
   2827          local diagnostics = {
   2828            _G.make_error('Error', 1, 1, 1, 2),
   2829            _G.make_warning('Warning', 3, 3, 3, 3),
   2830          }
   2831 
   2832          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2833 
   2834          local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns)
   2835          local sign_ns = ns.user_data.sign_ns
   2836 
   2837          local signs = vim.api.nvim_buf_get_extmarks(
   2838            _G.diagnostic_bufnr,
   2839            sign_ns,
   2840            0,
   2841            -1,
   2842            { type = 'sign', details = true }
   2843          )
   2844          local result = {}
   2845          for _, s in ipairs(signs) do
   2846            result[#result + 1] = { lnum = s[2] + 1, name = s[4].sign_hl_group }
   2847          end
   2848          return result
   2849        end)
   2850 
   2851        eq({ 2, 'DiagnosticSignError' }, { result[1].lnum, result[1].name })
   2852        eq({ 4, 'DiagnosticSignWarn' }, { result[2].lnum, result[2].name })
   2853      end
   2854 
   2855      do
   2856        local result = exec_lua(function()
   2857          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {})
   2858 
   2859          local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns)
   2860          local sign_ns = ns.user_data.sign_ns
   2861 
   2862          return vim.api.nvim_buf_get_extmarks(
   2863            _G.diagnostic_bufnr,
   2864            sign_ns,
   2865            0,
   2866            -1,
   2867            { type = 'sign', details = true }
   2868          )
   2869        end)
   2870 
   2871        eq({}, result)
   2872      end
   2873    end)
   2874 
   2875    it('always passes a table to DiagnosticChanged autocommand', function()
   2876      local result = exec_lua(function()
   2877        local changed_diags --- @type vim.Diagnostic[]?
   2878        vim.api.nvim_create_autocmd('DiagnosticChanged', {
   2879          buffer = _G.diagnostic_bufnr,
   2880          callback = function(args)
   2881            --- @type vim.Diagnostic[]
   2882            changed_diags = args.data.diagnostics
   2883          end,
   2884        })
   2885        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {})
   2886        return changed_diags
   2887      end)
   2888      eq('table', type(result))
   2889      eq(0, #result)
   2890    end)
   2891  end)
   2892 
   2893  describe('open_float()', function()
   2894    it('can display a header', function()
   2895      eq(
   2896        { 'Diagnostics:', '1. Syntax error' },
   2897        exec_lua(function()
   2898          local diagnostics = {
   2899            _G.make_error('Syntax error', 0, 1, 0, 3),
   2900          }
   2901          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   2902          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2903          local float_bufnr, winnr = vim.diagnostic.open_float()
   2904          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   2905          vim.api.nvim_win_close(winnr, true)
   2906          return lines
   2907        end)
   2908      )
   2909 
   2910      eq(
   2911        { "We're no strangers to love...", '1. Syntax error' },
   2912        exec_lua(function()
   2913          local diagnostics = {
   2914            _G.make_error('Syntax error', 0, 1, 0, 3),
   2915          }
   2916          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   2917          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2918          local float_bufnr, winnr =
   2919            vim.diagnostic.open_float({ header = "We're no strangers to love..." })
   2920          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   2921          vim.api.nvim_win_close(winnr, true)
   2922          return lines
   2923        end)
   2924      )
   2925 
   2926      eq(
   2927        { 'You know the rules', '1. Syntax error' },
   2928        exec_lua(function()
   2929          local diagnostics = {
   2930            _G.make_error('Syntax error', 0, 1, 0, 3),
   2931          }
   2932          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   2933          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2934          local float_bufnr, winnr =
   2935            vim.diagnostic.open_float({ header = { 'You know the rules', 'Search' } })
   2936          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   2937          vim.api.nvim_win_close(winnr, true)
   2938          return lines
   2939        end)
   2940      )
   2941    end)
   2942 
   2943    it('can show diagnostics from the whole buffer', function()
   2944      eq(
   2945        { '1. Syntax error', '2. Some warning' },
   2946        exec_lua(function()
   2947          local diagnostics = {
   2948            _G.make_error('Syntax error', 0, 1, 0, 3),
   2949            _G.make_warning('Some warning', 1, 1, 1, 3),
   2950          }
   2951          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   2952          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2953          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' })
   2954          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   2955          vim.api.nvim_win_close(winnr, true)
   2956          return lines
   2957        end)
   2958      )
   2959    end)
   2960 
   2961    it('can show diagnostics from a single line', function()
   2962      -- Using cursor position
   2963      eq(
   2964        { '1. Some warning' },
   2965        exec_lua(function()
   2966          local diagnostics = {
   2967            _G.make_error('Syntax error', 0, 1, 0, 3),
   2968            _G.make_warning('Some warning', 1, 1, 1, 3),
   2969          }
   2970          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   2971          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2972          vim.api.nvim_win_set_cursor(0, { 2, 1 })
   2973          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false })
   2974          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   2975          vim.api.nvim_win_close(winnr, true)
   2976          return lines
   2977        end)
   2978      )
   2979 
   2980      -- With specified position
   2981      eq(
   2982        { '1. Some warning' },
   2983        exec_lua(function()
   2984          local diagnostics = {
   2985            _G.make_error('Syntax error', 0, 1, 0, 3),
   2986            _G.make_warning('Some warning', 1, 1, 1, 3),
   2987          }
   2988          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   2989          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   2990          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   2991          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, pos = 1 })
   2992          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   2993          vim.api.nvim_win_close(winnr, true)
   2994          return lines
   2995        end)
   2996      )
   2997 
   2998      -- End position is exclusive
   2999      eq(
   3000        nil,
   3001        exec_lua(function()
   3002          local diagnostics = {
   3003            _G.make_error('Syntax error', 1, 1, 2, 0),
   3004          }
   3005          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3006          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3007          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   3008          local _, winnr = vim.diagnostic.open_float(0, { header = false, pos = { 2, 0 } })
   3009          return winnr
   3010        end)
   3011      )
   3012 
   3013      -- Works when width == 0
   3014      eq(
   3015        { '1. Syntax error' },
   3016        exec_lua(function()
   3017          local diagnostics = {
   3018            _G.make_error('Syntax error', 2, 0, 2, 0),
   3019          }
   3020          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3021          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3022          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   3023          local float_bufnr, winnr =
   3024            vim.diagnostic.open_float(0, { header = false, pos = { 2, 1 } })
   3025          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3026          vim.api.nvim_win_close(winnr, true)
   3027          return lines
   3028        end)
   3029      )
   3030    end)
   3031 
   3032    it('can show diagnostics from a specific position', function()
   3033      -- Using cursor position
   3034      eq(
   3035        { 'Syntax error' },
   3036        exec_lua(function()
   3037          local diagnostics = {
   3038            _G.make_error('Syntax error', 1, 1, 1, 3),
   3039            _G.make_warning('Some warning', 1, 3, 1, 4),
   3040          }
   3041          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3042          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3043          vim.api.nvim_win_set_cursor(0, { 2, 2 })
   3044          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'cursor' })
   3045          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3046          vim.api.nvim_win_close(winnr, true)
   3047          return lines
   3048        end)
   3049      )
   3050 
   3051      -- With specified position
   3052      eq(
   3053        { 'Some warning' },
   3054        exec_lua(function()
   3055          local diagnostics = {
   3056            _G.make_error('Syntax error', 1, 1, 1, 3),
   3057            _G.make_warning('Some warning', 1, 3, 1, 4),
   3058          }
   3059          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3060          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3061          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   3062          local float_bufnr, winnr =
   3063            vim.diagnostic.open_float({ header = false, scope = 'cursor', pos = { 1, 3 } })
   3064          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3065          vim.api.nvim_win_close(winnr, true)
   3066          return lines
   3067        end)
   3068      )
   3069 
   3070      -- With column position past the end of the line. #16062
   3071      eq(
   3072        { 'Syntax error' },
   3073        exec_lua(function()
   3074          local first_line_len = #vim.api.nvim_buf_get_lines(_G.diagnostic_bufnr, 0, 1, true)[1]
   3075          local diagnostics = {
   3076            _G.make_error('Syntax error', 0, first_line_len + 1, 1, 0),
   3077          }
   3078          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3079          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3080          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   3081          local float_bufnr, winnr = vim.diagnostic.open_float({
   3082            header = false,
   3083            scope = 'cursor',
   3084            pos = { 0, first_line_len - 1 },
   3085          })
   3086          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3087          vim.api.nvim_win_close(winnr, true)
   3088          return lines
   3089        end)
   3090      )
   3091 
   3092      -- End position is exclusive
   3093      eq(
   3094        nil,
   3095        exec_lua(function()
   3096          local diagnostics = {
   3097            _G.make_error('Syntax error', 1, 1, 1, 3),
   3098          }
   3099          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3100          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3101          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   3102          local _, winnr =
   3103            vim.diagnostic.open_float(0, { header = false, scope = 'cursor', pos = { 1, 3 } })
   3104          return winnr
   3105        end)
   3106      )
   3107 
   3108      -- Works when width == 0
   3109      eq(
   3110        { 'Syntax error' },
   3111        exec_lua(function()
   3112          local diagnostics = {
   3113            _G.make_error('Syntax error', 2, 0, 2, 0),
   3114          }
   3115          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3116          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3117          vim.api.nvim_win_set_cursor(0, { 1, 1 })
   3118          local float_bufnr, winnr =
   3119            vim.diagnostic.open_float({ header = false, scope = 'cursor', pos = { 2, 0 } })
   3120          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3121          vim.api.nvim_win_close(winnr, true)
   3122          return lines
   3123        end)
   3124      )
   3125    end)
   3126 
   3127    it(
   3128      'creates floating window and returns float bufnr and winnr if current line contains diagnostics',
   3129      function()
   3130        -- Two lines:
   3131        --    Diagnostic:
   3132        --    1. <msg>
   3133        eq(
   3134          2,
   3135          exec_lua(function()
   3136            local diagnostics = {
   3137              _G.make_error('Syntax error', 0, 1, 0, 3),
   3138            }
   3139            vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3140            vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3141            local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr)
   3142            local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3143            vim.api.nvim_win_close(winnr, true)
   3144            return #lines
   3145          end)
   3146        )
   3147      end
   3148    )
   3149 
   3150    it('only reports diagnostics from the current buffer when bufnr is omitted #15710', function()
   3151      eq(
   3152        2,
   3153        exec_lua(function()
   3154          local other_bufnr = vim.api.nvim_create_buf(true, false)
   3155          local buf_1_diagnostics = {
   3156            _G.make_error('Syntax error', 0, 1, 0, 3),
   3157          }
   3158          local buf_2_diagnostics = {
   3159            _G.make_warning('Some warning', 0, 1, 0, 3),
   3160          }
   3161          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3162          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, buf_1_diagnostics)
   3163          vim.diagnostic.set(_G.other_ns, other_bufnr, buf_2_diagnostics)
   3164          local float_bufnr, winnr = vim.diagnostic.open_float()
   3165          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3166          vim.api.nvim_win_close(winnr, true)
   3167          return #lines
   3168        end)
   3169      )
   3170    end)
   3171 
   3172    it('allows filtering by namespace', function()
   3173      eq(
   3174        { 'Diagnostics:', '1. Syntax error' },
   3175        exec_lua(function()
   3176          local ns_1_diagnostics = {
   3177            _G.make_error('Syntax error', 0, 1, 0, 3),
   3178          }
   3179          local ns_2_diagnostics = {
   3180            _G.make_warning('Some warning', 0, 1, 0, 3),
   3181          }
   3182          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3183          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diagnostics)
   3184          vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diagnostics)
   3185          local float_bufnr, winnr =
   3186            vim.diagnostic.open_float(_G.diagnostic_bufnr, { namespace = _G.diagnostic_ns })
   3187          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3188          vim.api.nvim_win_close(winnr, true)
   3189          return lines
   3190        end)
   3191      )
   3192    end)
   3193 
   3194    it('allows filtering by multiple namespaces', function()
   3195      eq(
   3196        { 'Diagnostics:', '1. Syntax error', '2. Some warning' },
   3197        exec_lua(function()
   3198          local ns_1_diagnostics = {
   3199            _G.make_error('Syntax error', 0, 1, 0, 3),
   3200          }
   3201          local ns_2_diagnostics = {
   3202            _G.make_warning('Some warning', 0, 1, 0, 3),
   3203          }
   3204          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3205          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diagnostics)
   3206          vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diagnostics)
   3207          local float_bufnr, winnr = vim.diagnostic.open_float(
   3208            _G.diagnostic_bufnr,
   3209            { namespace = { _G.diagnostic_ns, _G.other_ns } }
   3210          )
   3211          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3212          vim.api.nvim_win_close(winnr, true)
   3213          return lines
   3214        end)
   3215      )
   3216    end)
   3217 
   3218    it(
   3219      'creates floating window and returns float bufnr and winnr without header, if requested',
   3220      function()
   3221        -- One line (since no header):
   3222        --    1. <msg>
   3223        eq(
   3224          1,
   3225          exec_lua(function()
   3226            local diagnostics = {
   3227              _G.make_error('Syntax error', 0, 1, 0, 3),
   3228            }
   3229            vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3230            vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3231            local float_bufnr, winnr =
   3232              vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false })
   3233            local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3234            vim.api.nvim_win_close(winnr, true)
   3235            return #lines
   3236          end)
   3237        )
   3238      end
   3239    )
   3240 
   3241    it('clamps diagnostic line numbers within the valid range', function()
   3242      eq(
   3243        1,
   3244        exec_lua(function()
   3245          local diagnostics = {
   3246            _G.make_error('Syntax error', 6, 0, 6, 0),
   3247          }
   3248          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3249          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3250          local float_bufnr, winnr =
   3251            vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false, pos = 5 })
   3252          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3253          vim.api.nvim_win_close(winnr, true)
   3254          return #lines
   3255        end)
   3256      )
   3257    end)
   3258 
   3259    it('can show diagnostic source', function()
   3260      exec_lua [[vim.api.nvim_win_set_buf(0,  _G.diagnostic_bufnr)]]
   3261 
   3262      eq(
   3263        { '1. Syntax error' },
   3264        exec_lua(function()
   3265          local diagnostics = {
   3266            _G.make_error('Syntax error', 0, 1, 0, 3, 'source x'),
   3267          }
   3268          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3269          local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr, {
   3270            header = false,
   3271            source = 'if_many',
   3272          })
   3273          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3274          vim.api.nvim_win_close(winnr, true)
   3275          return lines
   3276        end)
   3277      )
   3278 
   3279      eq(
   3280        { '1. source x: Syntax error' },
   3281        exec_lua(function()
   3282          local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr, {
   3283            header = false,
   3284            source = 'always',
   3285          })
   3286          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3287          vim.api.nvim_win_close(winnr, true)
   3288          return lines
   3289        end)
   3290      )
   3291 
   3292      eq(
   3293        { '1. source x: Syntax error', '2. source y: Another error' },
   3294        exec_lua(function()
   3295          local diagnostics = {
   3296            _G.make_error('Syntax error', 0, 1, 0, 3, 'source x'),
   3297            _G.make_error('Another error', 0, 1, 0, 3, 'source y'),
   3298          }
   3299          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3300          local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr, {
   3301            header = false,
   3302            source = 'if_many',
   3303          })
   3304          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3305          vim.api.nvim_win_close(winnr, true)
   3306          return lines
   3307        end)
   3308      )
   3309    end)
   3310 
   3311    it('respects severity_sort', function()
   3312      exec_lua [[vim.api.nvim_win_set_buf(0,  _G.diagnostic_bufnr)]]
   3313 
   3314      eq(
   3315        { '1. Syntax error', '2. Info', '3. Error', '4. Warning' },
   3316        exec_lua(function()
   3317          local diagnostics = {
   3318            _G.make_error('Syntax error', 0, 1, 0, 3),
   3319            _G.make_info('Info', 0, 3, 0, 4),
   3320            _G.make_error('Error', 0, 2, 0, 2),
   3321            _G.make_warning('Warning', 0, 0, 0, 1),
   3322          }
   3323 
   3324          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3325 
   3326          vim.diagnostic.config({ severity_sort = false })
   3327 
   3328          local float_bufnr, winnr =
   3329            vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false })
   3330          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3331          vim.api.nvim_win_close(winnr, true)
   3332          return lines
   3333        end)
   3334      )
   3335 
   3336      eq(
   3337        { '1. Syntax error', '2. Error', '3. Warning', '4. Info' },
   3338        exec_lua(function()
   3339          vim.diagnostic.config({ severity_sort = true })
   3340          local float_bufnr, winnr =
   3341            vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false })
   3342          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3343          vim.api.nvim_win_close(winnr, true)
   3344          return lines
   3345        end)
   3346      )
   3347 
   3348      eq(
   3349        { '1. Info', '2. Warning', '3. Error', '4. Syntax error' },
   3350        exec_lua(function()
   3351          vim.diagnostic.config({ severity_sort = { reverse = true } })
   3352          local float_bufnr, winnr =
   3353            vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false })
   3354          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3355          vim.api.nvim_win_close(winnr, true)
   3356          return lines
   3357        end)
   3358      )
   3359    end)
   3360 
   3361    it('can filter by severity', function()
   3362      local count_diagnostics_with_severity = function(min_severity, max_severity)
   3363        return exec_lua(function()
   3364          vim.diagnostic.config({
   3365            float = {
   3366              severity = { min = min_severity, max = max_severity },
   3367            },
   3368          })
   3369 
   3370          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3371            _G.make_error('Syntax error', 0, 1, 0, 3),
   3372            _G.make_info('Info', 0, 3, 0, 4),
   3373            _G.make_error('Error', 0, 2, 0, 2),
   3374            _G.make_warning('Warning', 0, 0, 0, 1),
   3375          })
   3376 
   3377          local float_bufnr, winnr =
   3378            vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false })
   3379          if not float_bufnr then
   3380            return 0
   3381          end
   3382 
   3383          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3384          vim.api.nvim_win_close(winnr, true)
   3385          return #lines
   3386        end)
   3387      end
   3388 
   3389      eq(2, count_diagnostics_with_severity('ERROR'))
   3390      eq(3, count_diagnostics_with_severity('WARN'))
   3391      eq(1, count_diagnostics_with_severity('WARN', 'WARN'))
   3392      eq(4, count_diagnostics_with_severity('HINT'))
   3393      eq(0, count_diagnostics_with_severity('HINT', 'HINT'))
   3394    end)
   3395 
   3396    it('can add a prefix to diagnostics', function()
   3397      -- Default is to add a number
   3398      eq(
   3399        { '1. Syntax error', '2. Some warning' },
   3400        exec_lua(function()
   3401          local diagnostics = {
   3402            _G.make_error('Syntax error', 0, 1, 0, 3),
   3403            _G.make_warning('Some warning', 1, 1, 1, 3),
   3404          }
   3405          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3406          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3407          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' })
   3408          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3409          vim.api.nvim_win_close(winnr, true)
   3410          return lines
   3411        end)
   3412      )
   3413 
   3414      eq(
   3415        { 'Syntax error', 'Some warning' },
   3416        exec_lua(function()
   3417          local diagnostics = {
   3418            _G.make_error('Syntax error', 0, 1, 0, 3),
   3419            _G.make_warning('Some warning', 1, 1, 1, 3),
   3420          }
   3421          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3422          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3423          local float_bufnr, winnr =
   3424            vim.diagnostic.open_float({ header = false, scope = 'buffer', prefix = '' })
   3425          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3426          vim.api.nvim_win_close(winnr, true)
   3427          return lines
   3428        end)
   3429      )
   3430 
   3431      eq(
   3432        { '1. Syntax error', '2. Some warning' },
   3433        exec_lua(function()
   3434          local diagnostics = {
   3435            _G.make_error('Syntax error', 0, 1, 0, 3),
   3436            _G.make_warning('Some warning', 0, 1, 0, 3),
   3437          }
   3438          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3439          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3440          local float_bufnr, winnr = vim.diagnostic.open_float({
   3441            header = false,
   3442            prefix = function(_, i, total)
   3443              -- Only show a number if there is more than one diagnostic
   3444              if total > 1 then
   3445                return string.format('%d. ', i)
   3446              end
   3447              return ''
   3448            end,
   3449          })
   3450          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3451          vim.api.nvim_win_close(winnr, true)
   3452          return lines
   3453        end)
   3454      )
   3455 
   3456      eq(
   3457        { 'Syntax error' },
   3458        exec_lua(function()
   3459          local diagnostics = {
   3460            _G.make_error('Syntax error', 0, 1, 0, 3),
   3461          }
   3462          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3463          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3464          local float_bufnr, winnr = vim.diagnostic.open_float({
   3465            header = false,
   3466            prefix = function(_, i, total)
   3467              -- Only show a number if there is more than one diagnostic
   3468              if total > 1 then
   3469                return string.format('%d. ', i)
   3470              end
   3471              return ''
   3472            end,
   3473          })
   3474          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3475          vim.api.nvim_win_close(winnr, true)
   3476          return lines
   3477        end)
   3478      )
   3479 
   3480      eq(
   3481        '.../diagnostic.lua:0: prefix: expected string|table|function, got number',
   3482        pcall_err(exec_lua, [[ vim.diagnostic.open_float({ prefix = 42 }) ]])
   3483      )
   3484    end)
   3485 
   3486    it('can add a suffix to diagnostics', function()
   3487      -- Default is to render the diagnostic error code
   3488      eq(
   3489        { '1. Syntax error [code-x]', '2. Some warning [code-y]' },
   3490        exec_lua(function()
   3491          local diagnostics = {
   3492            _G.make_error('Syntax error', 0, 1, 0, 3, nil, 'code-x'),
   3493            _G.make_warning('Some warning', 1, 1, 1, 3, nil, 'code-y'),
   3494          }
   3495          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3496          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3497          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' })
   3498          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3499          vim.api.nvim_win_close(winnr, true)
   3500          return lines
   3501        end)
   3502      )
   3503 
   3504      eq(
   3505        { '1. Syntax error', '2. Some warning' },
   3506        exec_lua(function()
   3507          local diagnostics = {
   3508            _G.make_error('Syntax error', 0, 1, 0, 3, nil, 'code-x'),
   3509            _G.make_warning('Some warning', 1, 1, 1, 3, nil, 'code-y'),
   3510          }
   3511          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3512          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3513          local float_bufnr, winnr =
   3514            vim.diagnostic.open_float({ header = false, scope = 'buffer', suffix = '' })
   3515          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3516          vim.api.nvim_win_close(winnr, true)
   3517          return lines
   3518        end)
   3519      )
   3520 
   3521      -- Suffix is rendered on the last line of a multiline diagnostic
   3522      eq(
   3523        { '1. Syntax error', '   More context [code-x]' },
   3524        exec_lua(function()
   3525          local diagnostics = {
   3526            _G.make_error('Syntax error\nMore context', 0, 1, 0, 3, nil, 'code-x'),
   3527          }
   3528          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3529          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3530          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' })
   3531          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3532          vim.api.nvim_win_close(winnr, true)
   3533          return lines
   3534        end)
   3535      )
   3536 
   3537      eq(
   3538        '.../diagnostic.lua:0: suffix: expected string|table|function, got number',
   3539        pcall_err(exec_lua, [[ vim.diagnostic.open_float({ suffix = 42 }) ]])
   3540      )
   3541    end)
   3542 
   3543    it('can add LSP related information to a diagnostic', function()
   3544      local related_info_uri = 'file:///fake/uri'
   3545 
   3546      -- Populate the related info buffer.
   3547      exec_lua(function()
   3548        local fake_bufnr = vim.uri_to_bufnr(related_info_uri)
   3549        vim.fn.bufload(fake_bufnr)
   3550        vim.api.nvim_buf_set_lines(fake_bufnr, 0, 1, false, {
   3551          'O, the Pelican,',
   3552          'so smoothly doth he crest.',
   3553          'a wind god!',
   3554        })
   3555        vim.api.nvim_win_set_buf(0, fake_bufnr)
   3556      end)
   3557 
   3558      -- Displays related info.
   3559      eq(
   3560        {
   3561          '1. Some warning',
   3562          '   uri:2:1: Some extra info',
   3563          '   uri:3:4: Some more extra info',
   3564        },
   3565        exec_lua(function()
   3566          ---@type vim.Diagnostic
   3567          local diagnostic = _G.make_warning('Some warning', 1, 1, 1, 3)
   3568          diagnostic.user_data = {
   3569            -- Related information comes from LSP user_data
   3570            lsp = {
   3571              relatedInformation = {
   3572                {
   3573                  message = 'Some extra info',
   3574                  location = {
   3575                    uri = related_info_uri,
   3576                    range = {
   3577                      start = {
   3578                        line = 1,
   3579                        character = 0,
   3580                      },
   3581                      ['end'] = {
   3582                        line = 1,
   3583                        character = 1,
   3584                      },
   3585                    },
   3586                  },
   3587                },
   3588                {
   3589                  message = 'Some more extra info',
   3590                  location = {
   3591                    uri = related_info_uri,
   3592                    range = {
   3593                      start = {
   3594                        line = 2,
   3595                        character = 3,
   3596                      },
   3597                      ['end'] = {
   3598                        line = 4,
   3599                        character = 5,
   3600                      },
   3601                    },
   3602                  },
   3603                },
   3604              },
   3605            },
   3606          }
   3607          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3608          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { diagnostic })
   3609          local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' })
   3610          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3611          -- Put the cursor on a line with related info
   3612          vim.api.nvim_tabpage_set_win(0, winnr)
   3613          vim.api.nvim_win_set_cursor(0, { 2, 0 })
   3614          return lines
   3615        end)
   3616      )
   3617 
   3618      -- Jumps to related info.
   3619      eq(
   3620        'so smoothly doth he crest.',
   3621        exec_lua(function()
   3622          vim.cmd.norm('gf')
   3623          vim.wait(20, function() end)
   3624          return vim.api.nvim_get_current_line()
   3625        end)
   3626      )
   3627    end)
   3628 
   3629    it('works with the old signature', function()
   3630      eq(
   3631        { '1. Syntax error' },
   3632        exec_lua(function()
   3633          local diagnostics = {
   3634            _G.make_error('Syntax error', 0, 1, 0, 3),
   3635          }
   3636          vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3637          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3638          local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false })
   3639          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3640          vim.api.nvim_win_close(winnr, true)
   3641          return lines
   3642        end)
   3643      )
   3644    end)
   3645 
   3646    it('works for multi-line diagnostics #21949', function()
   3647      -- create diagnostic
   3648      exec_lua(function()
   3649        local diagnostics = {
   3650          _G.make_error('Error in two lines lnum is 1 and end_lnum is 2', 1, 1, 2, 3),
   3651        }
   3652        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3653        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics)
   3654      end)
   3655 
   3656      -- open float failed non diagnostic lnum
   3657      eq(
   3658        nil,
   3659        exec_lua(function()
   3660          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   3661          local _, winnr = vim.diagnostic.open_float(0, { header = false })
   3662          return winnr
   3663        end)
   3664      )
   3665      eq(
   3666        nil,
   3667        exec_lua(function()
   3668          vim.api.nvim_win_set_cursor(0, { 1, 0 })
   3669          local _, winnr = vim.diagnostic.open_float(0, { header = false, scope = 'cursor' })
   3670          return winnr
   3671        end)
   3672      )
   3673 
   3674      -- can open a float window on lnum 1
   3675      eq(
   3676        { '1. Error in two lines lnum is 1 and end_lnum is 2' },
   3677        exec_lua(function()
   3678          vim.api.nvim_win_set_cursor(0, { 2, 0 })
   3679          local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false })
   3680          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3681          vim.api.nvim_win_close(winnr, true)
   3682          return lines
   3683        end)
   3684      )
   3685 
   3686      -- can open a cursor-scoped float window on lnum 1
   3687      eq(
   3688        { 'Error in two lines lnum is 1 and end_lnum is 2' },
   3689        exec_lua(function()
   3690          vim.api.nvim_win_set_cursor(0, { 2, 1 })
   3691          local float_bufnr, winnr =
   3692            vim.diagnostic.open_float(0, { header = false, scope = 'cursor' })
   3693          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3694          vim.api.nvim_win_close(winnr, true)
   3695          return lines
   3696        end)
   3697      )
   3698 
   3699      -- can open a float window on end_lnum 2
   3700      eq(
   3701        { '1. Error in two lines lnum is 1 and end_lnum is 2' },
   3702        exec_lua(function()
   3703          vim.api.nvim_win_set_cursor(0, { 3, 0 })
   3704          local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false })
   3705          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3706          vim.api.nvim_win_close(winnr, true)
   3707          return lines
   3708        end)
   3709      )
   3710 
   3711      -- can open a cursor-scoped float window on end_lnum 2
   3712      eq(
   3713        { 'Error in two lines lnum is 1 and end_lnum is 2' },
   3714        exec_lua(function()
   3715          vim.api.nvim_win_set_cursor(0, { 3, 2 })
   3716          local float_bufnr, winnr =
   3717            vim.diagnostic.open_float(0, { header = false, scope = 'cursor' })
   3718          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3719          vim.api.nvim_win_close(winnr, true)
   3720          return lines
   3721        end)
   3722      )
   3723    end)
   3724 
   3725    it('shows diagnostics at their logical locations after text changes before', function()
   3726      exec_lua(function()
   3727        vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
   3728 
   3729        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3730          _G.make_error('Diagnostic #1', 1, 4, 1, 7),
   3731          _G.make_error('Diagnostic #2', 3, 0, 3, 3),
   3732        })
   3733      end)
   3734 
   3735      api.nvim_buf_set_text(0, 3, 0, 3, 0, { 'new line', 'new ' })
   3736 
   3737      eq(
   3738        { 'Diagnostic #1' },
   3739        exec_lua(function()
   3740          vim.api.nvim_win_set_cursor(0, { 2, 4 })
   3741          local float_bufnr, winnr = vim.diagnostic.open_float({ header = '', scope = 'cursor' })
   3742          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3743          vim.api.nvim_win_close(winnr, true)
   3744          return lines
   3745        end)
   3746      )
   3747 
   3748      eq(
   3749        { 'Diagnostic #2' },
   3750        exec_lua(function()
   3751          vim.api.nvim_win_set_cursor(0, { 5, 4 })
   3752          local float_bufnr, winnr = vim.diagnostic.open_float({ header = '', scope = 'cursor' })
   3753          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3754          vim.api.nvim_win_close(winnr, true)
   3755          return lines
   3756        end)
   3757      )
   3758    end)
   3759 
   3760    it('shows diagnostics at their logical locations after text changes inside', function()
   3761      exec_lua(function()
   3762        vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
   3763 
   3764        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3765          _G.make_error('Diagnostic #1', 1, 0, 1, 7),
   3766        })
   3767      end)
   3768 
   3769      api.nvim_buf_set_text(0, 1, 4, 1, 4, { 'new ' })
   3770 
   3771      eq(
   3772        { 'Diagnostic #1' },
   3773        exec_lua(function()
   3774          vim.api.nvim_win_set_cursor(0, { 2, 10 })
   3775          local float_bufnr, winnr = vim.diagnostic.open_float({ header = '', scope = 'cursor' })
   3776          local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3777          vim.api.nvim_win_close(winnr, true)
   3778          return lines
   3779        end)
   3780      )
   3781    end)
   3782 
   3783    it(
   3784      'shows diagnostics at the end of the line if diagnostic is set after last character in line',
   3785      function()
   3786        exec_lua(function()
   3787          vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
   3788 
   3789          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3790            _G.make_error('Diagnostic #1', 2, 3, 3, 4),
   3791          })
   3792        end)
   3793 
   3794        eq(
   3795          { 'Diagnostic #1' },
   3796          exec_lua(function()
   3797            vim.api.nvim_win_set_cursor(0, { 3, 2 })
   3798            local float_bufnr, winnr = vim.diagnostic.open_float({ header = '', scope = 'cursor' })
   3799            local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
   3800            vim.api.nvim_win_close(winnr, true)
   3801            return lines
   3802          end)
   3803        )
   3804      end
   3805    )
   3806  end)
   3807 
   3808  describe('setloclist()', function()
   3809    it('sets diagnostics in lnum order', function()
   3810      local loc_list = exec_lua(function()
   3811        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3812 
   3813        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3814          _G.make_error('Farther Diagnostic', 4, 4, 4, 4),
   3815          _G.make_error('Lower Diagnostic', 1, 1, 1, 1),
   3816        })
   3817 
   3818        vim.diagnostic.setloclist()
   3819 
   3820        return vim.fn.getloclist(0)
   3821      end)
   3822 
   3823      assert(loc_list[1].lnum < loc_list[2].lnum)
   3824    end)
   3825 
   3826    it('sets diagnostics in lnum order, regardless of namespace', function()
   3827      local loc_list = exec_lua(function()
   3828        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3829 
   3830        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3831          _G.make_error('Lower Diagnostic', 1, 1, 1, 1),
   3832        })
   3833 
   3834        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, {
   3835          _G.make_warning('Farther Diagnostic', 4, 4, 4, 4),
   3836        })
   3837 
   3838        vim.diagnostic.setloclist()
   3839 
   3840        return vim.fn.getloclist(0)
   3841      end)
   3842 
   3843      assert(loc_list[1].lnum < loc_list[2].lnum)
   3844    end)
   3845 
   3846    it('sets diagnostics from the specified namespaces', function()
   3847      local loc_list = exec_lua(function()
   3848        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3849 
   3850        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3851          _G.make_error('Error here!', 1, 1, 1, 1),
   3852        })
   3853        vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, {
   3854          _G.make_warning('Error there!', 2, 2, 2, 2),
   3855        })
   3856 
   3857        vim.diagnostic.setloclist({ namespace = { _G.diagnostic_ns } })
   3858 
   3859        return vim.fn.getloclist(0)
   3860      end)
   3861 
   3862      eq(1, #loc_list)
   3863      eq('Error here!', loc_list[1].text)
   3864    end)
   3865 
   3866    it('supports format function', function()
   3867      local loc_list = exec_lua(function()
   3868        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3869 
   3870        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3871          _G.make_error('Error', 1, 1, 1, 1, 'foo_ls'),
   3872          _G.make_error('Another error', 2, 2, 2, 2, 'foo_ls'),
   3873        })
   3874 
   3875        vim.diagnostic.setloclist({
   3876          format = function(diagnostic)
   3877            if diagnostic.lnum > 1 then
   3878              return nil
   3879            end
   3880 
   3881            return string.format('%s: %s', diagnostic.source, diagnostic.message)
   3882          end,
   3883        })
   3884 
   3885        return vim.fn.getloclist(0)
   3886      end)
   3887 
   3888      eq(1, #loc_list)
   3889      eq('foo_ls: Error', loc_list[1].text)
   3890    end)
   3891  end)
   3892 
   3893  describe('setqflist()', function()
   3894    it('updates existing diagnostics quickfix if one already exists', function()
   3895      local result = exec_lua(function()
   3896        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3897 
   3898        vim.fn.setqflist({}, ' ', { title = 'Diagnostics' })
   3899        local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id
   3900 
   3901        vim.diagnostic.setqflist({ title = 'Diagnostics' })
   3902        local qf_id = vim.fn.getqflist({ id = 0, nr = '$' }).id
   3903 
   3904        return { diagnostics_qf_id, qf_id }
   3905      end)
   3906 
   3907      eq(result[1], result[2])
   3908    end)
   3909 
   3910    it('navigates to existing diagnostics quickfix if one already exists and open=true', function()
   3911      local result = exec_lua(function()
   3912        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3913 
   3914        vim.fn.setqflist({}, ' ', { title = 'Diagnostics' })
   3915        local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id
   3916 
   3917        vim.fn.setqflist({}, ' ', { title = 'Other' })
   3918 
   3919        vim.diagnostic.setqflist({ title = 'Diagnostics', open = true })
   3920        local qf_id = vim.fn.getqflist({ id = 0 }).id
   3921 
   3922        return { diagnostics_qf_id, qf_id }
   3923      end)
   3924 
   3925      eq(result[1], result[2])
   3926    end)
   3927 
   3928    it('sets new diagnostics quickfix as active when open=true', function()
   3929      local result = exec_lua(function()
   3930        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3931 
   3932        vim.fn.setqflist({}, ' ', { title = 'Other' })
   3933        local other_qf_id = vim.fn.getqflist({ id = 0 }).id
   3934 
   3935        vim.diagnostic.setqflist({ title = 'Diagnostics', open = true })
   3936        local qf_id = vim.fn.getqflist({ id = 0 }).id
   3937 
   3938        return { other_qf_id, qf_id }
   3939      end)
   3940 
   3941      neq(result[1], result[2])
   3942    end)
   3943 
   3944    it('opens quickfix window when open=true', function()
   3945      local qf_winid = exec_lua(function()
   3946        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3947 
   3948        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3949          _G.make_error('Error', 1, 1, 1, 1),
   3950        })
   3951 
   3952        vim.diagnostic.setqflist({ open = true })
   3953 
   3954        return vim.fn.getqflist({ winid = 0 }).winid
   3955      end)
   3956 
   3957      neq(0, qf_winid)
   3958    end)
   3959 
   3960    it('supports format function', function()
   3961      local qf_list = exec_lua(function()
   3962        vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)
   3963 
   3964        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   3965          _G.make_error('Error', 1, 1, 1, 1, 'foo_ls'),
   3966          _G.make_error('Another error', 2, 2, 2, 2, 'foo_ls'),
   3967        })
   3968 
   3969        vim.diagnostic.setqflist({
   3970          format = function(diagnostic)
   3971            if diagnostic.lnum > 1 then
   3972              return nil
   3973            end
   3974 
   3975            return string.format('%s: %s', diagnostic.source, diagnostic.message)
   3976          end,
   3977        })
   3978 
   3979        return vim.fn.getqflist()
   3980      end)
   3981 
   3982      eq(1, #qf_list)
   3983      eq('foo_ls: Error', qf_list[1].text)
   3984    end)
   3985  end)
   3986 
   3987  describe('match()', function()
   3988    it('matches a string', function()
   3989      local msg = 'ERROR: george.txt:19:84:Two plus two equals five'
   3990      local diagnostic = {
   3991        severity = exec_lua [[return vim.diagnostic.severity.ERROR]],
   3992        lnum = 18,
   3993        col = 83,
   3994        end_lnum = 18,
   3995        end_col = 83,
   3996        message = 'Two plus two equals five',
   3997      }
   3998      eq(
   3999        diagnostic,
   4000        exec_lua(function()
   4001          return vim.diagnostic.match(
   4002            msg,
   4003            '^(%w+): [^:]+:(%d+):(%d+):(.+)$',
   4004            { 'severity', 'lnum', 'col', 'message' }
   4005          )
   4006        end)
   4007      )
   4008    end)
   4009 
   4010    it('returns nil if the pattern fails to match', function()
   4011      eq(
   4012        nil,
   4013        exec_lua(function()
   4014          local msg = 'The answer to life, the universe, and everything is'
   4015          return vim.diagnostic.match(msg, 'This definitely will not match', {})
   4016        end)
   4017      )
   4018    end)
   4019 
   4020    it('respects default values', function()
   4021      local msg = 'anna.txt:1:Happy families are all alike'
   4022      local diagnostic = {
   4023        severity = exec_lua [[return vim.diagnostic.severity.INFO]],
   4024        lnum = 0,
   4025        col = 0,
   4026        end_lnum = 0,
   4027        end_col = 0,
   4028        message = 'Happy families are all alike',
   4029      }
   4030      eq(
   4031        diagnostic,
   4032        exec_lua(function()
   4033          return vim.diagnostic.match(
   4034            msg,
   4035            '^[^:]+:(%d+):(.+)$',
   4036            { 'lnum', 'message' },
   4037            nil,
   4038            { severity = vim.diagnostic.severity.INFO }
   4039          )
   4040        end)
   4041      )
   4042    end)
   4043 
   4044    it('accepts a severity map', function()
   4045      local msg = '46:FATAL:Et tu, Brute?'
   4046      local diagnostic = {
   4047        severity = exec_lua [[return vim.diagnostic.severity.ERROR]],
   4048        lnum = 45,
   4049        col = 0,
   4050        end_lnum = 45,
   4051        end_col = 0,
   4052        message = 'Et tu, Brute?',
   4053      }
   4054      eq(
   4055        diagnostic,
   4056        exec_lua(function()
   4057          return vim.diagnostic.match(
   4058            msg,
   4059            '^(%d+):(%w+):(.+)$',
   4060            { 'lnum', 'severity', 'message' },
   4061            { FATAL = vim.diagnostic.severity.ERROR }
   4062          )
   4063        end)
   4064      )
   4065    end)
   4066  end)
   4067 
   4068  describe('toqflist() and fromqflist()', function()
   4069    it('works', function()
   4070      local result = exec_lua(function()
   4071        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   4072          _G.make_error('Error 1', 0, 1, 0, 1),
   4073          _G.make_error('Error 2', 1, 1, 1, 1),
   4074          _G.make_warning('Warning', 2, 2, 2, 2),
   4075        })
   4076 
   4077        local diagnostics = vim.diagnostic.get(_G.diagnostic_bufnr)
   4078        vim.fn.setqflist(vim.diagnostic.toqflist(diagnostics))
   4079        local list = vim.fn.getqflist()
   4080        local new_diagnostics = vim.diagnostic.fromqflist(list)
   4081 
   4082        -- Remove extra properties not present in the return value of fromlist()
   4083        for _, v in ipairs(diagnostics) do
   4084          v._extmark_id = nil
   4085          v.namespace = nil
   4086        end
   4087 
   4088        return { diagnostics, new_diagnostics }
   4089      end)
   4090      eq(result[1], result[2])
   4091    end)
   4092 
   4093    it('merge_lines=true merges continuation lines', function()
   4094      local result = exec_lua(function()
   4095        local qflist = {
   4096          {
   4097            bufnr = 1,
   4098            lnum = 10,
   4099            col = 5,
   4100            end_lnum = 10,
   4101            end_col = 10,
   4102            text = 'error: [GHC-83865]',
   4103            type = 'E',
   4104            nr = 0,
   4105            valid = 1,
   4106          },
   4107          {
   4108            bufnr = 1,
   4109            lnum = 0,
   4110            col = 0,
   4111            end_lnum = 0,
   4112            end_col = 0,
   4113            text = "    Couldn't match expected type",
   4114            type = '',
   4115            nr = 0,
   4116            valid = 0,
   4117          },
   4118          {
   4119            bufnr = 1,
   4120            lnum = 0,
   4121            col = 0,
   4122            end_lnum = 0,
   4123            end_col = 0,
   4124            text = '    with actual type',
   4125            type = '',
   4126            nr = 0,
   4127            valid = 0,
   4128          },
   4129          {
   4130            bufnr = 1,
   4131            lnum = 20,
   4132            col = 1,
   4133            end_lnum = 20,
   4134            end_col = 5,
   4135            text = 'warning: unused',
   4136            type = 'W',
   4137            nr = 0,
   4138            valid = 1,
   4139          },
   4140        }
   4141        return vim.diagnostic.fromqflist(qflist, { merge_lines = true })
   4142      end)
   4143 
   4144      eq(2, #result)
   4145      eq(
   4146        "error: [GHC-83865]\n    Couldn't match expected type\n    with actual type",
   4147        result[1].message
   4148      )
   4149      eq('warning: unused', result[2].message)
   4150    end)
   4151 
   4152    it('merge_lines=false ignores continuation lines', function()
   4153      local result = exec_lua(function()
   4154        local qflist = {
   4155          {
   4156            bufnr = 1,
   4157            lnum = 10,
   4158            col = 5,
   4159            end_lnum = 10,
   4160            end_col = 10,
   4161            text = 'error: main',
   4162            type = 'E',
   4163            nr = 0,
   4164            valid = 1,
   4165          },
   4166          {
   4167            bufnr = 1,
   4168            lnum = 0,
   4169            col = 0,
   4170            end_lnum = 0,
   4171            end_col = 0,
   4172            text = 'continuation',
   4173            type = '',
   4174            nr = 0,
   4175            valid = 0,
   4176          },
   4177        }
   4178        return vim.diagnostic.fromqflist(qflist)
   4179      end)
   4180 
   4181      eq(1, #result)
   4182      eq('error: main', result[1].message)
   4183    end)
   4184  end)
   4185 
   4186  describe('status()', function()
   4187    it('returns empty string if no diagnostics', function()
   4188      local result = exec_lua(function()
   4189        vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {})
   4190        return vim.diagnostic.status()
   4191      end)
   4192 
   4193      eq('', result)
   4194    end)
   4195 
   4196    it('returns count for each diagnostic kind', function()
   4197      local result = exec_lua(function()
   4198        vim.diagnostic.set(_G.diagnostic_ns, 0, {
   4199          _G.make_error('Error 1', 0, 1, 0, 1),
   4200 
   4201          _G.make_warning('Warning 1', 2, 2, 2, 2),
   4202          _G.make_warning('Warning 2', 2, 2, 2, 2),
   4203 
   4204          _G.make_info('Info 1', 3, 3, 3, 3),
   4205          _G.make_info('Info 2', 3, 3, 3, 3),
   4206          _G.make_info('Info 3', 3, 3, 3, 3),
   4207 
   4208          _G.make_hint('Hint 1', 4, 4, 4, 4),
   4209          _G.make_hint('Hint 2', 4, 4, 4, 4),
   4210          _G.make_hint('Hint 3', 4, 4, 4, 4),
   4211          _G.make_hint('Hint 4', 4, 4, 4, 4),
   4212        })
   4213        return vim.diagnostic.status()
   4214      end)
   4215 
   4216      eq(
   4217        '%#DiagnosticSignError#E:1 %#DiagnosticSignWarn#W:2 %#DiagnosticSignInfo#I:3 %#DiagnosticSignHint#H:4%##',
   4218        result
   4219      )
   4220 
   4221      exec_lua('vim.cmd.enew()')
   4222 
   4223      -- Empty diagnostics for a buffer without diagnostics
   4224      eq(
   4225        '',
   4226        exec_lua(function()
   4227          return vim.diagnostic.status()
   4228        end)
   4229      )
   4230    end)
   4231 
   4232    it('uses text from diagnostic.config().status.text[severity]', function()
   4233      local result = exec_lua(function()
   4234        vim.diagnostic.config({
   4235          status = {
   4236            text = {
   4237              [vim.diagnostic.severity.ERROR] = '⨯',
   4238              [vim.diagnostic.severity.WARN] = '⚠︎',
   4239            },
   4240          },
   4241        })
   4242 
   4243        vim.diagnostic.set(_G.diagnostic_ns, 0, {
   4244          _G.make_error('Error 1', 0, 1, 0, 1),
   4245          _G.make_warning('Warning 1', 2, 2, 2, 2),
   4246        })
   4247 
   4248        return vim.diagnostic.status()
   4249      end)
   4250 
   4251      eq('%#DiagnosticSignError#⨯:1 %#DiagnosticSignWarn#⚠︎:1%##', result)
   4252    end)
   4253  end)
   4254 
   4255  describe('handlers', function()
   4256    it('checks that a new handler is a table', function()
   4257      matches(
   4258        [[.*handler: expected table, got string.*]],
   4259        pcall_err(exec_lua, [[ vim.diagnostic.handlers.foo = "bar" ]])
   4260      )
   4261      matches(
   4262        [[.*handler: expected table, got function.*]],
   4263        pcall_err(exec_lua, [[ vim.diagnostic.handlers.foo = function() end ]])
   4264      )
   4265    end)
   4266 
   4267    it('can add new handlers', function()
   4268      eq(
   4269        true,
   4270        exec_lua(function()
   4271          local handler_called = false
   4272          vim.diagnostic.handlers.test = {
   4273            show = function(namespace, bufnr, diagnostics, opts)
   4274              assert(namespace == _G.diagnostic_ns)
   4275              assert(bufnr == _G.diagnostic_bufnr)
   4276              assert(#diagnostics == 1)
   4277              assert(opts.test.some_opt == 42)
   4278              handler_called = true
   4279            end,
   4280          }
   4281 
   4282          vim.diagnostic.config({ test = { some_opt = 42 } })
   4283          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   4284            _G.make_warning('Warning', 0, 0, 0, 0),
   4285          })
   4286          return handler_called
   4287        end)
   4288      )
   4289    end)
   4290 
   4291    it('can disable handlers by setting the corresponding option to false', function()
   4292      eq(
   4293        false,
   4294        exec_lua(function()
   4295          local handler_called = false
   4296          vim.diagnostic.handlers.test = {
   4297            show = function(_, _, _, _)
   4298              handler_called = true
   4299            end,
   4300          }
   4301 
   4302          vim.diagnostic.config({ test = false })
   4303          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   4304            _G.make_warning('Warning', 0, 0, 0, 0),
   4305          })
   4306          return handler_called
   4307        end)
   4308      )
   4309    end)
   4310 
   4311    it("always calls a handler's hide function if defined", function()
   4312      eq(
   4313        { false, true },
   4314        exec_lua(function()
   4315          local hide_called = false
   4316          local show_called = false
   4317          vim.diagnostic.handlers.test = {
   4318            show = function(_, _, _, _)
   4319              show_called = true
   4320            end,
   4321            hide = function(namespace, bufnr)
   4322              assert(namespace == _G.diagnostic_ns)
   4323              assert(bufnr == _G.diagnostic_bufnr)
   4324              hide_called = true
   4325            end,
   4326          }
   4327 
   4328          vim.diagnostic.config({ test = false })
   4329          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   4330            _G.make_warning('Warning', 0, 0, 0, 0),
   4331          })
   4332          vim.diagnostic.hide(_G.diagnostic_ns, _G.diagnostic_bufnr)
   4333          return { show_called, hide_called }
   4334        end)
   4335      )
   4336    end)
   4337 
   4338    it('triggers the autocommand when diagnostics are set', function()
   4339      eq(
   4340        { true, true },
   4341        exec_lua(function()
   4342          -- Set a different buffer as current to test that <abuf> is being set properly in
   4343          -- DiagnosticChanged callbacks
   4344          local tmp = vim.api.nvim_create_buf(false, true)
   4345          vim.api.nvim_set_current_buf(tmp)
   4346 
   4347          local triggered = {}
   4348          vim.api.nvim_create_autocmd('DiagnosticChanged', {
   4349            callback = function(args)
   4350              triggered = { args.buf, #args.data.diagnostics }
   4351            end,
   4352          })
   4353          vim.api.nvim_buf_set_name(_G.diagnostic_bufnr, 'test | test')
   4354          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   4355            _G.make_error('Diagnostic', 0, 0, 0, 0),
   4356          })
   4357          return {
   4358            triggered[1] == _G.diagnostic_bufnr,
   4359            triggered[2] == 1,
   4360          }
   4361        end)
   4362      )
   4363    end)
   4364 
   4365    it('triggers the autocommand when diagnostics are cleared', function()
   4366      eq(
   4367        true,
   4368        exec_lua(function()
   4369          local tmp = vim.api.nvim_create_buf(false, true)
   4370          vim.api.nvim_set_current_buf(tmp)
   4371          vim.g.diagnostic_autocmd_triggered = 0
   4372          vim.cmd(
   4373            'autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = +expand("<abuf>")'
   4374          )
   4375          vim.api.nvim_buf_set_name(_G.diagnostic_bufnr, 'test | test')
   4376          vim.diagnostic.reset(_G.diagnostic_ns, _G.diagnostic_bufnr)
   4377          return vim.g.diagnostic_autocmd_triggered == _G.diagnostic_bufnr
   4378        end)
   4379      )
   4380    end)
   4381 
   4382    it('is_enabled', function()
   4383      eq(
   4384        { false, false, false, false, false },
   4385        exec_lua(function()
   4386          vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {
   4387            _G.make_error('Diagnostic #1', 1, 1, 1, 1),
   4388          })
   4389          vim.api.nvim_set_current_buf(_G.diagnostic_bufnr)
   4390          vim.diagnostic.enable(false)
   4391          return {
   4392            vim.diagnostic.is_enabled(),
   4393            vim.diagnostic.is_enabled { bufnr = 0 },
   4394            vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr },
   4395            vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns },
   4396            vim.diagnostic.is_enabled { bufnr = 0, ns_id = _G.diagnostic_ns },
   4397          }
   4398        end)
   4399      )
   4400 
   4401      eq(
   4402        { true, true, true, true, true },
   4403        exec_lua(function()
   4404          vim.diagnostic.enable()
   4405          return {
   4406            vim.diagnostic.is_enabled(),
   4407            vim.diagnostic.is_enabled { bufnr = 0 },
   4408            vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr },
   4409            vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns },
   4410            vim.diagnostic.is_enabled { bufnr = 0, ns_id = _G.diagnostic_ns },
   4411          }
   4412        end)
   4413      )
   4414    end)
   4415  end)
   4416 end)