neovim

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

semantic_tokens_spec.lua (65066B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 local t_lsp = require('test.functional.plugin.lsp.testutil')
      5 
      6 local command = n.command
      7 local dedent = t.dedent
      8 local eq = t.eq
      9 local exec_lua = n.exec_lua
     10 local feed = n.feed
     11 local insert = n.insert
     12 local api = n.api
     13 
     14 local clear_notrace = t_lsp.clear_notrace
     15 local create_server_definition = t_lsp.create_server_definition
     16 
     17 before_each(function()
     18  clear_notrace()
     19 end)
     20 
     21 after_each(function()
     22  api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
     23 end)
     24 
     25 describe('semantic token highlighting', function()
     26  local screen --- @type test.functional.ui.screen
     27  before_each(function()
     28    screen = Screen.new(40, 16)
     29    screen:set_default_attr_ids {
     30      [1] = { bold = true, foreground = Screen.colors.Blue1 },
     31      [2] = { foreground = Screen.colors.DarkCyan },
     32      [3] = { foreground = Screen.colors.SlateBlue },
     33      [4] = { bold = true, foreground = Screen.colors.SeaGreen },
     34      [5] = { foreground = tonumber('0x6a0dad') },
     35      [6] = { foreground = Screen.colors.Blue1 },
     36      [7] = { bold = true, foreground = Screen.colors.DarkCyan },
     37      [8] = { bold = true, foreground = Screen.colors.SlateBlue },
     38      [9] = { bold = true, foreground = tonumber('0x6a0dad') },
     39      [10] = { bold = true, foreground = Screen.colors.Brown },
     40      [11] = { foreground = Screen.colors.Magenta1 },
     41    }
     42    command([[ hi link @lsp.type.namespace Type ]])
     43    command([[ hi link @lsp.type.function Special ]])
     44    command([[ hi link @lsp.type.comment Comment ]])
     45    command([[ hi @lsp.mod.declaration gui=bold ]])
     46  end)
     47 
     48  describe('general', function()
     49    local text = dedent([[
     50    #include <iostream>
     51 
     52    int main()
     53    {
     54        int x;
     55    #ifdef __cplusplus
     56        std::cout << x << "\n";
     57    #else
     58        printf("%d\n", x);
     59    #endif
     60    }
     61    }]])
     62 
     63    local legend = [[{
     64      "tokenTypes": [
     65        "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
     66      ],
     67      "tokenModifiers": [
     68        "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
     69      ]
     70    }]]
     71 
     72    local response = [[{
     73      "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ],
     74      "resultId": "1"
     75    }]]
     76 
     77    local range_response = [[{
     78      "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025 ],
     79      "resultId": "2"
     80    }]]
     81 
     82    local edit_response = [[{
     83      "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 3, 8192 ], "deleteCount": 25, "start": 5 } ],
     84      "resultId": "3"
     85    }]]
     86 
     87    before_each(function()
     88      exec_lua(create_server_definition)
     89      exec_lua(function()
     90        _G.server = _G._create_server({
     91          capabilities = {
     92            semanticTokensProvider = {
     93              full = { delta = true },
     94              range = false,
     95              legend = vim.fn.json_decode(legend),
     96            },
     97          },
     98          handlers = {
     99            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    100              callback(nil, vim.fn.json_decode(response))
    101            end,
    102            ['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
    103              callback(nil, vim.fn.json_decode(edit_response))
    104            end,
    105          },
    106        })
    107      end)
    108    end)
    109 
    110    it('buffer is highlighted when attached', function()
    111      insert(text)
    112      exec_lua(function()
    113        local bufnr = vim.api.nvim_get_current_buf()
    114        vim.api.nvim_win_set_buf(0, bufnr)
    115        vim.bo[bufnr].filetype = 'some-filetype'
    116        vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    117      end)
    118 
    119      screen:expect {
    120        grid = [[
    121        #include <iostream>                     |
    122                                                |
    123        int {8:main}()                              |
    124        {                                       |
    125            int {7:x};                              |
    126        #ifdef {5:__cplusplus}                      |
    127            {4:std}::{2:cout} << {2:x} << "\n";             |
    128        {6:#else}                                   |
    129        {6:    printf("%d\n", x);}                  |
    130        {6:#endif}                                  |
    131        }                                       |
    132        ^}                                       |
    133        {1:~                                       }|*3
    134                                                |
    135      ]],
    136      }
    137    end)
    138 
    139    it('buffer is highlighted with multiline tokens', function()
    140      insert(text)
    141      exec_lua(function()
    142        _G.server2 = _G._create_server({
    143          capabilities = {
    144            semanticTokensProvider = {
    145              full = { delta = false },
    146              legend = vim.fn.json_decode(legend),
    147            },
    148          },
    149          handlers = {
    150            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    151              callback(nil, {
    152                data = { 5, 0, 82, 0, 0 },
    153                resultId = 1,
    154              })
    155            end,
    156          },
    157        })
    158      end, legend, response, edit_response)
    159      exec_lua(function()
    160        local bufnr = vim.api.nvim_get_current_buf()
    161        vim.api.nvim_win_set_buf(0, bufnr)
    162        vim.bo[bufnr].filetype = 'some-filetype'
    163        vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })
    164      end)
    165 
    166      screen:expect {
    167        grid = [[
    168        #include <iostream>                     |
    169                                                |
    170        int main()                              |
    171        {                                       |
    172            int x;                              |
    173        {2:#ifdef __cplusplus}                      |
    174        {2:    std::cout << x << "\n";}             |
    175        {2:#else}                                   |
    176        {2:    printf("%d\n", x);}                  |
    177        {2:#endif}                                  |
    178        }                                       |
    179        ^}                                       |
    180        {1:~                                       }|*3
    181                                                |
    182      ]],
    183      }
    184    end)
    185 
    186    it('calls both range and full when range is supported', function()
    187      insert(text)
    188      exec_lua(function()
    189        _G.server_range = _G._create_server({
    190          capabilities = {
    191            semanticTokensProvider = {
    192              full = { delta = false },
    193              range = true,
    194              legend = vim.fn.json_decode(legend),
    195            },
    196          },
    197          handlers = {
    198            ['textDocument/semanticTokens/range'] = function(_, _, callback)
    199              callback(nil, vim.fn.json_decode(range_response))
    200            end,
    201            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    202              callback(nil, vim.fn.json_decode(response))
    203            end,
    204          },
    205        })
    206      end)
    207      exec_lua(function()
    208        local bufnr = vim.api.nvim_get_current_buf()
    209        vim.api.nvim_win_set_buf(0, bufnr)
    210        vim.lsp.start({ name = 'dummy', cmd = _G.server_range.cmd })
    211      end)
    212 
    213      screen:expect {
    214        grid = [[
    215        #include <iostream>                     |
    216                                                |
    217        int {8:main}()                              |
    218        {                                       |
    219            int {7:x};                              |
    220        #ifdef {5:__cplusplus}                      |
    221            {4:std}::{2:cout} << {2:x} << "\n";             |
    222        {6:#else}                                   |
    223        {6:    printf("%d\n", x);}                  |
    224        {6:#endif}                                  |
    225        }                                       |
    226        ^}                                       |
    227        {1:~                                       }|*3
    228                                                |
    229      ]],
    230      }
    231 
    232      local messages = exec_lua('return _G.server_range.messages')
    233      local called_range = false
    234      local called_full = false
    235      for _, m in ipairs(messages) do
    236        if m.method == 'textDocument/semanticTokens/range' then
    237          called_range = true
    238        end
    239        if m.method == 'textDocument/semanticTokens/full' then
    240          called_full = true
    241        end
    242      end
    243      eq(true, called_range)
    244      eq(true, called_full)
    245    end)
    246 
    247    it('does not call range when only full is supported', function()
    248      exec_lua(create_server_definition)
    249      insert(text)
    250      exec_lua(function()
    251        _G.server_full = _G._create_server({
    252          capabilities = {
    253            semanticTokensProvider = {
    254              full = { delta = false },
    255              range = false,
    256              legend = vim.fn.json_decode(legend),
    257            },
    258          },
    259          handlers = {
    260            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    261              callback(nil, vim.fn.json_decode(response))
    262            end,
    263            ['textDocument/semanticTokens/range'] = function(_, _, callback)
    264              callback(nil, vim.fn.json_decode(range_response))
    265            end,
    266          },
    267        })
    268        return vim.lsp.start({ name = 'dummy', cmd = _G.server_full.cmd })
    269      end)
    270 
    271      local messages = exec_lua('return _G.server_full.messages')
    272      local called_full = false
    273      local called_range = false
    274      for _, m in ipairs(messages) do
    275        if m.method == 'textDocument/semanticTokens/full' then
    276          called_full = true
    277        end
    278        if m.method == 'textDocument/semanticTokens/range' then
    279          called_range = true
    280        end
    281      end
    282      eq(true, called_full)
    283      eq(false, called_range)
    284    end)
    285 
    286    it('does not call range after full request received', function()
    287      exec_lua(create_server_definition)
    288      insert(text)
    289      exec_lua(function()
    290        _G.server_full = _G._create_server({
    291          capabilities = {
    292            semanticTokensProvider = {
    293              full = { delta = false },
    294              range = true,
    295              legend = vim.fn.json_decode(legend),
    296            },
    297          },
    298          handlers = {
    299            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    300              callback(nil, vim.fn.json_decode(response))
    301            end,
    302            ['textDocument/semanticTokens/range'] = function(_, _, callback)
    303              callback(nil, vim.fn.json_decode(range_response))
    304            end,
    305          },
    306        })
    307        return vim.lsp.start({ name = 'dummy', cmd = _G.server_full.cmd })
    308      end)
    309 
    310      -- ensure initial semantic token requests have been sent before feeding input
    311      n.poke_eventloop()
    312      -- modify the buffer
    313      feed('o<ESC>')
    314 
    315      local messages = exec_lua('return _G.server_full.messages')
    316      local called_full = 0
    317      local called_range = 0
    318      for _, m in ipairs(messages) do
    319        if m.method == 'textDocument/semanticTokens/full' then
    320          called_full = called_full + 1
    321        end
    322        if m.method == 'textDocument/semanticTokens/range' then
    323          called_range = called_range + 1
    324        end
    325      end
    326      eq(2, called_full)
    327      eq(1, called_range)
    328    end)
    329 
    330    it('range requests preserve highlights outside updated range', function()
    331      screen:try_resize(40, 6)
    332      insert(text)
    333      feed('gg')
    334 
    335      local client_id, bufnr = exec_lua(function()
    336        _G.response = [[{
    337          "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025 ]
    338        }]]
    339        _G.server2 = _G._create_server({
    340          capabilities = {
    341            semanticTokensProvider = {
    342              range = true,
    343              legend = vim.fn.json_decode(legend),
    344            },
    345          },
    346          handlers = {
    347            ['textDocument/semanticTokens/range'] = function(_, _, callback)
    348              callback(nil, vim.fn.json_decode(_G.response))
    349            end,
    350          },
    351        })
    352        local bufnr = vim.api.nvim_get_current_buf()
    353        local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }))
    354        vim.schedule(function()
    355          vim.lsp.semantic_tokens._start(bufnr, client_id, 0)
    356        end)
    357        return client_id, bufnr
    358      end)
    359 
    360      screen:expect {
    361        grid = [[
    362        ^#include <iostream>                     |
    363                                                |
    364        int {8:main}()                              |
    365        {                                       |
    366            int {7:x};                              |
    367                                                |
    368      ]],
    369      }
    370 
    371      eq(
    372        2,
    373        exec_lua(function()
    374          return #vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
    375        end)
    376      )
    377 
    378      exec_lua(function()
    379        _G.response = [[{
    380        "data": [ 7, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ]
    381      }]]
    382      end)
    383 
    384      feed('G')
    385 
    386      screen:expect {
    387        grid = [[
    388        {6:#else}                                   |
    389        {6:    printf("%d\n", x);}                  |
    390        {6:#endif}                                  |
    391        }                                       |
    392        ^}                                       |
    393                                                |
    394      ]],
    395      }
    396 
    397      eq(
    398        5,
    399        exec_lua(function()
    400          return #vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
    401        end)
    402      )
    403 
    404      exec_lua(function()
    405        _G.response = [[{
    406        "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192 ]
    407      }]]
    408      end)
    409      feed('ggLj0')
    410 
    411      screen:expect {
    412        grid = [[
    413                                                |
    414        int {8:main}()                              |
    415        {                                       |
    416            int {7:x};                              |
    417        ^#ifdef {5:__cplusplus}                      |
    418                                                |
    419      ]],
    420      }
    421 
    422      eq(
    423        6,
    424        exec_lua(function()
    425          return #vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
    426        end)
    427      )
    428 
    429      eq(
    430        {
    431          {
    432            line = 2,
    433            end_line = 2,
    434            start_col = 4,
    435            end_col = 8,
    436            marked = true,
    437            modifiers = {
    438              declaration = true,
    439              globalScope = true,
    440            },
    441            type = 'function',
    442          },
    443          {
    444            line = 4,
    445            end_line = 4,
    446            start_col = 8,
    447            end_col = 9,
    448            marked = true,
    449            modifiers = {
    450              declaration = true,
    451              functionScope = true,
    452            },
    453            type = 'variable',
    454          },
    455          {
    456            line = 5,
    457            end_line = 5,
    458            start_col = 7,
    459            end_col = 18,
    460            marked = true,
    461            modifiers = {
    462              globalScope = true,
    463            },
    464            type = 'macro',
    465          },
    466          {
    467            line = 7,
    468            end_line = 7,
    469            start_col = 0,
    470            end_col = 5,
    471            marked = true,
    472            modifiers = {},
    473            type = 'comment',
    474          },
    475          {
    476            line = 8,
    477            end_line = 8,
    478            start_col = 0,
    479            end_col = 22,
    480            marked = true,
    481            modifiers = {},
    482            type = 'comment',
    483          },
    484          {
    485            line = 9,
    486            end_line = 9,
    487            start_col = 0,
    488            end_col = 6,
    489            marked = true,
    490            modifiers = {},
    491            type = 'comment',
    492          },
    493        },
    494        exec_lua(function()
    495          return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
    496        end)
    497      )
    498    end)
    499 
    500    it('use LspTokenUpdate and highlight_token', function()
    501      insert(text)
    502      exec_lua(function()
    503        vim.api.nvim_create_autocmd('LspTokenUpdate', {
    504          callback = function(args)
    505            local token = args.data.token --- @type STTokenRange
    506            if token.type == 'function' and token.modifiers.declaration then
    507              vim.lsp.semantic_tokens.highlight_token(token, args.buf, args.data.client_id, 'Macro')
    508            end
    509          end,
    510        })
    511        local bufnr = vim.api.nvim_get_current_buf()
    512        vim.api.nvim_win_set_buf(0, bufnr)
    513        vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    514      end)
    515 
    516      screen:expect {
    517        grid = [[
    518        #include <iostream>                     |
    519                                                |
    520        int {9:main}()                              |
    521        {                                       |
    522            int {7:x};                              |
    523        #ifdef {5:__cplusplus}                      |
    524            {4:std}::{2:cout} << {2:x} << "\n";             |
    525        {6:#else}                                   |
    526        {6:    printf("%d\n", x);}                  |
    527        {6:#endif}                                  |
    528        }                                       |
    529        ^}                                       |
    530        {1:~                                       }|*3
    531                                                |
    532      ]],
    533      }
    534    end)
    535 
    536    it('buffer is unhighlighted when client is detached', function()
    537      insert(text)
    538 
    539      local bufnr = n.api.nvim_get_current_buf()
    540      local client_id = exec_lua(function()
    541        vim.api.nvim_win_set_buf(0, bufnr)
    542        local client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    543        vim.wait(1000, function()
    544          return #_G.server.messages > 1
    545        end)
    546        return client_id
    547      end)
    548 
    549      exec_lua(function()
    550        --- @diagnostic disable-next-line:duplicate-set-field
    551        vim.notify = function() end
    552        vim.lsp.buf_detach_client(bufnr, client_id)
    553      end)
    554 
    555      screen:expect {
    556        grid = [[
    557        #include <iostream>                     |
    558                                                |
    559        int main()                              |
    560        {                                       |
    561            int x;                              |
    562        #ifdef __cplusplus                      |
    563            std::cout << x << "\n";             |
    564        #else                                   |
    565            printf("%d\n", x);                  |
    566        #endif                                  |
    567        }                                       |
    568        ^}                                       |
    569        {1:~                                       }|*3
    570                                                |
    571      ]],
    572      }
    573    end)
    574 
    575    it(
    576      'buffer is highlighted and unhighlighted when semantic token highlighting is enabled and disabled',
    577      function()
    578        local bufnr = n.api.nvim_get_current_buf()
    579        exec_lua(function()
    580          vim.api.nvim_win_set_buf(0, bufnr)
    581          return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    582        end)
    583 
    584        insert(text)
    585 
    586        exec_lua(function()
    587          --- @diagnostic disable-next-line:duplicate-set-field
    588          vim.notify = function() end
    589          vim.lsp.semantic_tokens.enable(false)
    590        end)
    591 
    592        screen:expect {
    593          grid = [[
    594        #include <iostream>                     |
    595                                                |
    596        int main()                              |
    597        {                                       |
    598            int x;                              |
    599        #ifdef __cplusplus                      |
    600            std::cout << x << "\n";             |
    601        #else                                   |
    602            printf("%d\n", x);                  |
    603        #endif                                  |
    604        }                                       |
    605        ^}                                       |
    606        {1:~                                       }|*3
    607                                                |
    608      ]],
    609        }
    610 
    611        exec_lua(function()
    612          vim.lsp.semantic_tokens.enable(true)
    613        end)
    614 
    615        screen:expect {
    616          grid = [[
    617        #include <iostream>                     |
    618                                                |
    619        int {8:main}()                              |
    620        {                                       |
    621            int {7:x};                              |
    622        #ifdef {5:__cplusplus}                      |
    623            {4:std}::{2:cout} << {2:x} << "\n";             |
    624        {6:#else}                                   |
    625        {6:    printf("%d\n", x);}                  |
    626        {6:#endif}                                  |
    627        }                                       |
    628        ^}                                       |
    629        {1:~                                       }|*3
    630                                                |
    631      ]],
    632        }
    633      end
    634    )
    635 
    636    it('highlights start and stop when using "0" for current buffer', function()
    637      exec_lua(function()
    638        return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    639      end)
    640 
    641      insert(text)
    642 
    643      exec_lua(function()
    644        --- @diagnostic disable-next-line:duplicate-set-field
    645        vim.notify = function() end
    646        vim.lsp.semantic_tokens.enable(false, { bufnr = 0 })
    647      end)
    648 
    649      screen:expect {
    650        grid = [[
    651        #include <iostream>                     |
    652                                                |
    653        int main()                              |
    654        {                                       |
    655            int x;                              |
    656        #ifdef __cplusplus                      |
    657            std::cout << x << "\n";             |
    658        #else                                   |
    659            printf("%d\n", x);                  |
    660        #endif                                  |
    661        }                                       |
    662        ^}                                       |
    663        {1:~                                       }|*3
    664                                                |
    665      ]],
    666      }
    667 
    668      exec_lua(function()
    669        vim.lsp.semantic_tokens.enable(true, { bufnr = 0 })
    670      end)
    671 
    672      screen:expect {
    673        grid = [[
    674        #include <iostream>                     |
    675                                                |
    676        int {8:main}()                              |
    677        {                                       |
    678            int {7:x};                              |
    679        #ifdef {5:__cplusplus}                      |
    680            {4:std}::{2:cout} << {2:x} << "\n";             |
    681        {6:#else}                                   |
    682        {6:    printf("%d\n", x);}                  |
    683        {6:#endif}                                  |
    684        }                                       |
    685        ^}                                       |
    686        {1:~                                       }|*3
    687                                                |
    688      ]],
    689      }
    690    end)
    691 
    692    it('buffer is re-highlighted when force refreshed', function()
    693      insert(text)
    694      exec_lua(function()
    695        vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    696      end)
    697 
    698      screen:expect {
    699        grid = [[
    700        #include <iostream>                     |
    701                                                |
    702        int {8:main}()                              |
    703        {                                       |
    704            int {7:x};                              |
    705        #ifdef {5:__cplusplus}                      |
    706            {4:std}::{2:cout} << {2:x} << "\n";             |
    707        {6:#else}                                   |
    708        {6:    printf("%d\n", x);}                  |
    709        {6:#endif}                                  |
    710        }                                       |
    711        ^}                                       |
    712        {1:~                                       }|*3
    713                                                |
    714      ]],
    715      }
    716 
    717      exec_lua(function()
    718        vim.lsp.semantic_tokens.force_refresh()
    719      end)
    720 
    721      screen:expect {
    722        grid = [[
    723        #include <iostream>                     |
    724                                                |
    725        int {8:main}()                              |
    726        {                                       |
    727            int {7:x};                              |
    728        #ifdef {5:__cplusplus}                      |
    729            {4:std}::{2:cout} << {2:x} << "\n";             |
    730        {6:#else}                                   |
    731        {6:    printf("%d\n", x);}                  |
    732        {6:#endif}                                  |
    733        }                                       |
    734        ^}                                       |
    735        {1:~                                       }|*3
    736                                                |
    737      ]],
    738        unchanged = true,
    739      }
    740 
    741      local messages = exec_lua('return server.messages')
    742      local token_request_count = 0
    743      for _, message in
    744        ipairs(messages --[[@as {method:string,params:table}[] ]])
    745      do
    746        assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received')
    747        if message.method == 'textDocument/semanticTokens/full' then
    748          token_request_count = token_request_count + 1
    749        end
    750      end
    751      eq(2, token_request_count)
    752    end)
    753 
    754    it('destroys the highlighter if the buffer is deleted', function()
    755      exec_lua(function()
    756        vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    757      end)
    758 
    759      insert(text)
    760 
    761      eq(
    762        {},
    763        exec_lua(function()
    764          local bufnr = vim.api.nvim_get_current_buf()
    765          vim.api.nvim_buf_delete(bufnr, { force = true })
    766          return vim.lsp.semantic_tokens.__STHighlighter.active
    767        end)
    768      )
    769    end)
    770 
    771    it('updates highlights with delta request on buffer change', function()
    772      insert(text)
    773 
    774      exec_lua(function()
    775        vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
    776      end)
    777 
    778      screen:expect {
    779        grid = [[
    780        #include <iostream>                     |
    781                                                |
    782        int {8:main}()                              |
    783        {                                       |
    784            int {7:x};                              |
    785        #ifdef {5:__cplusplus}                      |
    786            {4:std}::{2:cout} << {2:x} << "\n";             |
    787        {6:#else}                                   |
    788        {6:    printf("%d\n", x);}                  |
    789        {6:#endif}                                  |
    790        }                                       |
    791        ^}                                       |
    792        {1:~                                       }|*3
    793                                                |
    794      ]],
    795      }
    796      feed(':%s/int x/int x()/<CR>')
    797      feed(':noh<CR>')
    798      screen:expect {
    799        grid = [[
    800        #include <iostream>                     |
    801                                                |
    802        int {8:main}()                              |
    803        {                                       |
    804            ^int {8:x}();                            |
    805        #ifdef {5:__cplusplus}                      |
    806            {4:std}::{2:cout} << {3:x} << "\n";             |
    807        {6:#else}                                   |
    808        {6:    printf("%d\n", x);}                  |
    809        {6:#endif}                                  |
    810        }                                       |*2
    811        {1:~                                       }|*3
    812        :noh                                    |
    813      ]],
    814      }
    815    end)
    816 
    817    it(
    818      'opt-out: does not activate semantic token highlighting if disabled in client attach',
    819      function()
    820        local client_id = exec_lua(function()
    821          return vim.lsp.start({
    822            name = 'dummy',
    823            cmd = _G.server.cmd,
    824            --- @param client vim.lsp.Client
    825            on_attach = vim.schedule_wrap(function(client, _bufnr)
    826              client.server_capabilities.semanticTokensProvider = nil
    827            end),
    828          })
    829        end)
    830        eq(true, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id))
    831 
    832        insert(text)
    833 
    834        screen:expect {
    835          grid = [[
    836          #include <iostream>                     |
    837                                                  |
    838          int main()                              |
    839          {                                       |
    840              int x;                              |
    841          #ifdef __cplusplus                      |
    842              std::cout << x << "\n";             |
    843          #else                                   |
    844              printf("%d\n", x);                  |
    845          #endif                                  |
    846          }                                       |
    847          ^}                                       |
    848          {1:~                                       }|*3
    849                                                  |
    850        ]],
    851        }
    852 
    853        screen:expect {
    854          grid = [[
    855          #include <iostream>                     |
    856                                                  |
    857          int main()                              |
    858          {                                       |
    859              int x;                              |
    860          #ifdef __cplusplus                      |
    861              std::cout << x << "\n";             |
    862          #else                                   |
    863              printf("%d\n", x);                  |
    864          #endif                                  |
    865          }                                       |
    866          ^}                                       |
    867          {1:~                                       }|*3
    868                                                  |
    869          ]],
    870          unchanged = true,
    871        }
    872      end
    873    )
    874 
    875    it('ignores null responses from the server', function()
    876      local client_id = exec_lua(function()
    877        _G.server2 = _G._create_server({
    878          capabilities = {
    879            semanticTokensProvider = {
    880              full = { delta = false },
    881            },
    882          },
    883          handlers = {
    884            --- @param callback function
    885            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    886              callback(nil, nil)
    887            end,
    888            --- @param callback function
    889            ['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
    890              callback(nil, nil)
    891            end,
    892          },
    893        })
    894        return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })
    895      end)
    896      eq(
    897        true,
    898        exec_lua(function()
    899          return vim.lsp.buf_is_attached(0, client_id)
    900        end)
    901      )
    902 
    903      insert(text)
    904 
    905      screen:expect {
    906        grid = [[
    907        #include <iostream>                     |
    908                                                |
    909        int main()                              |
    910        {                                       |
    911            int x;                              |
    912        #ifdef __cplusplus                      |
    913            std::cout << x << "\n";             |
    914        #else                                   |
    915            printf("%d\n", x);                  |
    916        #endif                                  |
    917        }                                       |
    918        ^}                                       |
    919        {1:~                                       }|*3
    920                                                |
    921      ]],
    922      }
    923    end)
    924 
    925    it('resets active request after receiving error responses from the server', function()
    926      local error = { code = -32801, message = 'Content modified' }
    927      exec_lua(function()
    928        _G.server2 = _G._create_server({
    929          capabilities = {
    930            semanticTokensProvider = {
    931              full = { delta = false },
    932            },
    933          },
    934          handlers = {
    935            -- There is same logic for handling nil responses and error responses,
    936            -- so keep responses not nil.
    937            --
    938            -- if an error response was not be handled, this test will hang on here.
    939            --- @param callback function
    940            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    941              callback(error, vim.fn.json_decode(response))
    942            end,
    943            --- @param callback function
    944            ['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
    945              callback(error, vim.fn.json_decode(response))
    946            end,
    947          },
    948        })
    949        return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })
    950      end)
    951      screen:expect([[
    952        ^                                        |
    953        {1:~                                       }|*14
    954                                                |
    955      ]])
    956    end)
    957 
    958    it('does not send delta requests if not supported by server', function()
    959      insert(text)
    960      exec_lua(function()
    961        _G.server2 = _G._create_server({
    962          capabilities = {
    963            semanticTokensProvider = {
    964              full = { delta = false },
    965              legend = vim.fn.json_decode(legend),
    966            },
    967          },
    968          handlers = {
    969            ['textDocument/semanticTokens/full'] = function(_, _, callback)
    970              callback(nil, vim.fn.json_decode(response))
    971            end,
    972            ['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
    973              callback(nil, vim.fn.json_decode(edit_response))
    974            end,
    975          },
    976        })
    977        return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })
    978      end)
    979 
    980      screen:expect {
    981        grid = [[
    982        #include <iostream>                     |
    983                                                |
    984        int {8:main}()                              |
    985        {                                       |
    986            int {7:x};                              |
    987        #ifdef {5:__cplusplus}                      |
    988            {4:std}::{2:cout} << {2:x} << "\n";             |
    989        {6:#else}                                   |
    990        {6:    printf("%d\n", x);}                  |
    991        {6:#endif}                                  |
    992        }                                       |
    993        ^}                                       |
    994        {1:~                                       }|*3
    995                                                |
    996      ]],
    997      }
    998      feed(':%s/int x/int x()/<CR>')
    999      feed(':noh<CR>')
   1000 
   1001      -- the highlights don't change because our fake server sent the exact
   1002      -- same result for the same method (the full request). "x" would have
   1003      -- changed to highlight index 3 had we sent a delta request
   1004      screen:expect {
   1005        grid = [[
   1006        #include <iostream>                     |
   1007                                                |
   1008        int {8:main}()                              |
   1009        {                                       |
   1010            ^int {7:x}();                            |
   1011        #ifdef {5:__cplusplus}                      |
   1012            {4:std}::{2:cout} << {2:x} << "\n";             |
   1013        {6:#else}                                   |
   1014        {6:    printf("%d\n", x);}                  |
   1015        {6:#endif}                                  |
   1016        }                                       |*2
   1017        {1:~                                       }|*3
   1018        :noh                                    |
   1019      ]],
   1020      }
   1021      local messages = exec_lua('return server2.messages')
   1022      local token_request_count = 0
   1023      for _, message in
   1024        ipairs(messages --[[@as {method:string,params:table}[] ]])
   1025      do
   1026        assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received')
   1027        if message.method == 'textDocument/semanticTokens/full' then
   1028          token_request_count = token_request_count + 1
   1029        end
   1030      end
   1031      eq(2, token_request_count)
   1032    end)
   1033  end)
   1034 
   1035  describe('token array decoding', function()
   1036    for _, test in ipairs({
   1037      {
   1038        it = 'clangd-15 on C',
   1039        text = [[char* foo = "\n";]],
   1040        response = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]],
   1041        legend = [[{
   1042          "tokenTypes": [
   1043            "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
   1044          ],
   1045          "tokenModifiers": [
   1046            "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
   1047          ]
   1048        }]],
   1049        expected = {
   1050          {
   1051            line = 0,
   1052            end_line = 0,
   1053            modifiers = { declaration = true, globalScope = true },
   1054            start_col = 6,
   1055            end_col = 9,
   1056            type = 'variable',
   1057            marked = true,
   1058          },
   1059        },
   1060        expected_screen = function()
   1061          screen:expect {
   1062            grid = [[
   1063            char* {7:foo} = "\n"^;                       |
   1064            {1:~                                       }|*14
   1065                                                    |
   1066          ]],
   1067          }
   1068        end,
   1069      },
   1070      {
   1071        it = 'clangd-15 on C++',
   1072        text = [[#include <iostream>
   1073 int main()
   1074 {
   1075  #ifdef __cplusplus
   1076  const int x = 1;
   1077  std::cout << x << std::endl;
   1078  #else
   1079    comment
   1080  #endif
   1081 }]],
   1082        response = [[{"data": [1, 4, 4, 3, 8193, 2, 9, 11, 19, 8192, 1, 12, 1, 1, 1033, 1, 2, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1032, 0, 5, 3, 15, 8448, 0, 5, 4, 3, 8448, 1, 0, 7, 20, 0, 1, 0, 11, 20, 0, 1, 0, 8, 20, 0], "resultId": "1"}]],
   1083        legend = [[{
   1084          "tokenTypes": [
   1085            "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
   1086          ],
   1087          "tokenModifiers": [
   1088            "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
   1089          ]
   1090        }]],
   1091        expected = {
   1092          { -- main
   1093            line = 1,
   1094            end_line = 1,
   1095            modifiers = { declaration = true, globalScope = true },
   1096            start_col = 4,
   1097            end_col = 8,
   1098            type = 'function',
   1099            marked = true,
   1100          },
   1101          { --  __cplusplus
   1102            line = 3,
   1103            end_line = 3,
   1104            modifiers = { globalScope = true },
   1105            start_col = 9,
   1106            end_col = 20,
   1107            type = 'macro',
   1108            marked = true,
   1109          },
   1110          { -- x
   1111            line = 4,
   1112            end_line = 4,
   1113            modifiers = { declaration = true, readonly = true, functionScope = true },
   1114            start_col = 12,
   1115            end_col = 13,
   1116            type = 'variable',
   1117            marked = true,
   1118          },
   1119          { -- std
   1120            line = 5,
   1121            end_line = 5,
   1122            modifiers = { defaultLibrary = true, globalScope = true },
   1123            start_col = 2,
   1124            end_col = 5,
   1125            type = 'namespace',
   1126            marked = true,
   1127          },
   1128          { -- cout
   1129            line = 5,
   1130            end_line = 5,
   1131            modifiers = { defaultLibrary = true, globalScope = true },
   1132            start_col = 7,
   1133            end_col = 11,
   1134            type = 'variable',
   1135            marked = true,
   1136          },
   1137          { -- x
   1138            line = 5,
   1139            end_line = 5,
   1140            modifiers = { readonly = true, functionScope = true },
   1141            start_col = 15,
   1142            end_col = 16,
   1143            type = 'variable',
   1144            marked = true,
   1145          },
   1146          { -- std
   1147            line = 5,
   1148            end_line = 5,
   1149            modifiers = { defaultLibrary = true, globalScope = true },
   1150            start_col = 20,
   1151            end_col = 23,
   1152            type = 'namespace',
   1153            marked = true,
   1154          },
   1155          { -- endl
   1156            line = 5,
   1157            end_line = 5,
   1158            modifiers = { defaultLibrary = true, globalScope = true },
   1159            start_col = 25,
   1160            end_col = 29,
   1161            type = 'function',
   1162            marked = true,
   1163          },
   1164          { -- #else comment #endif
   1165            line = 6,
   1166            end_line = 6,
   1167            modifiers = {},
   1168            start_col = 0,
   1169            end_col = 7,
   1170            type = 'comment',
   1171            marked = true,
   1172          },
   1173          {
   1174            line = 7,
   1175            end_line = 7,
   1176            modifiers = {},
   1177            start_col = 0,
   1178            end_col = 11,
   1179            type = 'comment',
   1180            marked = true,
   1181          },
   1182          {
   1183            line = 8,
   1184            end_line = 8,
   1185            modifiers = {},
   1186            start_col = 0,
   1187            end_col = 8,
   1188            type = 'comment',
   1189            marked = true,
   1190          },
   1191        },
   1192        expected_screen = function()
   1193          screen:expect {
   1194            grid = [[
   1195            #include <iostream>                     |
   1196            int {8:main}()                              |
   1197            {                                       |
   1198              #ifdef {5:__cplusplus}                    |
   1199              const int {7:x} = 1;                      |
   1200              {4:std}::{2:cout} << {2:x} << {4:std}::{3:endl};          |
   1201            {6:  #else}                                 |
   1202            {6:    comment}                             |
   1203            {6:  #endif}                                |
   1204            ^}                                       |
   1205            {1:~                                       }|*5
   1206                                                    |
   1207          ]],
   1208          }
   1209        end,
   1210      },
   1211      {
   1212        it = 'sumneko_lua',
   1213        text = [[-- comment
   1214 local a = 1
   1215 b = "as"]],
   1216        response = [[{"data": [0, 0, 10, 17, 0, 1, 6, 1, 8, 1, 1, 0, 1, 8, 8]}]],
   1217        legend = [[{
   1218          "tokenTypes": [
   1219            "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator"
   1220          ],
   1221          "tokenModifiers": [
   1222            "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary"
   1223          ]
   1224        }]],
   1225        expected = {
   1226          {
   1227            line = 0,
   1228            end_line = 0,
   1229            modifiers = {},
   1230            start_col = 0,
   1231            end_col = 10,
   1232            type = 'comment', -- comment
   1233            marked = true,
   1234          },
   1235          {
   1236            line = 1,
   1237            end_line = 1,
   1238            modifiers = { declaration = true }, -- a
   1239            start_col = 6,
   1240            end_col = 7,
   1241            type = 'variable',
   1242            marked = true,
   1243          },
   1244          {
   1245            line = 2,
   1246            end_line = 2,
   1247            modifiers = { static = true }, -- b (global)
   1248            start_col = 0,
   1249            end_col = 1,
   1250            type = 'variable',
   1251            marked = true,
   1252          },
   1253        },
   1254        expected_screen = function()
   1255          screen:expect {
   1256            grid = [[
   1257            {6:-- comment}                              |
   1258            local {7:a} = 1                             |
   1259            {2:b} = "as^"                                |
   1260            {1:~                                       }|*12
   1261                                                    |
   1262          ]],
   1263          }
   1264        end,
   1265      },
   1266      {
   1267        it = 'rust-analyzer',
   1268        text = [[pub fn main() {
   1269    println!("Hello world!");
   1270    break rust;
   1271    /// what?
   1272 }
   1273 ]],
   1274        response = [[{"data": [0, 0, 3, 1, 0, 0, 4, 2, 1, 0, 0, 3, 4, 14, 524290, 0, 4, 1, 45, 0, 0, 1, 1, 45, 0, 0, 2, 1, 26, 0, 1, 4, 8, 17, 0, 0, 8, 1, 45, 0, 0, 1, 14, 2, 0, 0, 14, 1, 45, 0, 0, 1, 1, 48, 0, 1, 4, 5, 1, 8192, 0, 6, 4, 52, 0, 0, 4, 1, 48, 0, 1, 4, 9, 0, 1, 1, 0, 1, 26, 0 ], "resultId": "1"}]],
   1275 
   1276        legend = [[{
   1277        "tokenTypes": [
   1278          "comment", "keyword", "string", "number", "regexp", "operator", "namespace", "type", "struct", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "macro", "variable",
   1279          "parameter", "angle", "arithmetic", "attribute", "attributeBracket", "bitwise", "boolean", "brace", "bracket", "builtinAttribute", "builtinType", "character", "colon", "comma", "comparison", "constParameter", "derive",
   1280          "dot", "escapeSequence", "formatSpecifier", "generic", "label", "lifetime", "logical", "macroBang", "operator", "parenthesis", "punctuation", "selfKeyword", "semicolon", "typeAlias", "toolModule", "union", "unresolvedReference"
   1281        ],
   1282        "tokenModifiers": [
   1283          "documentation", "declaration", "definition", "static", "abstract", "deprecated", "readonly", "defaultLibrary", "async", "attribute", "callable", "constant", "consuming", "controlFlow", "crateRoot", "injected", "intraDocLink",
   1284          "library", "mutable", "public", "reference", "trait", "unsafe"
   1285        ]
   1286        }]],
   1287        expected = {
   1288          {
   1289            line = 0,
   1290            end_line = 0,
   1291            modifiers = {},
   1292            start_col = 0,
   1293            end_col = 3, -- pub
   1294            type = 'keyword',
   1295            marked = true,
   1296          },
   1297          {
   1298            line = 0,
   1299            end_line = 0,
   1300            modifiers = {},
   1301            start_col = 4,
   1302            end_col = 6, -- fn
   1303            type = 'keyword',
   1304            marked = true,
   1305          },
   1306          {
   1307            line = 0,
   1308            end_line = 0,
   1309            modifiers = { declaration = true, public = true },
   1310            start_col = 7,
   1311            end_col = 11, -- main
   1312            type = 'function',
   1313            marked = true,
   1314          },
   1315          {
   1316            line = 0,
   1317            end_line = 0,
   1318            modifiers = {},
   1319            start_col = 11,
   1320            end_col = 12,
   1321            type = 'parenthesis',
   1322            marked = true,
   1323          },
   1324          {
   1325            line = 0,
   1326            end_line = 0,
   1327            modifiers = {},
   1328            start_col = 12,
   1329            end_col = 13,
   1330            type = 'parenthesis',
   1331            marked = true,
   1332          },
   1333          {
   1334            line = 0,
   1335            end_line = 0,
   1336            modifiers = {},
   1337            start_col = 14,
   1338            end_col = 15,
   1339            type = 'brace',
   1340            marked = true,
   1341          },
   1342          {
   1343            line = 1,
   1344            end_line = 1,
   1345            modifiers = {},
   1346            start_col = 4,
   1347            end_col = 12,
   1348            type = 'macro', -- println!
   1349            marked = true,
   1350          },
   1351          {
   1352            line = 1,
   1353            end_line = 1,
   1354            modifiers = {},
   1355            start_col = 12,
   1356            end_col = 13,
   1357            type = 'parenthesis',
   1358            marked = true,
   1359          },
   1360          {
   1361            line = 1,
   1362            end_line = 1,
   1363            modifiers = {},
   1364            start_col = 13,
   1365            end_col = 27,
   1366            type = 'string', -- "Hello world!"
   1367            marked = true,
   1368          },
   1369          {
   1370            line = 1,
   1371            end_line = 1,
   1372            modifiers = {},
   1373            start_col = 27,
   1374            end_col = 28,
   1375            type = 'parenthesis',
   1376            marked = true,
   1377          },
   1378          {
   1379            line = 1,
   1380            end_line = 1,
   1381            modifiers = {},
   1382            start_col = 28,
   1383            end_col = 29,
   1384            type = 'semicolon',
   1385            marked = true,
   1386          },
   1387          {
   1388            line = 2,
   1389            end_line = 2,
   1390            modifiers = { controlFlow = true },
   1391            start_col = 4,
   1392            end_col = 9, -- break
   1393            type = 'keyword',
   1394            marked = true,
   1395          },
   1396          {
   1397            line = 2,
   1398            end_line = 2,
   1399            modifiers = {},
   1400            start_col = 10,
   1401            end_col = 14, -- rust
   1402            type = 'unresolvedReference',
   1403            marked = true,
   1404          },
   1405          {
   1406            line = 2,
   1407            end_line = 2,
   1408            modifiers = {},
   1409            start_col = 14,
   1410            end_col = 15,
   1411            type = 'semicolon',
   1412            marked = true,
   1413          },
   1414          {
   1415            line = 3,
   1416            end_line = 3,
   1417            modifiers = { documentation = true },
   1418            start_col = 4,
   1419            end_col = 13,
   1420            type = 'comment', -- /// what?
   1421            marked = true,
   1422          },
   1423          {
   1424            line = 4,
   1425            end_line = 4,
   1426            modifiers = {},
   1427            start_col = 0,
   1428            end_col = 1,
   1429            type = 'brace',
   1430            marked = true,
   1431          },
   1432        },
   1433        expected_screen = function()
   1434          screen:expect {
   1435            grid = [[
   1436            {10:pub} {10:fn} {8:main}() {                         |
   1437                {5:println!}({11:"Hello world!"});           |
   1438                {10:break} rust;                         |
   1439                {6:/// what?}                           |
   1440            }                                       |
   1441            ^                                        |
   1442            {1:~                                       }|*9
   1443                                                    |
   1444          ]],
   1445          }
   1446        end,
   1447      },
   1448    }) do
   1449      it(test.it, function()
   1450        exec_lua(create_server_definition)
   1451        local client_id = exec_lua(function(legend, resp)
   1452          _G.server = _G._create_server({
   1453            capabilities = {
   1454              semanticTokensProvider = {
   1455                full = { delta = false },
   1456                legend = vim.fn.json_decode(legend),
   1457              },
   1458            },
   1459            handlers = {
   1460              ['textDocument/semanticTokens/full'] = function(_, _, callback)
   1461                callback(nil, vim.fn.json_decode(resp))
   1462              end,
   1463            },
   1464          })
   1465          return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
   1466        end, test.legend, test.response)
   1467 
   1468        insert(test.text)
   1469 
   1470        test.expected_screen()
   1471 
   1472        eq(
   1473          test.expected,
   1474          exec_lua(function()
   1475            local bufnr = vim.api.nvim_get_current_buf()
   1476            return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
   1477          end)
   1478        )
   1479      end)
   1480    end
   1481  end)
   1482 
   1483  describe('token decoding with deltas', function()
   1484    for _, test in ipairs({
   1485      {
   1486        it = 'semantic_tokens_delta: clangd-15 on C',
   1487        legend = [[{
   1488          "tokenTypes": [
   1489            "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
   1490          ],
   1491          "tokenModifiers": [
   1492            "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
   1493          ]
   1494        }]],
   1495        text1 = [[char* foo = "\n";]],
   1496        edit = [[ggO<Esc>]],
   1497        response1 = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]],
   1498        response2 = [[{"edits": [{ "start": 0, "deleteCount": 1, "data": [1] }], "resultId": "2"}]],
   1499        expected1 = {
   1500          {
   1501            line = 0,
   1502            modifiers = {
   1503              declaration = true,
   1504              globalScope = true,
   1505            },
   1506            start_col = 6,
   1507            end_line = 0,
   1508            end_col = 9,
   1509            type = 'variable',
   1510            marked = true,
   1511          },
   1512        },
   1513        expected2 = {
   1514          {
   1515            line = 1,
   1516            modifiers = {
   1517              declaration = true,
   1518              globalScope = true,
   1519            },
   1520            start_col = 6,
   1521            end_line = 1,
   1522            end_col = 9,
   1523            type = 'variable',
   1524            marked = true,
   1525          },
   1526        },
   1527        expected_screen1 = function()
   1528          screen:expect {
   1529            grid = [[
   1530          char* {7:foo} = "\n"^;                       |
   1531          {1:~                                       }|*14
   1532                                                  |
   1533        ]],
   1534          }
   1535        end,
   1536        expected_screen2 = function()
   1537          screen:expect {
   1538            grid = [[
   1539            ^                                        |
   1540            char* {7:foo} = "\n";                       |
   1541            {1:~                                       }|*13
   1542                                                    |
   1543          ]],
   1544          }
   1545        end,
   1546      },
   1547      {
   1548        it = 'response with multiple delta edits',
   1549        legend = [[{
   1550        "tokenTypes": [
   1551          "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment"
   1552        ],
   1553        "tokenModifiers": [
   1554          "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope"
   1555        ]
   1556        }]],
   1557        text1 = dedent([[
   1558        #include <iostream>
   1559 
   1560        int main()
   1561        {
   1562            int x;
   1563        #ifdef __cplusplus
   1564            std::cout << x << "\n";
   1565        #else
   1566            printf("%d\n", x);
   1567        #endif
   1568        }]]),
   1569        text2 = [[#include <iostream>
   1570 
   1571 int main()
   1572 {
   1573    int x();
   1574    double y;
   1575 #ifdef __cplusplus
   1576    std::cout << x << "\n";
   1577 #else
   1578    printf("%d\n", x);
   1579 #endif
   1580 }]],
   1581        response1 = [[{
   1582        "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ],
   1583        "resultId": 1
   1584        }]],
   1585        response2 = [[{
   1586        "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 11, 1, 1, 1025 ], "deleteCount": 5, "start": 5}, {"data": [ 0, 8, 1, 3, 8192 ], "deleteCount": 5, "start": 25 } ],
   1587        "resultId":"2"
   1588        }]],
   1589        expected1 = {
   1590          {
   1591            line = 2,
   1592            end_line = 2,
   1593            start_col = 4,
   1594            end_col = 8,
   1595            modifiers = { declaration = true, globalScope = true },
   1596            type = 'function',
   1597            marked = true,
   1598          },
   1599          {
   1600            line = 4,
   1601            end_line = 4,
   1602            start_col = 8,
   1603            end_col = 9,
   1604            modifiers = { declaration = true, functionScope = true },
   1605            type = 'variable',
   1606            marked = true,
   1607          },
   1608          {
   1609            line = 5,
   1610            end_line = 5,
   1611            start_col = 7,
   1612            end_col = 18,
   1613            modifiers = { globalScope = true },
   1614            type = 'macro',
   1615            marked = true,
   1616          },
   1617          {
   1618            line = 6,
   1619            end_line = 6,
   1620            start_col = 4,
   1621            end_col = 7,
   1622            modifiers = { defaultLibrary = true, globalScope = true },
   1623            type = 'namespace',
   1624            marked = true,
   1625          },
   1626          {
   1627            line = 6,
   1628            end_line = 6,
   1629            start_col = 9,
   1630            end_col = 13,
   1631            modifiers = { defaultLibrary = true, globalScope = true },
   1632            type = 'variable',
   1633            marked = true,
   1634          },
   1635          {
   1636            line = 6,
   1637            end_line = 6,
   1638            start_col = 17,
   1639            end_col = 18,
   1640            marked = true,
   1641            modifiers = { functionScope = true },
   1642            type = 'variable',
   1643          },
   1644          {
   1645            line = 7,
   1646            end_line = 7,
   1647            start_col = 0,
   1648            end_col = 5,
   1649            marked = true,
   1650            modifiers = {},
   1651            type = 'comment',
   1652          },
   1653          {
   1654            line = 8,
   1655            end_line = 8,
   1656            end_col = 22,
   1657            modifiers = {},
   1658            start_col = 0,
   1659            type = 'comment',
   1660            marked = true,
   1661          },
   1662          {
   1663            line = 9,
   1664            end_line = 9,
   1665            start_col = 0,
   1666            end_col = 6,
   1667            modifiers = {},
   1668            type = 'comment',
   1669            marked = true,
   1670          },
   1671        },
   1672        expected2 = {
   1673          {
   1674            line = 2,
   1675            end_line = 2,
   1676            start_col = 4,
   1677            end_col = 8,
   1678            modifiers = { declaration = true, globalScope = true },
   1679            type = 'function',
   1680            marked = true,
   1681          },
   1682          {
   1683            line = 4,
   1684            end_line = 4,
   1685            start_col = 8,
   1686            end_col = 9,
   1687            modifiers = { declaration = true, globalScope = true },
   1688            type = 'function',
   1689            marked = true,
   1690          },
   1691          {
   1692            line = 5,
   1693            end_line = 5,
   1694            end_col = 12,
   1695            start_col = 11,
   1696            modifiers = { declaration = true, functionScope = true },
   1697            type = 'variable',
   1698            marked = true,
   1699          },
   1700          {
   1701            line = 6,
   1702            end_line = 6,
   1703            start_col = 7,
   1704            end_col = 18,
   1705            modifiers = { globalScope = true },
   1706            type = 'macro',
   1707            marked = true,
   1708          },
   1709          {
   1710            line = 7,
   1711            end_line = 7,
   1712            start_col = 4,
   1713            end_col = 7,
   1714            modifiers = { defaultLibrary = true, globalScope = true },
   1715            type = 'namespace',
   1716            marked = true,
   1717          },
   1718          {
   1719            line = 7,
   1720            end_line = 7,
   1721            start_col = 9,
   1722            end_col = 13,
   1723            modifiers = { defaultLibrary = true, globalScope = true },
   1724            type = 'variable',
   1725            marked = true,
   1726          },
   1727          {
   1728            line = 7,
   1729            end_line = 7,
   1730            start_col = 17,
   1731            end_col = 18,
   1732            marked = true,
   1733            modifiers = { globalScope = true },
   1734            type = 'function',
   1735          },
   1736          {
   1737            line = 8,
   1738            end_line = 8,
   1739            start_col = 0,
   1740            end_col = 5,
   1741            marked = true,
   1742            modifiers = {},
   1743            type = 'comment',
   1744          },
   1745          {
   1746            line = 9,
   1747            end_line = 9,
   1748            end_col = 22,
   1749            modifiers = {},
   1750            start_col = 0,
   1751            type = 'comment',
   1752            marked = true,
   1753          },
   1754          {
   1755            line = 10,
   1756            end_line = 10,
   1757            start_col = 0,
   1758            end_col = 6,
   1759            modifiers = {},
   1760            type = 'comment',
   1761            marked = true,
   1762          },
   1763        },
   1764        expected_screen1 = function()
   1765          screen:expect {
   1766            grid = [[
   1767            #include <iostream>                     |
   1768                                                    |
   1769            int {8:main}()                              |
   1770            {                                       |
   1771                int {7:x};                              |
   1772            #ifdef {5:__cplusplus}                      |
   1773                {4:std}::{2:cout} << {2:x} << "\n";             |
   1774            {6:#else}                                   |
   1775            {6:    printf("%d\n", x);}                  |
   1776            {6:#endif}                                  |
   1777            ^}                                       |
   1778            {1:~                                       }|*4
   1779                                                    |
   1780          ]],
   1781          }
   1782        end,
   1783        expected_screen2 = function()
   1784          screen:expect {
   1785            grid = [[
   1786            #include <iostream>                     |
   1787                                                    |
   1788            int {8:main}()                              |
   1789            {                                       |
   1790                int {8:x}();                            |
   1791                double {7:y};                           |
   1792            #ifdef {5:__cplusplus}                      |
   1793                {4:std}::{2:cout} << {3:x} << "\n";             |
   1794            {6:#else}                                   |
   1795            {6:    printf("%d\n", x);}                  |
   1796            {6:^#endif}                                  |
   1797            }                                       |
   1798            {1:~                                       }|*3
   1799                                                    |
   1800          ]],
   1801          }
   1802        end,
   1803      },
   1804      {
   1805        it = 'optional token_edit.data on deletion',
   1806        legend = [[{
   1807          "tokenTypes": [
   1808            "comment", "keyword", "operator", "string", "number", "regexp", "type", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "variable", "parameter", "module", "intrinsic", "selfParameter", "clsParameter", "magicFunction", "builtinConstant", "parenthesis", "curlybrace", "bracket", "colon", "semicolon", "arrow"
   1809          ],
   1810          "tokenModifiers": [
   1811            "declaration", "static", "abstract", "async", "documentation", "typeHint", "typeHintComment", "readonly", "decorator", "builtin"
   1812          ]
   1813        }]],
   1814        text1 = [[string = "test"]],
   1815        text2 = [[]],
   1816        response1 = [[{"data": [0, 0, 6, 15, 1], "resultId": "1"}]],
   1817        response2 = [[{"edits": [{ "start": 0, "deleteCount": 5 }], "resultId": "2"}]],
   1818        expected1 = {
   1819          {
   1820            line = 0,
   1821            end_line = 0,
   1822            modifiers = {
   1823              declaration = true,
   1824            },
   1825            start_col = 0,
   1826            end_col = 6,
   1827            type = 'variable',
   1828            marked = true,
   1829          },
   1830        },
   1831        expected2 = {},
   1832        expected_screen1 = function()
   1833          screen:expect {
   1834            grid = [[
   1835            {7:string} = "test^"                         |
   1836            {1:~                                       }|*14
   1837                                                    |
   1838          ]],
   1839          }
   1840        end,
   1841        expected_screen2 = function()
   1842          screen:expect {
   1843            grid = [[
   1844            ^                                        |
   1845            {1:~                                       }|*14
   1846                                                    |
   1847          ]],
   1848          }
   1849        end,
   1850      },
   1851    }) do
   1852      it(test.it, function()
   1853        local bufnr = n.api.nvim_get_current_buf()
   1854        insert(test.text1)
   1855        exec_lua(create_server_definition)
   1856        local client_id = exec_lua(function(legend, resp1, resp2)
   1857          _G.server = _G._create_server({
   1858            capabilities = {
   1859              semanticTokensProvider = {
   1860                full = { delta = true },
   1861                legend = vim.fn.json_decode(legend),
   1862              },
   1863            },
   1864            handlers = {
   1865              ['textDocument/semanticTokens/full'] = function(_, _, callback)
   1866                callback(nil, vim.fn.json_decode(resp1))
   1867              end,
   1868              ['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
   1869                callback(nil, vim.fn.json_decode(resp2))
   1870              end,
   1871            },
   1872          })
   1873          local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }))
   1874 
   1875          -- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests
   1876          vim.schedule(function()
   1877            vim.lsp.semantic_tokens._start(bufnr, client_id, 10)
   1878          end)
   1879          return client_id
   1880        end, test.legend, test.response1, test.response2)
   1881 
   1882        test.expected_screen1()
   1883 
   1884        eq(
   1885          test.expected1,
   1886          exec_lua(function()
   1887            return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
   1888          end)
   1889        )
   1890 
   1891        if test.edit then
   1892          feed(test.edit)
   1893        else
   1894          exec_lua(function(text)
   1895            vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, '\n'))
   1896            vim.wait(15) -- wait for debounce
   1897          end, test.text2)
   1898        end
   1899 
   1900        test.expected_screen2()
   1901 
   1902        eq(
   1903          test.expected2,
   1904          exec_lua(function()
   1905            return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
   1906          end)
   1907        )
   1908      end)
   1909    end
   1910  end)
   1911 end)