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.lua400
1 files changed, 399 insertions, 1 deletions
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index cd7415de90..425427be54 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -418,6 +418,43 @@ describe('LSP', function()
}
end)
+ it('should detach buffer on bufwipe', function()
+ clear()
+ local result = exec_lua([[
+ local server = function(dispatchers)
+ local closing = false
+ return {
+ request = function(method, params, callback)
+ if method == 'initialize' then
+ callback(nil, { capabilities = {} })
+ end
+ end,
+ notify = function(...)
+ end,
+ is_closing = function() return closing end,
+ terminate = function() closing = true end
+ }
+ end
+ 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 })
+ 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}};
@@ -1682,6 +1719,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 +1977,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')
@@ -2493,7 +2586,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 +2594,166 @@ 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 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 +3433,150 @@ describe('LSP', function()
end,
}
end)
+ it('format formats range in visual mode', function()
+ local result = exec_lua([[
+ local messages = {}
+ local server = function(dispatchers)
+ local closing = false
+ return {
+ request = function(method, params, callback)
+ table.insert(messages, {
+ method = method,
+ params = params,
+ })
+ if method == 'initialize' then
+ callback(nil, {
+ capabilities = {
+ documentFormattingProvider = true,
+ documentRangeFormattingProvider = true,
+ }
+ })
+ end
+ end,
+ notify = function(...)
+ end,
+ is_closing = function()
+ return closing
+ end,
+ terminate = function()
+ closing = true
+ end
+ }
+ end
+ local bufnr = vim.api.nvim_get_current_buf()
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server })
+ 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 messages
+ ]])
+ eq("textDocument/rangeFormatting", result[2].method)
+ local expected_range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 1, character = 4 },
+ }
+ eq(expected_range, result[2].params.range)
+ 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)