aboutsummaryrefslogtreecommitdiff
path: root/test/functional/plugin/lsp_spec.lua
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/plugin/lsp_spec.lua')
-rw-r--r--test/functional/plugin/lsp_spec.lua626
1 files changed, 504 insertions, 122 deletions
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index cd7415de90..5229022564 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -1,8 +1,9 @@
local helpers = require('test.functional.helpers')(after_each)
+local lsp_helpers = require('test.functional.plugin.lsp.helpers')
local assert_log = helpers.assert_log
-local clear = helpers.clear
local buf_lines = helpers.buf_lines
+local clear = helpers.clear
local command = helpers.command
local dedent = helpers.dedent
local exec_lua = helpers.exec_lua
@@ -14,133 +15,28 @@ local pesc = helpers.pesc
local insert = helpers.insert
local funcs = helpers.funcs
local retry = helpers.retry
+local stop = helpers.stop
local NIL = helpers.NIL
local read_file = require('test.helpers').read_file
local write_file = require('test.helpers').write_file
-local isCI = helpers.isCI
+local is_ci = helpers.is_ci
local meths = helpers.meths
+local is_os = helpers.is_os
+local skip = helpers.skip
--- Use these to get access to a coroutine so that I can run async tests and use
--- yield.
-local run, stop = helpers.run, helpers.stop
+local clear_notrace = lsp_helpers.clear_notrace
+local create_server_definition = lsp_helpers.create_server_definition
+local fake_lsp_code = lsp_helpers.fake_lsp_code
+local fake_lsp_logfile = lsp_helpers.fake_lsp_logfile
+local test_rpc_server = lsp_helpers.test_rpc_server
-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
-if helpers.pending_win32(pending) then return end
-
--- Fake LSP server.
-local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua'
-local fake_lsp_logfile = 'Xtest-fake-lsp.log'
+if skip(is_os('win')) then return end
teardown(function()
os.remove(fake_lsp_logfile)
end)
-local function clear_notrace()
- -- problem: here be dragons
- -- solution: don't look for dragons to closely
- clear {env={
- NVIM_LUA_NOTRACK="1";
- VIMRUNTIME=os.getenv"VIMRUNTIME";
- }}
-end
-
-
-local function fake_lsp_server_setup(test_name, timeout_ms, options, settings)
- exec_lua([=[
- lsp = require('vim.lsp')
- local test_name, fixture_filename, logfile, timeout, options, settings = ...
- TEST_RPC_CLIENT_ID = lsp.start_client {
- cmd_env = {
- NVIM_LOG_FILE = logfile;
- NVIM_LUA_NOTRACK = "1";
- };
- cmd = {
- vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
- "-c", string.format("lua TEST_NAME = %q", test_name),
- "-c", string.format("lua TIMEOUT = %d", timeout),
- "-c", "luafile "..fixture_filename,
- };
- handlers = setmetatable({}, {
- __index = function(t, method)
- return function(...)
- return vim.rpcrequest(1, 'handler', ...)
- end
- end;
- });
- workspace_folders = {{
- uri = 'file://' .. vim.loop.cwd(),
- name = 'test_folder',
- }};
- on_init = function(client, result)
- TEST_RPC_CLIENT = client
- vim.rpcrequest(1, "init", result)
- end;
- flags = {
- allow_incremental_sync = options.allow_incremental_sync or false;
- debounce_text_changes = options.debounce_text_changes or 0;
- };
- settings = settings;
- on_exit = function(...)
- vim.rpcnotify(1, "exit", ...)
- end;
- }
- ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {})
-end
-
-local function test_rpc_server(config)
- if config.test_name then
- clear_notrace()
- fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings)
- end
- local client = setmetatable({}, {
- __index = function(_, name)
- -- Workaround for not being able to yield() inside __index for Lua 5.1 :(
- -- Otherwise I would just return the value here.
- return function(...)
- return exec_lua([=[
- local name = ...
- if type(TEST_RPC_CLIENT[name]) == 'function' then
- return TEST_RPC_CLIENT[name](select(2, ...))
- else
- return TEST_RPC_CLIENT[name]
- end
- ]=], name, ...)
- end
- end;
- })
- local code, signal
- local function on_request(method, args)
- if method == "init" then
- if config.on_init then
- config.on_init(client, unpack(args))
- end
- return NIL
- end
- if method == 'handler' then
- if config.on_handler then
- config.on_handler(unpack(args))
- end
- end
- return NIL
- end
- local function on_notify(method, args)
- if method == 'exit' then
- code, signal = unpack(args)
- return stop()
- end
- end
- -- TODO specify timeout?
- -- run(on_request, on_notify, config.on_setup, 1000)
- run(on_request, on_notify, config.on_setup)
- if config.on_exit then
- config.on_exit(code, signal)
- end
- stop()
- if config.test_name then
- exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })")
- end
-end
-
describe('LSP', function()
before_each(function()
clear_notrace()
@@ -236,9 +132,9 @@ describe('LSP', function()
end)
it('should invalid cmd argument', function()
- eq('Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim',
+ eq('.../lsp.lua:0: cmd: expected list, got nvim',
pcall_err(_cmd_parts, 'nvim'))
- eq('Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number',
+ eq('.../lsp.lua:0: cmd argument: expected string, got number',
pcall_err(_cmd_parts, {'nvim', 1}))
end)
end)
@@ -316,7 +212,7 @@ describe('LSP', function()
end)
it('should succeed with manual shutdown', function()
- if isCI() then
+ if is_ci() then
pending('hangs the build on CI #14028, re-enable with freeze timeout #14204')
return
elseif helpers.skip_fragile(pending) then
@@ -418,6 +314,31 @@ describe('LSP', function()
}
end)
+ it('should detach buffer on bufwipe', function()
+ clear()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server()
+ local bufnr = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_set_current_buf(bufnr)
+ local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd })
+ assert(client_id, "lsp.start must return client_id")
+ local client = vim.lsp.get_client_by_id(client_id)
+ local num_attached_before = vim.tbl_count(client.attached_buffers)
+ vim.api.nvim_buf_delete(bufnr, { force = true })
+ local num_attached_after = vim.tbl_count(client.attached_buffers)
+ return {
+ bufnr = bufnr,
+ client_id = client_id,
+ num_attached_before = num_attached_before,
+ num_attached_after = num_attached_after,
+ }
+ ]])
+ eq(true, result ~= nil, "exec_lua must return result")
+ eq(1, result.num_attached_before)
+ eq(0, result.num_attached_after)
+ end)
+
it('client should return settings via workspace/configuration handler', function()
local expected_handlers = {
{NIL, {}, {method="shutdown", client_id=1}};
@@ -535,6 +456,70 @@ describe('LSP', function()
}
end)
+ it('BufWritePre does not send notifications if server lacks willSave capabilities', function()
+ clear()
+ exec_lua(create_server_definition)
+ local messages = exec_lua([[
+ local server = _create_server({
+ capabilities = {
+ textDocumentSync = {
+ willSave = false,
+ willSaveWaitUntil = false,
+ }
+ },
+ })
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local buf = vim.api.nvim_get_current_buf()
+ vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false })
+ vim.lsp.stop_client(client_id)
+ return server.messages
+ ]])
+ eq(#messages, 4)
+ eq(messages[1].method, 'initialize')
+ eq(messages[2].method, 'initialized')
+ eq(messages[3].method, 'shutdown')
+ eq(messages[4].method, 'exit')
+ end)
+
+ it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function()
+ clear()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server({
+ capabilities = {
+ textDocumentSync = {
+ willSave = true,
+ willSaveWaitUntil = true,
+ }
+ },
+ handlers = {
+ ['textDocument/willSaveWaitUntil'] = function()
+ local text_edit = {
+ range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 0, character = 0 },
+ },
+ newText = 'Hello'
+ }
+ return { text_edit, }
+ end
+ },
+ })
+ local buf = vim.api.nvim_get_current_buf()
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false })
+ vim.lsp.stop_client(client_id)
+ return {
+ messages = server.messages,
+ lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true)
+ }
+ ]])
+ local messages = result.messages
+ eq('textDocument/willSave', messages[3].method)
+ eq('textDocument/willSaveWaitUntil', messages[4].method)
+ eq({'Hello'}, result.lines)
+ end)
+
it('saveas sends didOpen if filename changed', function()
local expected_handlers = {
{ NIL, {}, { method = 'shutdown', client_id = 1 } },
@@ -1682,6 +1667,46 @@ describe('LSP', function()
end)
end)
+ describe('apply_text_edits regression tests for #20116', function()
+ before_each(function()
+ insert(dedent([[
+ Test line one
+ Test line two 21 char]]))
+ end)
+ describe('with LSP end column out of bounds and start column at 0', function()
+ it('applies edits at the end of the buffer', function()
+ local edits = {
+ make_edit(0, 0, 1, 22, {'#include "whatever.h"\r\n#include <algorithm>\r'});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-8")
+ eq({'#include "whatever.h"', '#include <algorithm>'}, buf_lines(1))
+ end)
+ it('applies edits in the middle of the buffer', function()
+ local edits = {
+ make_edit(0, 0, 0, 22, {'#include "whatever.h"\r\n#include <algorithm>\r'});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-8")
+ eq({'#include "whatever.h"', '#include <algorithm>', 'Test line two 21 char'}, buf_lines(1))
+ end)
+ end)
+ describe('with LSP end column out of bounds and start column NOT at 0', function()
+ it('applies edits at the end of the buffer', function()
+ local edits = {
+ make_edit(0, 2, 1, 22, {'#include "whatever.h"\r\n#include <algorithm>\r'});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-8")
+ eq({'Te#include "whatever.h"', '#include <algorithm>'}, buf_lines(1))
+ end)
+ it('applies edits in the middle of the buffer', function()
+ local edits = {
+ make_edit(0, 2, 0, 22, {'#include "whatever.h"\r\n#include <algorithm>\r'});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-8")
+ eq({'Te#include "whatever.h"', '#include <algorithm>', 'Test line two 21 char'}, buf_lines(1))
+ end)
+ end)
+ end)
+
describe('apply_text_document_edit', function()
local target_bufnr
local text_document_edit = function(editVersion)
@@ -1900,6 +1925,22 @@ describe('LSP', function()
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')
eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
end)
+ it('Supports file creation in folder that needs to be created with CreateFile payload', function()
+ local tmpfile = helpers.tmpname()
+ os.remove(tmpfile) -- Should not exist, only interested in a tmpname
+ tmpfile = tmpfile .. '/dummy/x/'
+ local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile)
+ local edit = {
+ documentChanges = {
+ {
+ kind = 'create',
+ uri = uri,
+ },
+ }
+ }
+ exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')
+ eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
+ end)
it('createFile does not touch file if it exists and ignoreIfExists is set', function()
local tmpfile = helpers.tmpname()
write_file(tmpfile, 'Dummy content')
@@ -2377,7 +2418,7 @@ describe('LSP', function()
},
uri = "file:///test_a"
},
- contanerName = "TestAContainer"
+ containerName = "TestAContainer"
},
{
deprecated = false,
@@ -2396,7 +2437,7 @@ describe('LSP', function()
},
uri = "file:///test_b"
},
- contanerName = "TestBContainer"
+ containerName = "TestBContainer"
}
}
return vim.lsp.util.symbols_to_items(sym_info, nil)
@@ -2493,7 +2534,7 @@ describe('LSP', function()
local mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
eq({ 1, 0 }, mark)
- funcs.nvim_win_set_cursor(0, {2, 3})
+ funcs.nvim_win_set_cursor(0, { 2, 3 })
jump(location(0, 9, 0, 9))
mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
@@ -2501,6 +2542,193 @@ describe('LSP', function()
end)
end)
+ describe('lsp.util.show_document', function()
+ local target_bufnr
+ local target_bufnr2
+
+ before_each(function()
+ target_bufnr = exec_lua([[
+ local bufnr = vim.uri_to_bufnr("file:///fake/uri")
+ local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]])
+
+ target_bufnr2 = exec_lua([[
+ local bufnr = vim.uri_to_bufnr("file:///fake/uri2")
+ local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]])
+ end)
+
+ local location = function(start_line, start_char, end_line, end_char, second_uri)
+ return {
+ uri = second_uri and 'file:///fake/uri2' or 'file:///fake/uri',
+ range = {
+ start = { line = start_line, character = start_char },
+ ['end'] = { line = end_line, character = end_char },
+ },
+ }
+ end
+
+ local show_document = function(msg, focus, reuse_win)
+ eq(
+ true,
+ exec_lua(
+ 'return vim.lsp.util.show_document(...)',
+ msg,
+ 'utf-16',
+ { reuse_win = reuse_win, focus = focus }
+ )
+ )
+ if focus == true or focus == nil then
+ eq(target_bufnr, exec_lua([[return vim.fn.bufnr('%')]]))
+ end
+ return {
+ line = exec_lua([[return vim.fn.line('.')]]),
+ col = exec_lua([[return vim.fn.col('.')]]),
+ }
+ end
+
+ it('jumps to a Location if focus is true', function()
+ local pos = show_document(location(0, 9, 0, 9), true, true)
+ eq(1, pos.line)
+ eq(10, pos.col)
+ end)
+
+ it('jumps to a Location if focus is true via handler', function()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server()
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local result = {
+ uri = 'file:///fake/uri',
+ selection = {
+ start = { line = 0, character = 9 },
+ ['end'] = { line = 0, character = 9 }
+ },
+ takeFocus = true,
+ }
+ local ctx = {
+ client_id = client_id,
+ method = 'window/showDocument',
+ }
+ vim.lsp.handlers['window/showDocument'](nil, result, ctx)
+ vim.lsp.stop_client(client_id)
+ return {
+ cursor = vim.api.nvim_win_get_cursor(0)
+ }
+ ]])
+ eq(1, result.cursor[1])
+ eq(9, result.cursor[2])
+ end)
+
+ it('jumps to a Location if focus not set', function()
+ local pos = show_document(location(0, 9, 0, 9), nil, true)
+ eq(1, pos.line)
+ eq(10, pos.col)
+ end)
+
+ it('does not add current position to jumplist if not focus', function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+ local mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
+ eq({ 1, 0 }, mark)
+
+ funcs.nvim_win_set_cursor(0, { 2, 3 })
+ show_document(location(0, 9, 0, 9), false, true)
+ show_document(location(0, 9, 0, 9, true), false, true)
+
+ mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
+ eq({ 1, 0 }, mark)
+ end)
+
+ it('does not change cursor position if not focus and not reuse_win', function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+ local cursor = funcs.nvim_win_get_cursor(0)
+
+ show_document(location(0, 9, 0, 9), false, false)
+ eq(cursor, funcs.nvim_win_get_cursor(0))
+ end)
+
+ it('does not change window if not focus', function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+ local win = funcs.nvim_get_current_win()
+
+ -- same document/bufnr
+ show_document(location(0, 9, 0, 9), false, true)
+ eq(win, funcs.nvim_get_current_win())
+
+ -- different document/bufnr, new window/split
+ show_document(location(0, 9, 0, 9, true), false, true)
+ eq(2, #funcs.nvim_list_wins())
+ eq(win, funcs.nvim_get_current_win())
+ end)
+
+ it("respects 'reuse_win' parameter", function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+
+ -- does not create a new window if the buffer is already open
+ show_document(location(0, 9, 0, 9), false, true)
+ eq(1, #funcs.nvim_list_wins())
+
+ -- creates a new window even if the buffer is already open
+ show_document(location(0, 9, 0, 9), false, false)
+ eq(2, #funcs.nvim_list_wins())
+ end)
+
+ it('correctly sets the cursor of the split if range is given without focus', function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+
+ show_document(location(0, 9, 0, 9, true), false, true)
+
+ local wins = funcs.nvim_list_wins()
+ eq(2, #wins)
+ table.sort(wins)
+
+ eq({ 1, 0 }, funcs.nvim_win_get_cursor(wins[1]))
+ eq({ 1, 9 }, funcs.nvim_win_get_cursor(wins[2]))
+ end)
+
+ it('does not change cursor of the split if not range and not focus', function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+ funcs.nvim_win_set_cursor(0, { 2, 3 })
+
+ exec_lua([[vim.cmd.new()]])
+ funcs.nvim_win_set_buf(0, target_bufnr2)
+ funcs.nvim_win_set_cursor(0, { 2, 3 })
+
+ show_document({ uri = 'file:///fake/uri2' }, false, true)
+
+ local wins = funcs.nvim_list_wins()
+ eq(2, #wins)
+ eq({ 2, 3 }, funcs.nvim_win_get_cursor(wins[1]))
+ eq({ 2, 3 }, funcs.nvim_win_get_cursor(wins[2]))
+ end)
+
+ it('respects existing buffers', function()
+ funcs.nvim_win_set_buf(0, target_bufnr)
+ local win = funcs.nvim_get_current_win()
+
+ exec_lua([[vim.cmd.new()]])
+ funcs.nvim_win_set_buf(0, target_bufnr2)
+ funcs.nvim_win_set_cursor(0, { 2, 3 })
+ local split = funcs.nvim_get_current_win()
+
+ -- reuse win for open document/bufnr if called from split
+ show_document(location(0, 9, 0, 9, true), false, true)
+ eq({ 1, 9 }, funcs.nvim_win_get_cursor(split))
+ eq(2, #funcs.nvim_list_wins())
+
+ funcs.nvim_set_current_win(win)
+
+ -- reuse win for open document/bufnr if called outside the split
+ show_document(location(0, 9, 0, 9, true), false, true)
+ eq({ 1, 9 }, funcs.nvim_win_get_cursor(split))
+ eq(2, #funcs.nvim_list_wins())
+ end)
+ end)
+
describe('lsp.util._make_floating_popup_size', function()
before_each(function()
exec_lua [[ contents =
@@ -3180,5 +3408,159 @@ describe('LSP', function()
end,
}
end)
+ it('format formats range in visual mode', function()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server({ capabilities = {
+ documentFormattingProvider = true,
+ documentRangeFormattingProvider = true,
+ }})
+ local bufnr = vim.api.nvim_get_current_buf()
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ vim.api.nvim_win_set_buf(0, bufnr)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'})
+ vim.api.nvim_win_set_cursor(0, { 1, 0 })
+ vim.cmd.normal('v')
+ vim.api.nvim_win_set_cursor(0, { 2, 3 })
+ vim.lsp.buf.format({ bufnr = bufnr, false })
+ return server.messages
+ ]])
+ eq("textDocument/rangeFormatting", result[3].method)
+ local expected_range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 1, character = 4 },
+ }
+ eq(expected_range, result[3].params.range)
+ end)
+ it('Aborts with notify if no clients support requested method', function()
+ exec_lua(create_server_definition)
+ exec_lua([[
+ vim.notify = function(msg, _)
+ notify_msg = msg
+ end
+ ]])
+ local fail_msg = "[LSP] Format request failed, no matching language servers."
+ local function check_notify(name, formatting, range_formatting)
+ local timeout_msg = "[LSP][" .. name .. "] timeout"
+ exec_lua([[
+ local formatting, range_formatting, name = ...
+ local server = _create_server({ capabilities = {
+ documentFormattingProvider = formatting,
+ documentRangeFormattingProvider = range_formatting,
+ }})
+ vim.lsp.start({ name = name, cmd = server.cmd })
+ notify_msg = nil
+ vim.lsp.buf.format({ name = name, timeout_ms = 1 })
+ ]], formatting, range_formatting, name)
+ eq(formatting and timeout_msg or fail_msg, exec_lua('return notify_msg'))
+ exec_lua([[
+ notify_msg = nil
+ vim.lsp.buf.format({ name = name, timeout_ms = 1, range = {start={1, 0}, ['end']={1, 0}}})
+ ]])
+ eq(range_formatting and timeout_msg or fail_msg, exec_lua('return notify_msg'))
+ end
+ check_notify("none", false, false)
+ check_notify("formatting", true, false)
+ check_notify("rangeFormatting", false, true)
+ check_notify("both", true, true)
+ end)
+ end)
+ describe('cmd', function()
+ it('can connect to lsp server via rpc.connect', function()
+ local result = exec_lua [[
+ local uv = vim.loop
+ local server = uv.new_tcp()
+ local init = nil
+ server:bind('127.0.0.1', 0)
+ server:listen(127, function(err)
+ assert(not err, err)
+ local socket = uv.new_tcp()
+ server:accept(socket)
+ socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body)
+ init = body
+ socket:close()
+ end))
+ end)
+ local port = server:getsockname().port
+ vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) })
+ vim.wait(1000, function() return init ~= nil end)
+ assert(init, "server must receive `initialize` request")
+ server:close()
+ server:shutdown()
+ return vim.json.decode(init)
+ ]]
+ eq(result.method, "initialize")
+ end)
+ end)
+
+ describe('handlers', function()
+ it('handler can return false as response', function()
+ local result = exec_lua [[
+ local uv = vim.loop
+ local server = uv.new_tcp()
+ local messages = {}
+ local responses = {}
+ server:bind('127.0.0.1', 0)
+ server:listen(127, function(err)
+ assert(not err, err)
+ local socket = uv.new_tcp()
+ server:accept(socket)
+ socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body)
+ local payload = vim.json.decode(body)
+ if payload.method then
+ table.insert(messages, payload.method)
+ if payload.method == 'initialize' then
+ local msg = vim.json.encode({
+ id = payload.id,
+ jsonrpc = '2.0',
+ result = {
+ capabilities = {}
+ },
+ })
+ socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg}))
+ elseif payload.method == 'initialized' then
+ local msg = vim.json.encode({
+ id = 10,
+ jsonrpc = '2.0',
+ method = 'dummy',
+ params = {},
+ })
+ socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg}))
+ end
+ else
+ table.insert(responses, payload)
+ socket:close()
+ end
+ end))
+ end)
+ local port = server:getsockname().port
+ local handler_called = false
+ vim.lsp.handlers['dummy'] = function(err, result)
+ handler_called = true
+ return false
+ end
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) })
+ local client = vim.lsp.get_client_by_id(client_id)
+ vim.wait(1000, function() return #messages == 2 and handler_called and #responses == 1 end)
+ server:close()
+ server:shutdown()
+ return {
+ messages = messages,
+ handler_called = handler_called,
+ responses = responses }
+ ]]
+ local expected = {
+ messages = { 'initialize', 'initialized' },
+ handler_called = true,
+ responses = {
+ {
+ id = 10,
+ jsonrpc = '2.0',
+ result = false
+ }
+ }
+ }
+ eq(expected, result)
+ end)
end)
end)