mirror of
https://github.com/neovim/neovim.git
synced 2026-02-21 01:40:19 +10:00
Problem: Fake LSP server does not timeout or respond to SIGTERM as it
does not run the event loop.
Solution: Instead of io.read(), use stdioopen()'s on_stdin callback to
accumulate input and use vim.wait() to wait for input.
Also, in the test suite, don't stop a session when it's not running, as
calling uv.stop() outside uv.run() will instead cause the next uv.run()
to stop immediately, which cancels the next RPC request.
1103 lines
26 KiB
Lua
1103 lines
26 KiB
Lua
local protocol = require 'vim.lsp.protocol'
|
|
|
|
local pid = vim.uv.os_getpid()
|
|
local stdin = ''
|
|
vim.fn.stdioopen({
|
|
on_stdin = function(_, data, _)
|
|
stdin = stdin .. table.concat(data, '\n')
|
|
end,
|
|
})
|
|
|
|
-- Logs to $NVIM_LOG_FILE.
|
|
--
|
|
-- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062
|
|
local function log(loglevel, area, msg)
|
|
vim.fn.writefile(
|
|
{ string.format('%d %s %s: %s', pid, loglevel, area, msg) },
|
|
vim.env.NVIM_LOG_FILE,
|
|
'a'
|
|
)
|
|
end
|
|
|
|
local function message_parts(sep, ...)
|
|
local parts = {}
|
|
for i = 1, select('#', ...) do
|
|
local arg = select(i, ...)
|
|
if arg ~= nil then
|
|
table.insert(parts, arg)
|
|
end
|
|
end
|
|
return table.concat(parts, sep)
|
|
end
|
|
|
|
-- Assert utility methods
|
|
|
|
local function assert_eq(a, b, ...)
|
|
if not vim.deep_equal(a, b) then
|
|
error(
|
|
message_parts(
|
|
': ',
|
|
...,
|
|
'assert_eq failed',
|
|
string.format(
|
|
'left == %q, right == %q',
|
|
table.concat(vim.split(vim.inspect(a), '\n'), ''),
|
|
table.concat(vim.split(vim.inspect(b), '\n'), '')
|
|
)
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
local function format_message_with_content_length(encoded_message)
|
|
return table.concat {
|
|
'Content-Length: ',
|
|
tostring(#encoded_message),
|
|
'\r\n\r\n',
|
|
encoded_message,
|
|
}
|
|
end
|
|
|
|
local function read_line()
|
|
vim.wait(math.huge, function()
|
|
return stdin:find('\n') ~= nil
|
|
end, 1)
|
|
local eol = assert(stdin:find('\n'))
|
|
local line = stdin:sub(1, eol - 1)
|
|
stdin = stdin:sub(eol + 1)
|
|
return line
|
|
end
|
|
|
|
--- @param len integer
|
|
local function read_len(len)
|
|
vim.wait(math.huge, function()
|
|
return stdin:len() >= len
|
|
end, 1)
|
|
local content = stdin:sub(1, len)
|
|
stdin = stdin:sub(len + 1)
|
|
return content
|
|
end
|
|
|
|
local function read_message()
|
|
local line = read_line()
|
|
local length = line:lower():match('content%-length:%s*(%d+)')
|
|
return vim.json.decode(read_len(2 + length):sub(2))
|
|
end
|
|
|
|
local function send(payload)
|
|
io.stdout:write(format_message_with_content_length(vim.json.encode(payload)))
|
|
end
|
|
|
|
local function respond(id, err, result)
|
|
assert(type(id) == 'number', 'id must be a number')
|
|
send { jsonrpc = '2.0', id = id, error = err, result = result }
|
|
end
|
|
|
|
local function notify(method, params)
|
|
assert(type(method) == 'string', 'method must be a string')
|
|
send { method = method, params = params or {} }
|
|
end
|
|
|
|
local function expect_notification(method, params, ...)
|
|
local message = read_message()
|
|
assert_eq(method, message.method, ..., 'expect_notification', 'method')
|
|
if params then
|
|
assert_eq(params, message.params, ..., 'expect_notification', method, 'params')
|
|
assert_eq(
|
|
{ jsonrpc = '2.0', method = method, params = params },
|
|
message,
|
|
...,
|
|
'expect_notification',
|
|
'message'
|
|
)
|
|
end
|
|
end
|
|
|
|
local function expect_request(method, handler, ...)
|
|
local req = read_message()
|
|
assert_eq(method, req.method, ..., 'expect_request', 'method')
|
|
local err, result = handler(req.params)
|
|
respond(req.id, err, result)
|
|
end
|
|
|
|
io.stderr:setvbuf('no')
|
|
|
|
local function skeleton(config)
|
|
local on_init = assert(config.on_init)
|
|
local body = assert(config.body)
|
|
expect_request('initialize', function(params)
|
|
return nil, on_init(params)
|
|
end)
|
|
expect_notification('initialized', {})
|
|
body()
|
|
expect_request('shutdown', function()
|
|
return nil, {}
|
|
end)
|
|
expect_notification('exit', nil)
|
|
end
|
|
|
|
-- The actual tests.
|
|
|
|
local tests = {}
|
|
|
|
function tests.basic_init()
|
|
skeleton {
|
|
on_init = function(params)
|
|
assert_eq(params.workDoneToken, '1')
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.None,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('test')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_init_did_change_configuration()
|
|
skeleton({
|
|
on_init = function(_)
|
|
return {
|
|
capabilities = {},
|
|
}
|
|
end,
|
|
body = function()
|
|
expect_notification('workspace/didChangeConfiguration', { settings = { dummy = 1 } })
|
|
end,
|
|
})
|
|
end
|
|
|
|
function tests.check_workspace_configuration()
|
|
skeleton {
|
|
on_init = function(_params)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
notify('workspace/configuration', {
|
|
items = {
|
|
{ section = 'testSetting1' },
|
|
{ section = 'testSetting2' },
|
|
{ section = 'test.Setting3' },
|
|
{ section = 'test.Setting4' },
|
|
{},
|
|
{ section = '' },
|
|
},
|
|
})
|
|
local all = {
|
|
testSetting1 = true,
|
|
testSetting2 = false,
|
|
test = { Setting3 = 'nested' },
|
|
}
|
|
expect_notification('workspace/configuration', { true, false, 'nested', vim.NIL, all, all })
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.prepare_rename_nil()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
renameProvider = {
|
|
prepareProvider = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/prepareRename', function()
|
|
return {}, nil
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.prepare_rename_placeholder()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
renameProvider = {
|
|
prepareProvider = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/prepareRename', function()
|
|
return nil, { placeholder = 'placeholder' }
|
|
end)
|
|
expect_request('textDocument/rename', function(params)
|
|
assert_eq(params.newName, 'renameto')
|
|
return {}, nil
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.prepare_rename_range()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
renameProvider = {
|
|
prepareProvider = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/prepareRename', function()
|
|
return nil,
|
|
{
|
|
start = { line = 1, character = 8 },
|
|
['end'] = { line = 1, character = 12 },
|
|
}
|
|
end)
|
|
expect_request('textDocument/rename', function(params)
|
|
assert_eq(params.newName, 'renameto')
|
|
return {}, nil
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.prepare_rename_error()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
renameProvider = {
|
|
prepareProvider = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/prepareRename', function()
|
|
return {}, nil
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_capabilities()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
codeLensProvider = false,
|
|
},
|
|
}
|
|
end,
|
|
body = function() end,
|
|
}
|
|
end
|
|
|
|
function tests.text_document_save_did_open()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = {
|
|
save = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didClose')
|
|
expect_notification('textDocument/didOpen')
|
|
expect_notification('textDocument/didSave')
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.text_document_sync_save_bool()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = {
|
|
save = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didSave', { textDocument = { uri = 'file://' } })
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.text_document_sync_save_includeText()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = {
|
|
save = {
|
|
includeText = true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didSave', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
},
|
|
text = 'help me\n',
|
|
})
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.capabilities_for_client_supports_method()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
completionProvider = true,
|
|
hoverProvider = true,
|
|
renameProvider = false,
|
|
definitionProvider = false,
|
|
referencesProvider = false,
|
|
codeLensProvider = { resolveProvider = true },
|
|
},
|
|
}
|
|
end,
|
|
body = function() end,
|
|
}
|
|
end
|
|
|
|
function tests.check_forward_request_cancelled()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
expect_request('error_code_test', function()
|
|
return { code = -32800 }, nil, { method = 'error_code_test', client_id = 1 }
|
|
end)
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.check_forward_content_modified()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
expect_request('error_code_test', function()
|
|
return { code = -32801 }, nil, { method = 'error_code_test', client_id = 1 }
|
|
end)
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.check_forward_server_cancelled()
|
|
skeleton {
|
|
on_init = function()
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
expect_request('error_code_test', function()
|
|
return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 }
|
|
end)
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.check_pending_request_tracked()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
local msg = read_message()
|
|
assert_eq('slow_request', msg.method)
|
|
expect_notification('release')
|
|
respond(msg.id, nil, {})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.check_cancel_request_tracked()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
local msg = read_message()
|
|
assert_eq('slow_request', msg.method)
|
|
expect_notification('$/cancelRequest', { id = msg.id })
|
|
expect_notification('release')
|
|
respond(msg.id, { code = -32800 }, nil)
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.check_tracked_requests_cleared()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
local msg = read_message()
|
|
assert_eq('slow_request', msg.method)
|
|
expect_notification('$/cancelRequest', { id = msg.id })
|
|
expect_notification('release')
|
|
respond(msg.id, nil, {})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_finish()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_buffer_open()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n') .. '\n',
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_buffer_open_and_change()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n') .. '\n',
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 3,
|
|
},
|
|
contentChanges = {
|
|
{ text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' },
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_buffer_open_and_change_noeol()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n'),
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 3,
|
|
},
|
|
contentChanges = {
|
|
{ text = table.concat({ 'testing', 'boop' }, '\n') },
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
function tests.basic_check_buffer_open_and_change_multi()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n') .. '\n',
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 3,
|
|
},
|
|
contentChanges = {
|
|
{ text = table.concat({ 'testing', '321' }, '\n') .. '\n' },
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 4,
|
|
},
|
|
contentChanges = {
|
|
{ text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' },
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_buffer_open_and_change_multi_and_close()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Full,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n') .. '\n',
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 3,
|
|
},
|
|
contentChanges = {
|
|
{ text = table.concat({ 'testing', '321' }, '\n') .. '\n' },
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 4,
|
|
},
|
|
contentChanges = {
|
|
{ text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' },
|
|
},
|
|
})
|
|
expect_notification('textDocument/didClose', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_buffer_open_and_change_incremental()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = {
|
|
openClose = true,
|
|
change = protocol.TextDocumentSyncKind.Incremental,
|
|
willSave = true,
|
|
willSaveWaitUntil = true,
|
|
save = {
|
|
includeText = true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n') .. '\n',
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 3,
|
|
},
|
|
contentChanges = {
|
|
{
|
|
range = {
|
|
start = { line = 1, character = 3 },
|
|
['end'] = { line = 1, character = 3 },
|
|
},
|
|
rangeLength = 0,
|
|
text = 'boop',
|
|
},
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_check_buffer_open_and_change_incremental_editing()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
textDocumentSync = protocol.TextDocumentSyncKind.Incremental,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_notification('textDocument/didOpen', {
|
|
textDocument = {
|
|
languageId = '',
|
|
text = table.concat({ 'testing', '123' }, '\n'),
|
|
uri = 'file://',
|
|
version = 0,
|
|
},
|
|
})
|
|
expect_notification('textDocument/didChange', {
|
|
textDocument = {
|
|
uri = 'file://',
|
|
version = 3,
|
|
},
|
|
contentChanges = {
|
|
{
|
|
range = {
|
|
start = { line = 0, character = 0 },
|
|
['end'] = { line = 1, character = 0 },
|
|
},
|
|
rangeLength = 4,
|
|
text = 'testing\n\n',
|
|
},
|
|
},
|
|
})
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.decode_nil()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return { capabilities = {} }
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
notify('workspace/executeCommand', {
|
|
arguments = { 'EXTRACT_METHOD', { metadata = { field = vim.NIL } }, 3, 0, 6123, vim.NIL },
|
|
command = 'refactor.perform',
|
|
title = 'EXTRACT_METHOD',
|
|
})
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.code_action_with_resolve()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
codeActionProvider = {
|
|
resolveProvider = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
local cmd = { title = 'Action 1' }
|
|
expect_request('textDocument/codeAction', function()
|
|
return nil, { cmd }
|
|
end)
|
|
expect_request('codeAction/resolve', function()
|
|
return nil,
|
|
{
|
|
title = 'Action 1',
|
|
command = {
|
|
title = 'Command 1',
|
|
command = 'dummy1',
|
|
},
|
|
}
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.code_action_server_side_command()
|
|
skeleton({
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
codeActionProvider = {
|
|
resolveProvider = false,
|
|
},
|
|
executeCommandProvider = {
|
|
commands = { 'dummy1' },
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
local cmd = {
|
|
title = 'Command 1',
|
|
command = 'dummy1',
|
|
}
|
|
expect_request('textDocument/codeAction', function()
|
|
return nil, { cmd }
|
|
end)
|
|
expect_request('workspace/executeCommand', function()
|
|
return nil, cmd
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
})
|
|
end
|
|
|
|
function tests.code_action_filter()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
codeActionProvider = {
|
|
resolveProvider = false,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
local action = {
|
|
title = 'Action 1',
|
|
command = 'command',
|
|
}
|
|
local preferred_action = {
|
|
title = 'Action 2',
|
|
isPreferred = true,
|
|
command = 'preferred_command',
|
|
}
|
|
local type_annotate_action = {
|
|
title = 'Action 3',
|
|
kind = 'type-annotate',
|
|
command = 'type_annotate_command',
|
|
}
|
|
local type_annotate_foo_action = {
|
|
title = 'Action 4',
|
|
kind = 'type-annotate.foo',
|
|
command = 'type_annotate_foo_command',
|
|
}
|
|
expect_request('textDocument/codeAction', function()
|
|
return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action }
|
|
end)
|
|
expect_request('textDocument/codeAction', function()
|
|
return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action }
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.clientside_commands()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.codelens_refresh_lock()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
codeLensProvider = { resolveProvider = true },
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/codeLens', function()
|
|
return { code = -32002, message = 'ServerNotInitialized' }, nil
|
|
end)
|
|
expect_request('textDocument/codeLens', function()
|
|
local lenses = {
|
|
{
|
|
range = {
|
|
start = { line = 0, character = 0 },
|
|
['end'] = { line = 0, character = 3 },
|
|
},
|
|
command = { title = 'Lens1', command = 'Dummy' },
|
|
},
|
|
}
|
|
return nil, lenses
|
|
end)
|
|
expect_request('textDocument/codeLens', function()
|
|
local lenses = {
|
|
{
|
|
range = {
|
|
start = { line = 0, character = 0 },
|
|
['end'] = { line = 0, character = 3 },
|
|
},
|
|
command = { title = 'Lens2', command = 'Dummy' },
|
|
},
|
|
}
|
|
return nil, lenses
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.basic_formatting()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
documentFormattingProvider = true,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/formatting', function()
|
|
return nil, {}
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.range_formatting()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
documentFormattingProvider = true,
|
|
documentRangeFormattingProvider = true,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/rangeFormatting', function()
|
|
return nil, {}
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.ranges_formatting()
|
|
skeleton {
|
|
on_init = function()
|
|
return {
|
|
capabilities = {
|
|
documentFormattingProvider = true,
|
|
documentRangeFormattingProvider = {
|
|
rangesSupport = true,
|
|
},
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/rangesFormatting', function()
|
|
return nil, {}
|
|
end)
|
|
notify('shutdown')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.set_defaults_all_capabilities()
|
|
skeleton {
|
|
on_init = function(_)
|
|
return {
|
|
capabilities = {
|
|
definitionProvider = true,
|
|
completionProvider = true,
|
|
documentRangeFormattingProvider = true,
|
|
hoverProvider = true,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('test')
|
|
end,
|
|
}
|
|
end
|
|
|
|
function tests.inlay_hint()
|
|
skeleton {
|
|
on_init = function(params)
|
|
local expected_capabilities = protocol.make_client_capabilities()
|
|
assert_eq(params.capabilities, expected_capabilities)
|
|
return {
|
|
capabilities = {
|
|
inlayHintProvider = true,
|
|
},
|
|
}
|
|
end,
|
|
body = function()
|
|
notify('start')
|
|
expect_request('textDocument/inlayHint', function()
|
|
return nil, {}
|
|
end)
|
|
expect_notification('finish')
|
|
notify('finish')
|
|
end,
|
|
}
|
|
end
|
|
|
|
-- Tests will be indexed by test_name
|
|
local test_name = arg[1]
|
|
local timeout = tonumber(arg[2])
|
|
assert(type(test_name) == 'string', 'test_name must be specified as first arg.')
|
|
|
|
local kill_timer = vim.defer_fn(function()
|
|
log('ERROR', 'LSP', 'TIMEOUT')
|
|
io.stderr:write('TIMEOUT')
|
|
os.exit(100)
|
|
end, timeout or 1e3)
|
|
|
|
-- Close the timer on exit (deadly signal or :cquit) to avoid delay with ASAN/TSAN.
|
|
vim.api.nvim_create_autocmd('VimLeave', {
|
|
callback = function()
|
|
kill_timer:stop()
|
|
kill_timer:close()
|
|
end,
|
|
})
|
|
|
|
local status, err = pcall(assert(tests[test_name], 'Test not found'))
|
|
if not status then
|
|
log('ERROR', 'LSP', tostring(err))
|
|
io.stderr:write(err)
|
|
vim.cmd [[101cquit]]
|
|
end
|