neovim

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

fake-lsp-server.lua (27000B)


      1 local protocol = require 'vim.lsp.protocol'
      2 
      3 local pid = vim.uv.os_getpid()
      4 local stdin = ''
      5 vim.fn.stdioopen({
      6  on_stdin = function(_, data, _)
      7    stdin = stdin .. table.concat(data, '\n')
      8  end,
      9 })
     10 
     11 -- Logs to $NVIM_LOG_FILE.
     12 --
     13 -- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062
     14 local function log(loglevel, area, msg)
     15  vim.fn.writefile(
     16    { string.format('%d %s %s: %s', pid, loglevel, area, msg) },
     17    vim.env.NVIM_LOG_FILE,
     18    'a'
     19  )
     20 end
     21 
     22 local function message_parts(sep, ...)
     23  local parts = {}
     24  for i = 1, select('#', ...) do
     25    local arg = select(i, ...)
     26    if arg ~= nil then
     27      table.insert(parts, arg)
     28    end
     29  end
     30  return table.concat(parts, sep)
     31 end
     32 
     33 -- Assert utility methods
     34 
     35 local function assert_eq(a, b, ...)
     36  if not vim.deep_equal(a, b) then
     37    error(
     38      message_parts(
     39        ': ',
     40        ...,
     41        'assert_eq failed',
     42        string.format(
     43          'left == %q, right == %q',
     44          table.concat(vim.split(vim.inspect(a), '\n'), ''),
     45          table.concat(vim.split(vim.inspect(b), '\n'), '')
     46        )
     47      )
     48    )
     49  end
     50 end
     51 
     52 local function format_message_with_content_length(encoded_message)
     53  return table.concat {
     54    'Content-Length: ',
     55    tostring(#encoded_message),
     56    '\r\n\r\n',
     57    encoded_message,
     58  }
     59 end
     60 
     61 local function read_line()
     62  vim.wait(math.huge, function()
     63    return stdin:find('\n') ~= nil
     64  end, 1)
     65  local eol = assert(stdin:find('\n'))
     66  local line = stdin:sub(1, eol - 1)
     67  stdin = stdin:sub(eol + 1)
     68  return line
     69 end
     70 
     71 --- @param len integer
     72 local function read_len(len)
     73  vim.wait(math.huge, function()
     74    return stdin:len() >= len
     75  end, 1)
     76  local content = stdin:sub(1, len)
     77  stdin = stdin:sub(len + 1)
     78  return content
     79 end
     80 
     81 local function read_message()
     82  local line = read_line()
     83  local length = line:lower():match('content%-length:%s*(%d+)')
     84  return vim.json.decode(read_len(2 + length):sub(2))
     85 end
     86 
     87 local function send(payload)
     88  io.stdout:write(format_message_with_content_length(vim.json.encode(payload)))
     89 end
     90 
     91 local function respond(id, err, result)
     92  assert(type(id) == 'number', 'id must be a number')
     93  send { jsonrpc = '2.0', id = id, error = err, result = result }
     94 end
     95 
     96 local function notify(method, params)
     97  assert(type(method) == 'string', 'method must be a string')
     98  send { method = method, params = params or {} }
     99 end
    100 
    101 local function expect_notification(method, params, ...)
    102  local message = read_message()
    103  assert_eq(method, message.method, ..., 'expect_notification', 'method')
    104  if params then
    105    assert_eq(params, message.params, ..., 'expect_notification', method, 'params')
    106    assert_eq(
    107      { jsonrpc = '2.0', method = method, params = params },
    108      message,
    109      ...,
    110      'expect_notification',
    111      'message'
    112    )
    113  end
    114 end
    115 
    116 local function expect_request(method, handler, ...)
    117  local req = read_message()
    118  assert_eq(method, req.method, ..., 'expect_request', 'method')
    119  local err, result = handler(req.params)
    120  respond(req.id, err, result)
    121 end
    122 
    123 io.stderr:setvbuf('no')
    124 
    125 local function skeleton(config)
    126  local on_init = assert(config.on_init)
    127  local body = assert(config.body)
    128  expect_request('initialize', function(params)
    129    return nil, on_init(params)
    130  end)
    131  expect_notification('initialized', {})
    132  body()
    133  expect_request('shutdown', function()
    134    return nil, {}
    135  end)
    136  expect_notification('exit', nil)
    137 end
    138 
    139 -- The actual tests.
    140 
    141 local tests = {}
    142 
    143 function tests.basic_init()
    144  skeleton {
    145    on_init = function(params)
    146      assert_eq(params.workDoneToken, '1')
    147      return {
    148        capabilities = {
    149          textDocumentSync = protocol.TextDocumentSyncKind.None,
    150        },
    151      }
    152    end,
    153    body = function()
    154      notify('test')
    155    end,
    156  }
    157 end
    158 
    159 function tests.basic_init_did_change_configuration()
    160  skeleton({
    161    on_init = function(_)
    162      return {
    163        capabilities = {},
    164      }
    165    end,
    166    body = function()
    167      expect_notification('workspace/didChangeConfiguration', { settings = { dummy = 1 } })
    168    end,
    169  })
    170 end
    171 
    172 function tests.check_workspace_configuration()
    173  skeleton {
    174    on_init = function(_params)
    175      return { capabilities = {} }
    176    end,
    177    body = function()
    178      notify('start')
    179      notify('workspace/configuration', {
    180        items = {
    181          { section = 'testSetting1' },
    182          { section = 'testSetting2' },
    183          { section = 'test.Setting3' },
    184          { section = 'test.Setting4' },
    185          {},
    186          { section = '' },
    187        },
    188      })
    189      local all = {
    190        testSetting1 = true,
    191        testSetting2 = false,
    192        test = { Setting3 = 'nested' },
    193      }
    194      expect_notification('workspace/configuration', { true, false, 'nested', vim.NIL, all, all })
    195      notify('shutdown')
    196    end,
    197  }
    198 end
    199 
    200 function tests.prepare_rename_nil()
    201  skeleton {
    202    on_init = function()
    203      return {
    204        capabilities = {
    205          renameProvider = {
    206            prepareProvider = true,
    207          },
    208        },
    209      }
    210    end,
    211    body = function()
    212      notify('start')
    213      expect_request('textDocument/prepareRename', function()
    214        return {}, nil
    215      end)
    216      notify('shutdown')
    217    end,
    218  }
    219 end
    220 
    221 function tests.prepare_rename_placeholder()
    222  skeleton {
    223    on_init = function()
    224      return {
    225        capabilities = {
    226          renameProvider = {
    227            prepareProvider = true,
    228          },
    229        },
    230      }
    231    end,
    232    body = function()
    233      notify('start')
    234      expect_request('textDocument/prepareRename', function()
    235        return nil, { placeholder = 'placeholder' }
    236      end)
    237      expect_request('textDocument/rename', function(params)
    238        assert_eq(params.newName, 'renameto')
    239        return {}, nil
    240      end)
    241      notify('shutdown')
    242    end,
    243  }
    244 end
    245 
    246 function tests.prepare_rename_range()
    247  skeleton {
    248    on_init = function()
    249      return {
    250        capabilities = {
    251          renameProvider = {
    252            prepareProvider = true,
    253          },
    254        },
    255      }
    256    end,
    257    body = function()
    258      notify('start')
    259      expect_request('textDocument/prepareRename', function()
    260        return nil,
    261          {
    262            start = { line = 1, character = 8 },
    263            ['end'] = { line = 1, character = 12 },
    264          }
    265      end)
    266      expect_request('textDocument/rename', function(params)
    267        assert_eq(params.newName, 'renameto')
    268        return {}, nil
    269      end)
    270      notify('shutdown')
    271    end,
    272  }
    273 end
    274 
    275 function tests.prepare_rename_error()
    276  skeleton {
    277    on_init = function()
    278      return {
    279        capabilities = {
    280          renameProvider = {
    281            prepareProvider = true,
    282          },
    283        },
    284      }
    285    end,
    286    body = function()
    287      notify('start')
    288      expect_request('textDocument/prepareRename', function()
    289        return {}, nil
    290      end)
    291      notify('shutdown')
    292    end,
    293  }
    294 end
    295 
    296 function tests.basic_check_capabilities()
    297  skeleton {
    298    on_init = function(params)
    299      local expected_capabilities = protocol.make_client_capabilities()
    300      assert_eq(params.capabilities, expected_capabilities)
    301      return {
    302        capabilities = {
    303          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    304          codeLensProvider = false,
    305        },
    306      }
    307    end,
    308    body = function() end,
    309  }
    310 end
    311 
    312 function tests.text_document_save_did_open()
    313  skeleton {
    314    on_init = function()
    315      return {
    316        capabilities = {
    317          textDocumentSync = {
    318            save = true,
    319          },
    320        },
    321      }
    322    end,
    323    body = function()
    324      notify('start')
    325      expect_notification('textDocument/didClose')
    326      expect_notification('textDocument/didOpen')
    327      expect_notification('textDocument/didSave')
    328      notify('shutdown')
    329    end,
    330  }
    331 end
    332 
    333 function tests.text_document_sync_save_bool()
    334  skeleton {
    335    on_init = function()
    336      return {
    337        capabilities = {
    338          textDocumentSync = {
    339            save = true,
    340          },
    341        },
    342      }
    343    end,
    344    body = function()
    345      notify('start')
    346      expect_notification('textDocument/didSave', { textDocument = { uri = 'file://' } })
    347      notify('shutdown')
    348    end,
    349  }
    350 end
    351 
    352 function tests.text_document_sync_save_includeText()
    353  skeleton {
    354    on_init = function()
    355      return {
    356        capabilities = {
    357          textDocumentSync = {
    358            save = {
    359              includeText = true,
    360            },
    361          },
    362        },
    363      }
    364    end,
    365    body = function()
    366      notify('start')
    367      expect_notification('textDocument/didSave', {
    368        textDocument = {
    369          uri = 'file://',
    370        },
    371        text = 'help me\n',
    372      })
    373      notify('shutdown')
    374    end,
    375  }
    376 end
    377 
    378 function tests.capabilities_for_client_supports_method()
    379  skeleton {
    380    on_init = function(params)
    381      local expected_capabilities = protocol.make_client_capabilities()
    382      assert_eq(params.capabilities, expected_capabilities)
    383      return {
    384        capabilities = {
    385          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    386          completionProvider = true,
    387          hoverProvider = true,
    388          renameProvider = false,
    389          definitionProvider = false,
    390          referencesProvider = false,
    391          codeLensProvider = { resolveProvider = true },
    392        },
    393      }
    394    end,
    395    body = function() end,
    396  }
    397 end
    398 
    399 function tests.check_forward_request_cancelled()
    400  skeleton {
    401    on_init = function(_)
    402      return { capabilities = {} }
    403    end,
    404    body = function()
    405      expect_request('error_code_test', function()
    406        return { code = -32800 }, nil, { method = 'error_code_test', client_id = 1 }
    407      end)
    408      notify('finish')
    409    end,
    410  }
    411 end
    412 
    413 function tests.check_forward_content_modified()
    414  skeleton {
    415    on_init = function(_)
    416      return { capabilities = {} }
    417    end,
    418    body = function()
    419      expect_request('error_code_test', function()
    420        return { code = -32801 }, nil, { method = 'error_code_test', client_id = 1 }
    421      end)
    422      expect_notification('finish')
    423      notify('finish')
    424    end,
    425  }
    426 end
    427 
    428 function tests.check_forward_server_cancelled()
    429  skeleton {
    430    on_init = function()
    431      return { capabilities = {} }
    432    end,
    433    body = function()
    434      expect_request('error_code_test', function()
    435        return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 }
    436      end)
    437      expect_notification('finish')
    438      notify('finish')
    439    end,
    440  }
    441 end
    442 
    443 function tests.check_pending_request_tracked()
    444  skeleton {
    445    on_init = function(_)
    446      return { capabilities = {} }
    447    end,
    448    body = function()
    449      local msg = read_message()
    450      assert_eq('slow_request', msg.method)
    451      expect_notification('release')
    452      respond(msg.id, nil, {})
    453      expect_notification('finish')
    454      notify('finish')
    455    end,
    456  }
    457 end
    458 
    459 function tests.check_cancel_request_tracked()
    460  skeleton {
    461    on_init = function(_)
    462      return { capabilities = {} }
    463    end,
    464    body = function()
    465      local msg = read_message()
    466      assert_eq('slow_request', msg.method)
    467      expect_notification('$/cancelRequest', { id = msg.id })
    468      expect_notification('release')
    469      respond(msg.id, { code = -32800 }, nil)
    470      notify('finish')
    471    end,
    472  }
    473 end
    474 
    475 function tests.check_tracked_requests_cleared()
    476  skeleton {
    477    on_init = function(_)
    478      return { capabilities = {} }
    479    end,
    480    body = function()
    481      local msg = read_message()
    482      assert_eq('slow_request', msg.method)
    483      expect_notification('$/cancelRequest', { id = msg.id })
    484      expect_notification('release')
    485      respond(msg.id, nil, {})
    486      expect_notification('finish')
    487      notify('finish')
    488    end,
    489  }
    490 end
    491 
    492 function tests.basic_finish()
    493  skeleton {
    494    on_init = function(params)
    495      local expected_capabilities = protocol.make_client_capabilities()
    496      assert_eq(params.capabilities, expected_capabilities)
    497      return {
    498        capabilities = {
    499          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    500        },
    501      }
    502    end,
    503    body = function()
    504      expect_notification('finish')
    505      notify('finish')
    506    end,
    507  }
    508 end
    509 
    510 function tests.basic_check_buffer_open()
    511  skeleton {
    512    on_init = function(params)
    513      local expected_capabilities = protocol.make_client_capabilities()
    514      assert_eq(params.capabilities, expected_capabilities)
    515      return {
    516        capabilities = {
    517          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    518        },
    519      }
    520    end,
    521    body = function()
    522      notify('start')
    523      expect_notification('textDocument/didOpen', {
    524        textDocument = {
    525          languageId = '',
    526          text = table.concat({ 'testing', '123' }, '\n') .. '\n',
    527          uri = 'file://',
    528          version = 0,
    529        },
    530      })
    531      expect_notification('finish')
    532      notify('finish')
    533    end,
    534  }
    535 end
    536 
    537 function tests.basic_check_buffer_open_and_change()
    538  skeleton {
    539    on_init = function(params)
    540      local expected_capabilities = protocol.make_client_capabilities()
    541      assert_eq(params.capabilities, expected_capabilities)
    542      return {
    543        capabilities = {
    544          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    545        },
    546      }
    547    end,
    548    body = function()
    549      notify('start')
    550      expect_notification('textDocument/didOpen', {
    551        textDocument = {
    552          languageId = '',
    553          text = table.concat({ 'testing', '123' }, '\n') .. '\n',
    554          uri = 'file://',
    555          version = 0,
    556        },
    557      })
    558      expect_notification('textDocument/didChange', {
    559        textDocument = {
    560          uri = 'file://',
    561          version = 3,
    562        },
    563        contentChanges = {
    564          { text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' },
    565        },
    566      })
    567      expect_notification('finish')
    568      notify('finish')
    569    end,
    570  }
    571 end
    572 
    573 function tests.basic_check_buffer_open_and_change_noeol()
    574  skeleton {
    575    on_init = function(params)
    576      local expected_capabilities = protocol.make_client_capabilities()
    577      assert_eq(params.capabilities, expected_capabilities)
    578      return {
    579        capabilities = {
    580          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    581        },
    582      }
    583    end,
    584    body = function()
    585      notify('start')
    586      expect_notification('textDocument/didOpen', {
    587        textDocument = {
    588          languageId = '',
    589          text = table.concat({ 'testing', '123' }, '\n'),
    590          uri = 'file://',
    591          version = 0,
    592        },
    593      })
    594      expect_notification('textDocument/didChange', {
    595        textDocument = {
    596          uri = 'file://',
    597          version = 3,
    598        },
    599        contentChanges = {
    600          { text = table.concat({ 'testing', 'boop' }, '\n') },
    601        },
    602      })
    603      expect_notification('finish')
    604      notify('finish')
    605    end,
    606  }
    607 end
    608 function tests.basic_check_buffer_open_and_change_multi()
    609  skeleton {
    610    on_init = function(params)
    611      local expected_capabilities = protocol.make_client_capabilities()
    612      assert_eq(params.capabilities, expected_capabilities)
    613      return {
    614        capabilities = {
    615          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    616        },
    617      }
    618    end,
    619    body = function()
    620      notify('start')
    621      expect_notification('textDocument/didOpen', {
    622        textDocument = {
    623          languageId = '',
    624          text = table.concat({ 'testing', '123' }, '\n') .. '\n',
    625          uri = 'file://',
    626          version = 0,
    627        },
    628      })
    629      expect_notification('textDocument/didChange', {
    630        textDocument = {
    631          uri = 'file://',
    632          version = 3,
    633        },
    634        contentChanges = {
    635          { text = table.concat({ 'testing', '321' }, '\n') .. '\n' },
    636        },
    637      })
    638      expect_notification('textDocument/didChange', {
    639        textDocument = {
    640          uri = 'file://',
    641          version = 4,
    642        },
    643        contentChanges = {
    644          { text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' },
    645        },
    646      })
    647      expect_notification('finish')
    648      notify('finish')
    649    end,
    650  }
    651 end
    652 
    653 function tests.basic_check_buffer_open_and_change_multi_and_close()
    654  skeleton {
    655    on_init = function(params)
    656      local expected_capabilities = protocol.make_client_capabilities()
    657      assert_eq(params.capabilities, expected_capabilities)
    658      return {
    659        capabilities = {
    660          textDocumentSync = protocol.TextDocumentSyncKind.Full,
    661        },
    662      }
    663    end,
    664    body = function()
    665      notify('start')
    666      expect_notification('textDocument/didOpen', {
    667        textDocument = {
    668          languageId = '',
    669          text = table.concat({ 'testing', '123' }, '\n') .. '\n',
    670          uri = 'file://',
    671          version = 0,
    672        },
    673      })
    674      expect_notification('textDocument/didChange', {
    675        textDocument = {
    676          uri = 'file://',
    677          version = 3,
    678        },
    679        contentChanges = {
    680          { text = table.concat({ 'testing', '321' }, '\n') .. '\n' },
    681        },
    682      })
    683      expect_notification('textDocument/didChange', {
    684        textDocument = {
    685          uri = 'file://',
    686          version = 4,
    687        },
    688        contentChanges = {
    689          { text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' },
    690        },
    691      })
    692      expect_notification('textDocument/didClose', {
    693        textDocument = {
    694          uri = 'file://',
    695        },
    696      })
    697      expect_notification('finish')
    698      notify('finish')
    699    end,
    700  }
    701 end
    702 
    703 function tests.basic_check_buffer_open_and_change_incremental()
    704  skeleton {
    705    on_init = function(params)
    706      local expected_capabilities = protocol.make_client_capabilities()
    707      assert_eq(params.capabilities, expected_capabilities)
    708      return {
    709        capabilities = {
    710          textDocumentSync = {
    711            openClose = true,
    712            change = protocol.TextDocumentSyncKind.Incremental,
    713            willSave = true,
    714            willSaveWaitUntil = true,
    715            save = {
    716              includeText = true,
    717            },
    718          },
    719        },
    720      }
    721    end,
    722    body = function()
    723      notify('start')
    724      expect_notification('textDocument/didOpen', {
    725        textDocument = {
    726          languageId = '',
    727          text = table.concat({ 'testing', '123' }, '\n') .. '\n',
    728          uri = 'file://',
    729          version = 0,
    730        },
    731      })
    732      expect_notification('textDocument/didChange', {
    733        textDocument = {
    734          uri = 'file://',
    735          version = 3,
    736        },
    737        contentChanges = {
    738          {
    739            range = {
    740              start = { line = 1, character = 3 },
    741              ['end'] = { line = 1, character = 3 },
    742            },
    743            rangeLength = 0,
    744            text = 'boop',
    745          },
    746        },
    747      })
    748      expect_notification('finish')
    749      notify('finish')
    750    end,
    751  }
    752 end
    753 
    754 function tests.basic_check_buffer_open_and_change_incremental_editing()
    755  skeleton {
    756    on_init = function(params)
    757      local expected_capabilities = protocol.make_client_capabilities()
    758      assert_eq(params.capabilities, expected_capabilities)
    759      return {
    760        capabilities = {
    761          textDocumentSync = protocol.TextDocumentSyncKind.Incremental,
    762        },
    763      }
    764    end,
    765    body = function()
    766      notify('start')
    767      expect_notification('textDocument/didOpen', {
    768        textDocument = {
    769          languageId = '',
    770          text = table.concat({ 'testing', '123' }, '\n'),
    771          uri = 'file://',
    772          version = 0,
    773        },
    774      })
    775      expect_notification('textDocument/didChange', {
    776        textDocument = {
    777          uri = 'file://',
    778          version = 3,
    779        },
    780        contentChanges = {
    781          {
    782            range = {
    783              start = { line = 0, character = 0 },
    784              ['end'] = { line = 1, character = 0 },
    785            },
    786            rangeLength = 4,
    787            text = 'testing\n\n',
    788          },
    789        },
    790      })
    791      expect_notification('finish')
    792      notify('finish')
    793    end,
    794  }
    795 end
    796 
    797 function tests.decode_nil()
    798  skeleton {
    799    on_init = function(_)
    800      return { capabilities = {} }
    801    end,
    802    body = function()
    803      notify('start')
    804      notify('workspace/executeCommand', {
    805        arguments = { 'EXTRACT_METHOD', { metadata = { field = vim.NIL } }, 3, 0, 6123, vim.NIL },
    806        command = 'refactor.perform',
    807        title = 'EXTRACT_METHOD',
    808      })
    809      notify('finish')
    810    end,
    811  }
    812 end
    813 
    814 function tests.code_action_with_resolve()
    815  skeleton {
    816    on_init = function()
    817      return {
    818        capabilities = {
    819          codeActionProvider = {
    820            resolveProvider = true,
    821          },
    822        },
    823      }
    824    end,
    825    body = function()
    826      notify('start')
    827      local cmd = { title = 'Action 1' }
    828      expect_request('textDocument/codeAction', function()
    829        return nil, { cmd }
    830      end)
    831      expect_request('codeAction/resolve', function()
    832        return nil,
    833          {
    834            title = 'Action 1',
    835            command = {
    836              title = 'Command 1',
    837              command = 'dummy1',
    838            },
    839          }
    840      end)
    841      notify('shutdown')
    842    end,
    843  }
    844 end
    845 
    846 function tests.code_action_server_side_command()
    847  skeleton({
    848    on_init = function()
    849      return {
    850        capabilities = {
    851          codeActionProvider = {
    852            resolveProvider = false,
    853          },
    854          executeCommandProvider = {
    855            commands = { 'dummy1' },
    856          },
    857        },
    858      }
    859    end,
    860    body = function()
    861      notify('start')
    862      local cmd = {
    863        title = 'Command 1',
    864        command = 'dummy1',
    865      }
    866      expect_request('textDocument/codeAction', function()
    867        return nil, { cmd }
    868      end)
    869      expect_request('workspace/executeCommand', function()
    870        return nil, cmd
    871      end)
    872      notify('shutdown')
    873    end,
    874  })
    875 end
    876 
    877 function tests.code_action_filter()
    878  skeleton {
    879    on_init = function()
    880      return {
    881        capabilities = {
    882          codeActionProvider = {
    883            resolveProvider = false,
    884          },
    885        },
    886      }
    887    end,
    888    body = function()
    889      notify('start')
    890      local action = {
    891        title = 'Action 1',
    892        command = 'command',
    893      }
    894      local preferred_action = {
    895        title = 'Action 2',
    896        isPreferred = true,
    897        command = 'preferred_command',
    898      }
    899      local type_annotate_action = {
    900        title = 'Action 3',
    901        kind = 'type-annotate',
    902        command = 'type_annotate_command',
    903      }
    904      local type_annotate_foo_action = {
    905        title = 'Action 4',
    906        kind = 'type-annotate.foo',
    907        command = 'type_annotate_foo_command',
    908      }
    909      expect_request('textDocument/codeAction', function()
    910        return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action }
    911      end)
    912      expect_request('textDocument/codeAction', function()
    913        return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action }
    914      end)
    915      notify('shutdown')
    916    end,
    917  }
    918 end
    919 
    920 function tests.clientside_commands()
    921  skeleton {
    922    on_init = function()
    923      return {
    924        capabilities = {},
    925      }
    926    end,
    927    body = function()
    928      notify('start')
    929      notify('shutdown')
    930    end,
    931  }
    932 end
    933 
    934 function tests.codelens_refresh_lock()
    935  skeleton {
    936    on_init = function()
    937      return {
    938        capabilities = {
    939          codeLensProvider = { resolveProvider = true },
    940        },
    941      }
    942    end,
    943    body = function()
    944      notify('start')
    945      expect_request('textDocument/codeLens', function()
    946        return { code = -32002, message = 'ServerNotInitialized' }, nil
    947      end)
    948      expect_request('textDocument/codeLens', function()
    949        local lenses = {
    950          {
    951            range = {
    952              start = { line = 0, character = 0 },
    953              ['end'] = { line = 0, character = 3 },
    954            },
    955            command = { title = 'Lens1', command = 'Dummy' },
    956          },
    957        }
    958        return nil, lenses
    959      end)
    960      expect_request('textDocument/codeLens', function()
    961        local lenses = {
    962          {
    963            range = {
    964              start = { line = 0, character = 0 },
    965              ['end'] = { line = 0, character = 3 },
    966            },
    967            command = { title = 'Lens2', command = 'Dummy' },
    968          },
    969        }
    970        return nil, lenses
    971      end)
    972      notify('shutdown')
    973    end,
    974  }
    975 end
    976 
    977 function tests.basic_formatting()
    978  skeleton {
    979    on_init = function()
    980      return {
    981        capabilities = {
    982          documentFormattingProvider = true,
    983        },
    984      }
    985    end,
    986    body = function()
    987      notify('start')
    988      expect_request('textDocument/formatting', function()
    989        return nil, {}
    990      end)
    991      notify('shutdown')
    992    end,
    993  }
    994 end
    995 
    996 function tests.range_formatting()
    997  skeleton {
    998    on_init = function()
    999      return {
   1000        capabilities = {
   1001          documentFormattingProvider = true,
   1002          documentRangeFormattingProvider = true,
   1003        },
   1004      }
   1005    end,
   1006    body = function()
   1007      notify('start')
   1008      expect_request('textDocument/rangeFormatting', function()
   1009        return nil, {}
   1010      end)
   1011      notify('shutdown')
   1012    end,
   1013  }
   1014 end
   1015 
   1016 function tests.ranges_formatting()
   1017  skeleton {
   1018    on_init = function()
   1019      return {
   1020        capabilities = {
   1021          documentFormattingProvider = true,
   1022          documentRangeFormattingProvider = {
   1023            rangesSupport = true,
   1024          },
   1025        },
   1026      }
   1027    end,
   1028    body = function()
   1029      notify('start')
   1030      expect_request('textDocument/rangesFormatting', function()
   1031        return nil, {}
   1032      end)
   1033      notify('shutdown')
   1034    end,
   1035  }
   1036 end
   1037 
   1038 function tests.set_defaults_all_capabilities()
   1039  skeleton {
   1040    on_init = function(_)
   1041      return {
   1042        capabilities = {
   1043          definitionProvider = true,
   1044          completionProvider = true,
   1045          documentRangeFormattingProvider = true,
   1046          hoverProvider = true,
   1047        },
   1048      }
   1049    end,
   1050    body = function()
   1051      notify('test')
   1052    end,
   1053  }
   1054 end
   1055 
   1056 function tests.inlay_hint()
   1057  skeleton {
   1058    on_init = function(params)
   1059      local expected_capabilities = protocol.make_client_capabilities()
   1060      assert_eq(params.capabilities, expected_capabilities)
   1061      return {
   1062        capabilities = {
   1063          inlayHintProvider = true,
   1064        },
   1065      }
   1066    end,
   1067    body = function()
   1068      notify('start')
   1069      expect_request('textDocument/inlayHint', function()
   1070        return nil, {}
   1071      end)
   1072      expect_notification('finish')
   1073      notify('finish')
   1074    end,
   1075  }
   1076 end
   1077 
   1078 -- Tests will be indexed by test_name
   1079 local test_name = arg[1]
   1080 local timeout = tonumber(arg[2])
   1081 assert(type(test_name) == 'string', 'test_name must be specified as first arg.')
   1082 
   1083 local kill_timer = vim.defer_fn(function()
   1084  log('ERROR', 'LSP', 'TIMEOUT')
   1085  io.stderr:write('TIMEOUT')
   1086  os.exit(100)
   1087 end, timeout or 1e3)
   1088 
   1089 -- Close the timer on exit (deadly signal or :cquit) to avoid delay with ASAN/TSAN.
   1090 vim.api.nvim_create_autocmd('VimLeave', {
   1091  callback = function()
   1092    kill_timer:stop()
   1093    kill_timer:close()
   1094  end,
   1095 })
   1096 
   1097 local status, err = pcall(assert(tests[test_name], 'Test not found'))
   1098 if not status then
   1099  log('ERROR', 'LSP', tostring(err))
   1100  io.stderr:write(err)
   1101  vim.cmd [[101cquit]]
   1102 end