neovim

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

bufhl_spec.lua (32207B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local clear, feed, insert = n.clear, n.feed, n.insert
      6 local command, neq = n.command, t.neq
      7 local api = n.api
      8 local eq = t.eq
      9 local pcall_err = t.pcall_err
     10 local set_virtual_text = api.nvim_buf_set_virtual_text
     11 
     12 describe('Buffer highlighting', function()
     13  local screen
     14 
     15  before_each(function()
     16    clear()
     17    command('syntax on')
     18    screen = Screen.new(40, 8)
     19    screen:set_default_attr_ids({
     20      [1] = { bold = true, foreground = Screen.colors.Blue },
     21      [2] = { foreground = Screen.colors.Fuchsia }, -- String
     22      [3] = { foreground = Screen.colors.Brown, bold = true }, -- Statement
     23      [4] = { foreground = Screen.colors.SlateBlue }, -- Special
     24      [5] = { bold = true, foreground = Screen.colors.SlateBlue },
     25      [6] = { foreground = Screen.colors.DarkCyan }, -- Identifier
     26      [7] = { bold = true },
     27      [8] = { underline = true, bold = true, foreground = Screen.colors.SlateBlue },
     28      [9] = { foreground = Screen.colors.SlateBlue, underline = true },
     29      [10] = { foreground = Screen.colors.Red },
     30      [11] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
     31      [12] = { foreground = Screen.colors.Blue1 },
     32      [13] = { foreground = Screen.colors.Black, background = Screen.colors.LightGrey },
     33      [14] = { background = Screen.colors.Gray90 },
     34      [15] = { background = Screen.colors.Gray90, bold = true, foreground = Screen.colors.Brown },
     35      [16] = { foreground = Screen.colors.Magenta, background = Screen.colors.Gray90 },
     36      [17] = { foreground = Screen.colors.Magenta, background = Screen.colors.LightRed },
     37      [18] = { background = Screen.colors.LightRed },
     38      [19] = { foreground = Screen.colors.Blue1, background = Screen.colors.LightRed },
     39      [20] = { underline = true, bold = true, foreground = Screen.colors.Cyan4 },
     40    })
     41  end)
     42 
     43  local add_highlight = api.nvim_buf_add_highlight
     44  local clear_namespace = api.nvim_buf_clear_namespace
     45 
     46  it('works', function()
     47    insert([[
     48      these are some lines
     49      with colorful text]])
     50    feed('+')
     51 
     52    screen:expect([[
     53      these are some lines                    |
     54      with colorful tex^t                      |
     55      {1:~                                       }|*5
     56                                              |
     57    ]])
     58 
     59    add_highlight(0, -1, 'String', 0, 10, 14)
     60    add_highlight(0, -1, 'Statement', 1, 5, -1)
     61 
     62    screen:expect([[
     63      these are {2:some} lines                    |
     64      with {3:colorful tex^t}                      |
     65      {1:~                                       }|*5
     66                                              |
     67    ]])
     68 
     69    feed('ggo<esc>')
     70    screen:expect([[
     71      these are {2:some} lines                    |
     72      ^                                        |
     73      with {3:colorful text}                      |
     74      {1:~                                       }|*4
     75                                              |
     76    ]])
     77 
     78    clear_namespace(0, -1, 0, -1)
     79    screen:expect([[
     80      these are some lines                    |
     81      ^                                        |
     82      with colorful text                      |
     83      {1:~                                       }|*4
     84                                              |
     85    ]])
     86  end)
     87 
     88  describe('support using multiple namespaces', function()
     89    local id1, id2
     90    before_each(function()
     91      insert([[
     92        a longer example
     93        in order to demonstrate
     94        combining highlights
     95        from different sources]])
     96 
     97      command('hi ImportantWord gui=bold cterm=bold')
     98      id1 = add_highlight(0, 0, 'ImportantWord', 0, 2, 8)
     99      add_highlight(0, id1, 'ImportantWord', 1, 12, -1)
    100      add_highlight(0, id1, 'ImportantWord', 2, 0, 9)
    101      add_highlight(0, id1, 'ImportantWord', 3, 5, 14)
    102 
    103      -- add_highlight can be called like this to get a new source
    104      -- without adding any highlight
    105      id2 = add_highlight(0, 0, '', 0, 0, 0)
    106      neq(id1, id2)
    107 
    108      add_highlight(0, id2, 'Special', 0, 2, 8)
    109      add_highlight(0, id2, 'Identifier', 1, 3, 8)
    110      add_highlight(0, id2, 'Special', 1, 14, 20)
    111      add_highlight(0, id2, 'Underlined', 2, 6, 12)
    112      add_highlight(0, id2, 'Underlined', 3, 0, 9)
    113 
    114      screen:expect([[
    115        a {5:longer} example                        |
    116        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    117        {7:combin}{8:ing}{9: hi}ghlights                    |
    118        {9:from }{8:diff}{7:erent} source^s                  |
    119        {1:~                                       }|*3
    120                                                |
    121      ]])
    122    end)
    123 
    124    it('and clearing the first added', function()
    125      clear_namespace(0, id1, 0, -1)
    126      screen:expect([[
    127        a {4:longer} example                        |
    128        in {6:order} to de{4:monstr}ate                 |
    129        combin{9:ing hi}ghlights                    |
    130        {9:from diff}erent source^s                  |
    131        {1:~                                       }|*3
    132                                                |
    133      ]])
    134    end)
    135 
    136    it('and clearing using deprecated name', function()
    137      api.nvim_buf_clear_highlight(0, id1, 0, -1)
    138      screen:expect([[
    139        a {4:longer} example                        |
    140        in {6:order} to de{4:monstr}ate                 |
    141        combin{9:ing hi}ghlights                    |
    142        {9:from diff}erent source^s                  |
    143        {1:~                                       }|*3
    144                                                |
    145      ]])
    146    end)
    147 
    148    it('and clearing the second added', function()
    149      clear_namespace(0, id2, 0, -1)
    150      screen:expect([[
    151        a {7:longer} example                        |
    152        in order to {7:demonstrate}                 |
    153        {7:combining} highlights                    |
    154        from {7:different} source^s                  |
    155        {1:~                                       }|*3
    156                                                |
    157      ]])
    158    end)
    159 
    160    it('and clearing line ranges', function()
    161      clear_namespace(0, -1, 0, 1)
    162      clear_namespace(0, id1, 1, 2)
    163      clear_namespace(0, id2, 2, -1)
    164      screen:expect([[
    165        a longer example                        |
    166        in {6:order} to de{4:monstr}ate                 |
    167        {7:combining} highlights                    |
    168        from {7:different} source^s                  |
    169        {1:~                                       }|*3
    170                                                |
    171      ]])
    172    end)
    173 
    174    it('and renumbering lines', function()
    175      feed('3Gddggo<esc>')
    176      screen:expect([[
    177        a {5:longer} example                        |
    178        ^                                        |
    179        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    180        {9:from }{8:diff}{7:erent} sources                  |
    181        {1:~                                       }|*3
    182                                                |
    183      ]])
    184 
    185      -- TODO(bfedl): this behaves a bit weirdly due to the highlight on
    186      -- the deleted line wrapping around. we should invalidate
    187      -- highlights when they are completely inside deleted text
    188      command('3move 4')
    189      screen:expect {
    190        grid = [[
    191        a {5:longer} example                        |
    192                                                |
    193        {8:from different sources}                  |
    194        {8:^in }{20:order}{8: to demonstrate}                 |
    195        {1:~                                       }|*3
    196                                                |
    197      ]],
    198      }
    199      --screen:expect([[
    200      --  a {5:longer} example                        |
    201      --                                          |
    202      --  {9:from }{8:diff}{7:erent} sources                  |
    203      --  ^in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    204      --  {1:~                                       }|*3
    205      --                                          |
    206      --]])
    207 
    208      command('undo')
    209      screen:expect {
    210        grid = [[
    211        a {5:longer} example                        |
    212        ^                                        |
    213        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    214        {9:from }{8:diff}{7:erent} sources                  |
    215        {1:~                                       }|*3
    216        1 change; before #4  {MATCH:.*}|
    217      ]],
    218      }
    219 
    220      command('undo')
    221      screen:expect {
    222        grid = [[
    223        ^a {5:longer} example                        |
    224        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    225        {9:from }{8:diff}{7:erent} sources                  |
    226        {1:~                                       }|*4
    227        1 line less; before #3  {MATCH:.*}|
    228      ]],
    229      }
    230 
    231      command('undo')
    232      screen:expect {
    233        grid = [[
    234        a {5:longer} example                        |
    235        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    236        {7:^combin}{8:ing}{9: hi}ghlights                    |
    237        {9:from }{8:diff}{7:erent} sources                  |
    238        {1:~                                       }|*3
    239        1 more line; before #2  {MATCH:.*}|
    240      ]],
    241      }
    242    end)
    243 
    244    it('and moving lines around', function()
    245      command('2move 3')
    246      screen:expect {
    247        grid = [[
    248        a {5:longer} example                        |
    249        {7:combin}{8:ing}{9: hi}ghlights                    |
    250        ^in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    251        {9:from }{8:diff}{7:erent} sources                  |
    252        {1:~                                       }|*3
    253                                                |
    254      ]],
    255      }
    256 
    257      command('1,2move 4')
    258      screen:expect {
    259        grid = [[
    260        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    261        {9:from }{8:diff}{7:erent} sources                  |
    262        a {5:longer} example                        |
    263        {7:^combin}{8:ing}{9: hi}ghlights                    |
    264        {1:~                                       }|*3
    265                                                |
    266      ]],
    267      }
    268 
    269      command('undo')
    270      screen:expect {
    271        grid = [[
    272        a {5:longer} example                        |
    273        {7:combin}{8:ing}{9: hi}ghlights                    |
    274        ^in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    275        {9:from }{8:diff}{7:erent} sources                  |
    276        {1:~                                       }|*3
    277        2 changes; before #3  {MATCH:.*}|
    278      ]],
    279      }
    280 
    281      command('undo')
    282      screen:expect {
    283        grid = [[
    284        a {5:longer} example                        |
    285        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    286        {7:combin}{8:ing}{9: hi}ghlights                    |
    287        {9:from }{8:diff}{7:erent} source^s                  |
    288        {1:~                                       }|*3
    289        1 change; before #2  {MATCH:.*}|
    290      ]],
    291      }
    292    end)
    293 
    294    it('and adjusting columns', function()
    295      -- insert before
    296      feed('ggiquite <esc>')
    297      screen:expect {
    298        grid = [[
    299        quite^ a {5:longer} example                  |
    300        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    301        {7:combin}{8:ing}{9: hi}ghlights                    |
    302        {9:from }{8:diff}{7:erent} sources                  |
    303        {1:~                                       }|*3
    304                                                |
    305      ]],
    306      }
    307 
    308      feed('u')
    309      screen:expect {
    310        grid = [[
    311        ^a {5:longer} example                        |
    312        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    313        {7:combin}{8:ing}{9: hi}ghlights                    |
    314        {9:from }{8:diff}{7:erent} sources                  |
    315        {1:~                                       }|*3
    316        1 change; before #2  {MATCH:.*}|
    317      ]],
    318      }
    319 
    320      -- change/insert in the middle
    321      feed('+fesAAAA')
    322      screen:expect {
    323        grid = [[
    324        a {5:longer} example                        |
    325        in {6:ordAAAA^r} to {7:de}{5:monstr}{7:ate}              |
    326        {7:combin}{8:ing}{9: hi}ghlights                    |
    327        {9:from }{8:diff}{7:erent} sources                  |
    328        {1:~                                       }|*3
    329        {7:-- INSERT --}                            |
    330      ]],
    331      }
    332 
    333      feed('<esc>tdD')
    334      screen:expect {
    335        grid = [[
    336        a {5:longer} example                        |
    337        in {6:ordAAAAr} t^o                          |
    338        {7:combin}{8:ing}{9: hi}ghlights                    |
    339        {9:from }{8:diff}{7:erent} sources                  |
    340        {1:~                                       }|*3
    341                                                |
    342      ]],
    343      }
    344 
    345      feed('u')
    346      screen:expect {
    347        grid = [[
    348        a {5:longer} example                        |
    349        in {6:ordAAAAr} to^ {7:de}{5:monstr}{7:ate}              |
    350        {7:combin}{8:ing}{9: hi}ghlights                    |
    351        {9:from }{8:diff}{7:erent} sources                  |
    352        {1:~                                       }|*3
    353        1 change; before #4  {MATCH:.*}|
    354      ]],
    355      }
    356 
    357      feed('u')
    358      screen:expect {
    359        grid = [[
    360        a {5:longer} example                        |
    361        in {6:ord^er} to {7:de}{5:monstr}{7:ate}                 |
    362        {7:combin}{8:ing}{9: hi}ghlights                    |
    363        {9:from }{8:diff}{7:erent} sources                  |
    364        {1:~                                       }|*3
    365        1 change; before #3  {MATCH:.*}|
    366      ]],
    367      }
    368    end)
    369 
    370    it('and joining lines', function()
    371      feed('ggJJJ')
    372      screen:expect {
    373        grid = [[
    374        a {5:longer} example in {6:order} to {7:de}{5:monstr}{7:ate}|
    375         {7:combin}{8:ing}{9: hi}ghlights^ {9:from }{8:diff}{7:erent} sou|
    376        rces                                    |
    377        {1:~                                       }|*4
    378                                                |
    379      ]],
    380      }
    381 
    382      feed('uuu')
    383      screen:expect {
    384        grid = [[
    385        ^a {5:longer} example                        |
    386        in {6:order} to {7:de}{5:monstr}{7:ate}                 |
    387        {7:combin}{8:ing}{9: hi}ghlights                    |
    388        {9:from }{8:diff}{7:erent} sources                  |
    389        {1:~                                       }|*3
    390        1 more line; before #2  {MATCH:.*}|
    391      ]],
    392      }
    393    end)
    394 
    395    it('and splitting lines', function()
    396      feed('2Gtti<cr>')
    397      screen:expect {
    398        grid = [[
    399        a {5:longer} example                        |
    400        in {6:order}                                |
    401        ^ to {7:de}{5:monstr}{7:ate}                         |
    402        {7:combin}{8:ing}{9: hi}ghlights                    |
    403        {9:from }{8:diff}{7:erent} sources                  |
    404        {1:~                                       }|*2
    405        {7:-- INSERT --}                            |
    406      ]],
    407      }
    408 
    409      feed('<esc>tsi<cr>')
    410      screen:expect {
    411        grid = [[
    412        a {5:longer} example                        |
    413        in {6:order}                                |
    414         to {7:de}{5:mo}                                |
    415        {5:^nstr}{7:ate}                                 |
    416        {7:combin}{8:ing}{9: hi}ghlights                    |
    417        {9:from }{8:diff}{7:erent} sources                  |
    418        {1:~                                       }|
    419        {7:-- INSERT --}                            |
    420      ]],
    421      }
    422 
    423      feed('<esc>u')
    424      screen:expect {
    425        grid = [[
    426        a {5:longer} example                        |
    427        in {6:order}                                |
    428         to {7:de}{5:mo^nstr}{7:ate}                         |
    429        {7:combin}{8:ing}{9: hi}ghlights                    |
    430        {9:from }{8:diff}{7:erent} sources                  |
    431        {1:~                                       }|*2
    432        1 line less; before #3  {MATCH:.*}|
    433      ]],
    434      }
    435 
    436      feed('<esc>u')
    437      screen:expect {
    438        grid = [[
    439        a {5:longer} example                        |
    440        in {6:order}^ to {7:de}{5:monstr}{7:ate}                 |
    441        {7:combin}{8:ing}{9: hi}ghlights                    |
    442        {9:from }{8:diff}{7:erent} sources                  |
    443        {1:~                                       }|*3
    444        1 line less; before #2  {MATCH:.*}|
    445      ]],
    446      }
    447    end)
    448  end)
    449 
    450  pending('prioritizes latest added highlight', function()
    451    insert([[
    452      three overlapping colors]])
    453    add_highlight(0, 0, 'Identifier', 0, 6, 17)
    454    add_highlight(0, 0, 'String', 0, 14, 23)
    455    local id = add_highlight(0, 0, 'Special', 0, 0, 9)
    456 
    457    screen:expect([[
    458      {4:three ove}{6:rlapp}{2:ing color}^s                |
    459      {1:~                                       }|*6
    460                                              |
    461    ]])
    462 
    463    clear_namespace(0, id, 0, 1)
    464    screen:expect([[
    465      three {6:overlapp}{2:ing color}^s                |
    466      {1:~                                       }|*6
    467                                              |
    468    ]])
    469  end)
    470 
    471  it('prioritizes earlier highlight groups (TEMP)', function()
    472    insert([[
    473      three overlapping colors]])
    474    add_highlight(0, 0, 'Identifier', 0, 6, 17)
    475    add_highlight(0, 0, 'String', 0, 14, 23)
    476    local id = add_highlight(0, 0, 'Special', 0, 0, 9)
    477 
    478    screen:expect {
    479      grid = [[
    480      {4:three }{6:overlapp}{2:ing color}^s                |
    481      {1:~                                       }|*6
    482                                              |
    483    ]],
    484    }
    485 
    486    clear_namespace(0, id, 0, 1)
    487    screen:expect {
    488      grid = [[
    489      three {6:overlapp}{2:ing color}^s                |
    490      {1:~                                       }|*6
    491                                              |
    492    ]],
    493    }
    494  end)
    495 
    496  it('respects priority', function()
    497    local id = api.nvim_create_namespace('')
    498    insert [[foobar]]
    499 
    500    api.nvim_buf_set_extmark(0, id, 0, 0, {
    501      end_line = 0,
    502      end_col = 5,
    503      hl_group = 'Statement',
    504      priority = 100,
    505    })
    506    api.nvim_buf_set_extmark(0, id, 0, 0, {
    507      end_line = 0,
    508      end_col = 6,
    509      hl_group = 'String',
    510      priority = 1,
    511    })
    512 
    513    screen:expect [[
    514      {3:fooba}{2:^r}                                  |
    515      {1:~                                       }|*6
    516                                              |
    517    ]]
    518 
    519    clear_namespace(0, id, 0, -1)
    520    screen:expect {
    521      grid = [[
    522      fooba^r                                  |
    523      {1:~                                       }|*6
    524                                              |
    525    ]],
    526    }
    527 
    528    api.nvim_buf_set_extmark(0, id, 0, 0, {
    529      end_line = 0,
    530      end_col = 6,
    531      hl_group = 'String',
    532      priority = 1,
    533    })
    534    api.nvim_buf_set_extmark(0, id, 0, 0, {
    535      end_line = 0,
    536      end_col = 5,
    537      hl_group = 'Statement',
    538      priority = 100,
    539    })
    540 
    541    screen:expect [[
    542      {3:fooba}{2:^r}                                  |
    543      {1:~                                       }|*6
    544                                              |
    545    ]]
    546  end)
    547 
    548  it('works with multibyte text', function()
    549    insert([[
    550      Ta båten över sjön!]])
    551    add_highlight(0, -1, 'Identifier', 0, 3, 9)
    552    add_highlight(0, -1, 'String', 0, 16, 21)
    553 
    554    screen:expect([[
    555      Ta {6:båten} över {2:sjön}^!                     |
    556      {1:~                                       }|*6
    557                                              |
    558    ]])
    559  end)
    560 
    561  it('works with new syntax groups', function()
    562    insert([[
    563      fancy code in a new fancy language]])
    564    add_highlight(0, -1, 'FancyLangItem', 0, 0, 5)
    565    screen:expect([[
    566      fancy code in a new fancy languag^e      |
    567      {1:~                                       }|*6
    568                                              |
    569    ]])
    570 
    571    command('hi FancyLangItem guifg=red')
    572    screen:expect([[
    573      {10:fancy} code in a new fancy languag^e      |
    574      {1:~                                       }|*6
    575                                              |
    576    ]])
    577  end)
    578 
    579  describe('virtual text decorations', function()
    580    local id1, id2 ---@type integer, integer
    581    before_each(function()
    582      insert([[
    583        1 + 2
    584        3 +
    585        x = 4]])
    586      feed('O<esc>20A5, <esc>gg')
    587      screen:expect([[
    588        ^1 + 2                                   |
    589        3 +                                     |
    590        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    591        , 5, 5, 5, 5, 5, 5,                     |
    592        x = 4                                   |
    593        {1:~                                       }|*2
    594                                                |
    595      ]])
    596 
    597      id1 = set_virtual_text(0, 0, 0, { { '=', 'Statement' }, { ' 3', 'Number' } }, {})
    598      set_virtual_text(0, id1, 1, { { 'ERROR:', 'ErrorMsg' }, { ' invalid syntax' } }, {})
    599      id2 = set_virtual_text(0, 0, 2, {
    600        {
    601          'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
    602        },
    603      }, {})
    604      neq(id2, id1)
    605    end)
    606 
    607    it('works', function()
    608      screen:expect([[
    609        ^1 + 2 {3:=}{2: 3}                               |
    610        3 + {11:ERROR:} invalid syntax               |
    611        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    612        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    613        x = 4                                   |
    614        {1:~                                       }|*2
    615                                                |
    616      ]])
    617 
    618      clear_namespace(0, id1, 0, -1)
    619      screen:expect([[
    620        ^1 + 2                                   |
    621        3 +                                     |
    622        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    623        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    624        x = 4                                   |
    625        {1:~                                       }|*2
    626                                                |
    627      ]])
    628 
    629      -- Handles doublewidth chars, leaving a space if truncating
    630      -- in the middle of a char
    631      eq(
    632        -1,
    633        set_virtual_text(
    634          0,
    635          -1,
    636          1,
    637          { { '暗x事zz速野谷質結育副住新覚丸活解終事', 'Comment' } },
    638          {}
    639        )
    640      )
    641      screen:expect([[
    642        ^1 + 2                                   |
    643        3 + {12:x事zz速野谷質結育副住新覚丸活解終 }|
    644        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    645        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    646        x = 4                                   |
    647        {1:~                                       }|*2
    648                                                |
    649      ]])
    650 
    651      feed('2Gx')
    652      screen:expect([[
    653        1 + 2                                   |
    654        ^ + {12:x事zz速野谷質結育副住新覚丸活解終事}|
    655        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    656        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    657        x = 4                                   |
    658        {1:~                                       }|*2
    659                                                |
    660      ]])
    661 
    662      feed('2Gdd')
    663      -- TODO(bfredl): currently decorations get moved from a deleted line
    664      -- to the next one. We might want to add "invalidation" when deleting
    665      -- over a decoration.
    666      screen:expect {
    667        grid = [[
    668        1 + 2                                   |
    669        ^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    670        , 5, 5, 5, 5, 5, 5,  {12:暗x事zz速野谷質結育}|
    671        x = 4                                   |
    672        {1:~                                       }|*3
    673                                                |
    674      ]],
    675      }
    676      --screen:expect([[
    677      --  1 + 2                                   |
    678      --  ^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    679      --  , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    680      --  x = 4                                   |
    681      --  {1:~                                       }|*3
    682      --                                          |
    683      --]])
    684    end)
    685 
    686    it('validates contents', function()
    687      -- this used to leak memory
    688      eq(
    689        "Invalid 'chunk': expected Array, got String",
    690        pcall_err(set_virtual_text, 0, id1, 0, { 'texty' }, {})
    691      )
    692      eq(
    693        "Invalid 'chunk': expected Array, got String",
    694        pcall_err(set_virtual_text, 0, id1, 0, { { 'very' }, 'texty' }, {})
    695      )
    696    end)
    697 
    698    it('can be retrieved', function()
    699      local get_extmarks = api.nvim_buf_get_extmarks
    700      local line_count = api.nvim_buf_line_count
    701 
    702      local s1 = { { 'Köttbullar', 'Comment' }, { 'Kräuterbutter' } }
    703      local s2 = { { 'こんにちは', 'Comment' } }
    704 
    705      set_virtual_text(0, id1, 0, s1, {})
    706      eq({
    707        {
    708          1,
    709          0,
    710          0,
    711          {
    712            ns_id = id1,
    713            priority = 0,
    714            virt_text = s1,
    715            -- other details
    716            right_gravity = true,
    717            virt_text_repeat_linebreak = false,
    718            virt_text_pos = 'eol',
    719            virt_text_hide = false,
    720          },
    721        },
    722      }, get_extmarks(0, id1, { 0, 0 }, { 0, -1 }, { details = true }))
    723 
    724      local lastline = line_count(0)
    725      set_virtual_text(0, id1, line_count(0), s2, {})
    726      eq({
    727        {
    728          3,
    729          lastline,
    730          0,
    731          {
    732            ns_id = id1,
    733            priority = 0,
    734            virt_text = s2,
    735            -- other details
    736            right_gravity = true,
    737            virt_text_repeat_linebreak = false,
    738            virt_text_pos = 'eol',
    739            virt_text_hide = false,
    740          },
    741        },
    742      }, get_extmarks(0, id1, { lastline, 0 }, { lastline, -1 }, { details = true }))
    743 
    744      eq({}, get_extmarks(0, id1, { lastline + 9000, 0 }, { lastline + 9000, -1 }, {}))
    745    end)
    746 
    747    it('is not highlighted by visual selection', function()
    748      feed('ggVG')
    749      screen:expect([[
    750        {13:1 + 2} {3:=}{2: 3}                               |
    751        {13:3 +} {11:ERROR:} invalid syntax               |
    752        {13:5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}|
    753        {13:, 5, 5, 5, 5, 5, 5, } Lorem ipsum dolor s|
    754        ^x{13: = 4}                                   |
    755        {1:~                                       }|*2
    756        {7:-- VISUAL LINE --}                       |
    757      ]])
    758 
    759      feed('<esc>')
    760      screen:expect([[
    761        1 + 2 {3:=}{2: 3}                               |
    762        3 + {11:ERROR:} invalid syntax               |
    763        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    764        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    765        ^x = 4                                   |
    766        {1:~                                       }|*2
    767                                                |
    768      ]])
    769 
    770      -- special case: empty line has extra eol highlight
    771      feed('ggd$')
    772      screen:expect([[
    773        ^ {3:=}{2: 3}                                    |
    774        3 + {11:ERROR:} invalid syntax               |
    775        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    776        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    777        x = 4                                   |
    778        {1:~                                       }|*2
    779                                                |
    780      ]])
    781 
    782      feed('jvk')
    783      screen:expect([[
    784        ^ {3:=}{2: 3}                                    |
    785        {13:3} + {11:ERROR:} invalid syntax               |
    786        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    787        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    788        x = 4                                   |
    789        {1:~                                       }|*2
    790        {7:-- VISUAL --}                            |
    791      ]])
    792 
    793      feed('o')
    794      screen:expect([[
    795        {13: }{3:=}{2: 3}                                    |
    796        ^3 + {11:ERROR:} invalid syntax               |
    797        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    798        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    799        x = 4                                   |
    800        {1:~                                       }|*2
    801        {7:-- VISUAL --}                            |
    802      ]])
    803    end)
    804 
    805    it('works with listchars', function()
    806      command('set list listchars+=eol:$')
    807      screen:expect([[
    808        ^1 + 2{1:$}{3:=}{2: 3}                               |
    809        3 +{1:$}{11:ERROR:} invalid syntax               |
    810        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    811        , 5, 5, 5, 5, 5, 5,{1:-$}Lorem ipsum dolor s|
    812        x = 4{1:$}                                  |
    813        {1:~                                       }|*2
    814                                                |
    815      ]])
    816 
    817      clear_namespace(0, -1, 0, -1)
    818      screen:expect([[
    819        ^1 + 2{1:$}                                  |
    820        3 +{1:$}                                    |
    821        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    822        , 5, 5, 5, 5, 5, 5,{1:-$}                   |
    823        x = 4{1:$}                                  |
    824        {1:~                                       }|*2
    825                                                |
    826      ]])
    827    end)
    828 
    829    it('works with cursorline', function()
    830      command('set cursorline')
    831 
    832      screen:expect {
    833        grid = [[
    834        {14:^1 + 2 }{3:=}{2: 3}{14:                               }|
    835        3 + {11:ERROR:} invalid syntax               |
    836        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    837        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    838        x = 4                                   |
    839        {1:~                                       }|*2
    840                                                |
    841      ]],
    842      }
    843 
    844      feed('j')
    845      screen:expect {
    846        grid = [[
    847        1 + 2 {3:=}{2: 3}                               |
    848        {14:^3 + }{11:ERROR:} invalid syntax{14:               }|
    849        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    850        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    851        x = 4                                   |
    852        {1:~                                       }|*2
    853                                                |
    854      ]],
    855      }
    856 
    857      feed('j')
    858      screen:expect {
    859        grid = [[
    860        1 + 2 {3:=}{2: 3}                               |
    861        3 + {11:ERROR:} invalid syntax               |
    862        {14:^5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}|
    863        {14:, 5, 5, 5, 5, 5, 5,  }Lorem ipsum dolor s|
    864        x = 4                                   |
    865        {1:~                                       }|*2
    866                                                |
    867      ]],
    868      }
    869    end)
    870 
    871    it('works with color column', function()
    872      eq(-1, set_virtual_text(0, -1, 3, { { '暗x事', 'Comment' } }, {}))
    873      screen:expect {
    874        grid = [[
    875        ^1 + 2 {3:=}{2: 3}                               |
    876        3 + {11:ERROR:} invalid syntax               |
    877        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    878        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    879        x = 4 {12:暗x事}                             |
    880        {1:~                                       }|*2
    881                                                |
    882      ]],
    883      }
    884 
    885      command('set colorcolumn=9')
    886      screen:expect {
    887        grid = [[
    888        ^1 + 2 {3:=}{2: 3}                               |
    889        3 + {11:ERROR:} invalid syntax               |
    890        5, 5, 5,{18: }5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5|
    891        , 5, 5, 5, 5, 5, 5,  Lorem ipsum dolor s|
    892        x = 4 {12:暗x事}                             |
    893        {1:~                                       }|*2
    894                                                |
    895      ]],
    896      }
    897    end)
    898  end)
    899 
    900  it('and virtual text use the same namespace counter', function()
    901    local base = vim.iter(api.nvim_get_namespaces()):fold(0, function(acc, _, v)
    902      return math.max(acc, v)
    903    end)
    904 
    905    eq(base + 1, add_highlight(0, 0, 'String', 0, 0, -1))
    906    eq(base + 2, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {}))
    907    eq(base + 3, api.nvim_create_namespace('my-ns'))
    908    eq(base + 4, add_highlight(0, 0, 'String', 0, 0, -1))
    909    eq(base + 5, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {}))
    910    eq(base + 6, api.nvim_create_namespace('other-ns'))
    911  end)
    912 end)