diff options
Diffstat (limited to 'test/functional/plugin')
-rw-r--r-- | test/functional/plugin/health_spec.lua | 12 | ||||
-rw-r--r-- | test/functional/plugin/lsp/completion_spec.lua | 54 | ||||
-rw-r--r-- | test/functional/plugin/lsp/diagnostic_spec.lua | 61 | ||||
-rw-r--r-- | test/functional/plugin/lsp/folding_range_spec.lua | 647 | ||||
-rw-r--r-- | test/functional/plugin/lsp/handler_spec.lua | 42 | ||||
-rw-r--r-- | test/functional/plugin/lsp/incremental_sync_spec.lua | 10 | ||||
-rw-r--r-- | test/functional/plugin/lsp/semantic_tokens_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/plugin/lsp/testutil.lua | 13 | ||||
-rw-r--r-- | test/functional/plugin/lsp/utils_spec.lua | 62 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 419 | ||||
-rw-r--r-- | test/functional/plugin/man_spec.lua | 26 |
11 files changed, 1169 insertions, 179 deletions
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 753da64522..406b5c3c16 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -66,6 +66,18 @@ describe(':checkhealth', function() eq({}, getcompletion('', 'checkhealth')) assert_alive() end) + + it('vim.g.health', function() + clear() + command("let g:health = {'style':'float'}") + command('checkhealth lsp') + eq( + 'editor', + exec_lua([[ + return vim.api.nvim_win_get_config(0).relative + ]]) + ) + end) end) describe('vim.health', function() diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 39b6ddc105..4e90c2fd1b 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -216,6 +216,43 @@ describe('vim.lsp.completion: item conversion', function() }) end) + it('uses filterText as word if label/newText would not match', function() + local items = { + { + filterText = '<module', + insertTextFormat = 2, + kind = 10, + label = 'module', + sortText = 'module', + textEdit = { + newText = '<module>$1</module>$0', + range = { + start = { + character = 0, + line = 0, + }, + ['end'] = { + character = 0, + line = 0, + }, + }, + }, + }, + } + assert_completion_matches('<mo', items, { + { + abbr = 'module', + word = '<module', + }, + }) + assert_completion_matches('', items, { + { + abbr = 'module', + word = 'module', + }, + }) + end) + it('fuzzy matches on label when filterText is missing', function() assert_completion_matches('fo', { { label = 'foo' }, @@ -731,9 +768,10 @@ describe('vim.lsp.completion: item conversion', function() ) end) +--- @param name string --- @param completion_result lsp.CompletionList --- @return integer -local function create_server(completion_result) +local function create_server(name, completion_result) return exec_lua(function() local server = _G._create_server({ capabilities = { @@ -751,7 +789,7 @@ local function create_server(completion_result) local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) return vim.lsp.start({ - name = 'dummy', + name = name, cmd = server.cmd, on_attach = function(client, bufnr0) vim.lsp.completion.enable(true, client.id, bufnr0, { @@ -800,7 +838,7 @@ describe('vim.lsp.completion: protocol', function() end it('fetches completions and shows them using complete on trigger', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -892,7 +930,7 @@ describe('vim.lsp.completion: protocol', function() end) it('merges results from multiple clients', function() - create_server({ + create_server('dummy1', { isIncomplete = false, items = { { @@ -900,7 +938,7 @@ describe('vim.lsp.completion: protocol', function() }, }, }) - create_server({ + create_server('dummy2', { isIncomplete = false, items = { { @@ -933,7 +971,7 @@ describe('vim.lsp.completion: protocol', function() }, }, } - local client_id = create_server(completion_list) + local client_id = create_server('dummy', completion_list) exec_lua(function() _G.called = false @@ -970,7 +1008,7 @@ describe('vim.lsp.completion: protocol', function() end) it('enable(…,{convert=fn}) custom word/abbr format', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -1012,7 +1050,7 @@ describe('vim.lsp.completion: integration', function() exec_lua(function() vim.o.completeopt = 'menuone,noselect' end) - create_server(completion_list) + create_server('dummy', completion_list) feed('i world<esc>0ih<c-x><c-o>') retry(nil, nil, function() eq( diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 5afbe22793..4ecb056d01 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -89,7 +89,7 @@ describe('vim.lsp.diagnostic', function() return extmarks end - client_id = assert(vim.lsp.start_client { + client_id = assert(vim.lsp.start({ cmd_env = { NVIM_LUA_NOTRACK = '1', }, @@ -101,7 +101,7 @@ describe('vim.lsp.diagnostic', function() '--headless', }, offset_encoding = 'utf-16', - }) + }, { attach = false })) end) fake_uri = 'file:///fake/uri' @@ -209,10 +209,16 @@ describe('vim.lsp.diagnostic', function() before_each(function() exec_lua(create_server_definition) exec_lua(function() + _G.requests = 0 _G.server = _G._create_server({ capabilities = { diagnosticProvider = {}, }, + handlers = { + [vim.lsp.protocol.Methods.textDocument_diagnostic] = function() + _G.requests = _G.requests + 1 + end, + }, }) function _G.get_extmarks(bufnr, client_id0) @@ -373,5 +379,56 @@ describe('vim.lsp.diagnostic', function() end) ) end) + + it('handles server cancellation', function() + eq( + 1, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + -- Empty data defaults to retriggering request + data = {}, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = true }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = false }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + end) end) end) diff --git a/test/functional/plugin/lsp/folding_range_spec.lua b/test/functional/plugin/lsp/folding_range_spec.lua new file mode 100644 index 0000000000..7e68a598d2 --- /dev/null +++ b/test/functional/plugin/lsp/folding_range_spec.lua @@ -0,0 +1,647 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') +local t_lsp = require('test.functional.plugin.lsp.testutil') + +local eq = t.eq +local tempname = t.tmpname + +local clear_notrace = t_lsp.clear_notrace +local create_server_definition = t_lsp.create_server_definition + +local api = n.api +local exec_lua = n.exec_lua +local insert = n.insert +local command = n.command +local feed = n.feed + +describe('vim.lsp.folding_range', function() + local text = [[// foldLevel() {{{2 +/// @return fold level at line number "lnum" in the current window. +static int foldLevel(linenr_T lnum) +{ + // While updating the folds lines between invalid_top and invalid_bot have + // an undefined fold level. Otherwise update the folds first. + if (invalid_top == 0) { + checkupdate(curwin); + } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { + return prev_lnum_lvl; + } else if (lnum >= invalid_top && lnum <= invalid_bot) { + return -1; + } + + // Return quickly when there is no folding at all in this window. + if (!hasAnyFolding(curwin)) { + return 0; + } + + return foldLevelWin(curwin, lnum); +}]] + + local result = { + { + endLine = 19, + kind = 'region', + startCharacter = 1, + startLine = 3, + }, + { + endCharacter = 2, + endLine = 7, + kind = 'region', + startCharacter = 25, + startLine = 6, + }, + { + endCharacter = 2, + endLine = 9, + kind = 'region', + startCharacter = 55, + startLine = 8, + }, + { + endCharacter = 2, + endLine = 11, + kind = 'region', + startCharacter = 58, + startLine = 10, + }, + { + endCharacter = 2, + endLine = 16, + kind = 'region', + startCharacter = 31, + startLine = 15, + }, + { + endCharacter = 68, + endLine = 1, + kind = 'comment', + startCharacter = 2, + startLine = 0, + }, + { + endCharacter = 64, + endLine = 5, + kind = 'comment', + startCharacter = 4, + startLine = 4, + }, + } + + local bufnr ---@type integer + local client_id ---@type integer + + clear_notrace() + before_each(function() + clear_notrace() + + exec_lua(create_server_definition) + bufnr = n.api.nvim_get_current_buf() + client_id = exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + foldingRangeProvider = true, + }, + handlers = { + ['textDocument/foldingRange'] = function(_, _, callback) + callback(nil, result) + end, + }, + }) + + vim.api.nvim_win_set_buf(0, bufnr) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + command('set foldmethod=expr foldcolumn=1 foldlevel=999') + insert(text) + end) + after_each(function() + api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) + end) + + describe('setup()', function() + ---@type integer + local bufnr_set_expr + ---@type integer + local bufnr_never_set_expr + + local function buf_autocmd_num(bufnr_to_check) + return exec_lua(function() + return #vim.api.nvim_get_autocmds({ buffer = bufnr_to_check, event = 'LspNotify' }) + end) + end + + before_each(function() + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_set_expr) + end) + insert(text) + command('write ' .. tempname(false)) + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_never_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_never_set_expr) + end) + insert(text) + api.nvim_win_set_buf(0, bufnr_set_expr) + end) + + it('only create event hooks where foldexpr has been set', function() + eq(1, buf_autocmd_num(bufnr)) + eq(1, buf_autocmd_num(bufnr_set_expr)) + eq(0, buf_autocmd_num(bufnr_never_set_expr)) + end) + + it('does not create duplicate event hooks after reloaded', function() + command('edit') + eq(1, buf_autocmd_num(bufnr_set_expr)) + end) + + it('cleans up event hooks when buffer is unloaded', function() + command('bdelete') + eq(0, buf_autocmd_num(bufnr_set_expr)) + end) + end) + + describe('expr()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 45) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { bold = true, foreground = Screen.colors.Blue1 }, + [3] = { bold = true, reverse = true }, + [4] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + command([[split]]) + end) + + it('can compute fold levels', function() + ---@type table<integer, string> + local foldlevels = {} + for i = 1, 21 do + foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')') + end + eq({ + [1] = '>1', + [2] = '<1', + [3] = '0', + [4] = '>1', + [5] = '>2', + [6] = '<2', + [7] = '>2', + [8] = '<2', + [9] = '>2', + [10] = '<2', + [11] = '>2', + [12] = '<2', + [13] = '1', + [14] = '1', + [15] = '1', + [16] = '>2', + [17] = '<2', + [18] = '1', + [19] = '1', + [20] = '<1', + [21] = '0', + }, foldlevels) + end) + + it('updates folds in all windows', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('persists wherever foldexpr is set', function() + command([[setlocal foldexpr=]]) + feed('<C-w><C-w>zx') + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| + | + ]], + }) + end) + + it('synchronizes changed rows with their previous foldlevels', function() + command('1,2d') + screen:expect({ + grid = [[ +{1: }^static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{3:[No Name] [+] }| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{4:[No Name] [+] }| + | +]], + }) + end) + + it('clears folds when sole client detaches', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('remains valid after the client re-attaches.', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + vim.lsp.buf_attach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + end) + + describe('foldtext()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command( + [[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]] + ) + end) + + it('shows the first folded line if `collapsedText` does not exist', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2: // While updating the folds lines between invalid_top and invalid_bot have···}| +{1:+}{2: if (invalid_top == 0) {······················································}| +{1:+}{2: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}| +{1:+}{2: } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2: if (!hasAnyFolding(curwin)) {················································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*6 + | + ]], + }) + end) + end) + + describe('foldclose()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + end) + + it('closes all folds of one kind immediately', function() + exec_lua(function() + vim.lsp.foldclose('comment') + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + + it('closes the smallest fold first', function() + exec_lua(function() + vim.lsp.foldclose('region') + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:+}{2:+-- 17 lines: {································································}| +{1: }^} | +{3:~ }|*17 + | + ]], + }) + command('4foldopen') + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:+}{2:+--- 2 lines: if (invalid_top == 0) {·········································}| +{1:+}{2:+--- 2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}| +{1:+}{2:+--- 2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2:+--- 2 lines: if (!hasAnyFolding(curwin)) {···································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*5 + | + ]], + }) + end) + + it('is defered when the buffer is not up-to-date', function() + exec_lua(function() + vim.lsp.foldclose('comment') + vim.lsp.util.buf_versions[bufnr] = 0 + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + end) +end) diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua deleted file mode 100644 index 4b05b676a8..0000000000 --- a/test/functional/plugin/lsp/handler_spec.lua +++ /dev/null @@ -1,42 +0,0 @@ -local t = require('test.testutil') -local n = require('test.functional.testnvim')() - -local eq = t.eq -local exec_lua = n.exec_lua -local pcall_err = t.pcall_err -local matches = t.matches - -describe('lsp-handlers', function() - describe('vim.lsp._with_extend', function() - it('should return a table with the default keys', function() - eq( - { hello = 'world' }, - exec_lua(function() - return vim.lsp._with_extend('test', { hello = 'world' }) - end) - ) - end) - - it('should override with config keys', function() - eq( - { hello = 'universe', other = true }, - exec_lua(function() - return vim.lsp._with_extend( - 'test', - { other = true, hello = 'world' }, - { hello = 'universe' } - ) - end) - ) - end) - - it('should not allow invalid keys', function() - matches( - '.*Invalid option for `test`.*', - pcall_err(exec_lua, function() - return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true }) - end) - ) - end) - end) -end) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index f60e159d64..0bca1fa4ca 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -23,7 +23,7 @@ before_each(function() -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] --- @diagnostic disable-next-line:duplicate-set-field - function _G.test_register(bufnr, id, offset_encoding, line_ending) + function _G.test_register(bufnr, id, position_encoding, line_ending) local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline) @@ -38,7 +38,7 @@ before_each(function() firstline, lastline, new_lastline, - offset_encoding, + position_encoding, line_ending ) @@ -63,15 +63,15 @@ local function test_edit( prev_buffer, edit_operations, expected_text_changes, - offset_encoding, + position_encoding, line_ending ) - offset_encoding = offset_encoding or 'utf-16' + position_encoding = position_encoding or 'utf-16' line_ending = line_ending or '\n' api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer) exec_lua(function() - return _G.test_register(0, 'test1', offset_encoding, line_ending) + return _G.test_register(0, 'test1', position_encoding, line_ending) end) for _, edit in ipairs(edit_operations) do diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 280bd27207..9912bf2063 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -456,7 +456,7 @@ describe('semantic token highlighting', function() vim.notify = function(...) table.insert(_G.notifications, 1, { ... }) end - return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd }) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }, { attach = false }) end) eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index a36cbac568..95fc22b96b 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -182,16 +182,17 @@ function M.test_rpc_server(config) ) end local client = setmetatable({}, { - __index = function(_, name) + __index = function(t, 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 function(arg1, ...) + local ismethod = arg1 == t return exec_lua(function(...) - if type(_G.TEST_RPC_CLIENT[name]) == 'function' then - return _G.TEST_RPC_CLIENT[name](...) - else - return _G.TEST_RPC_CLIENT[name] + local client = _G.TEST_RPC_CLIENT + if type(client[name]) == 'function' then + return client[name](ismethod and client or arg1, ...) end + return client[name] end, ...) end end, diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 813b8de812..1e3e759e0b 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -5,6 +5,8 @@ local Screen = require('test.functional.ui.screen') local feed = n.feed local eq = t.eq local exec_lua = n.exec_lua +local command, api = n.command, n.api +local pcall_err = t.pcall_err describe('vim.lsp.util', function() before_each(n.clear) @@ -265,6 +267,66 @@ describe('vim.lsp.util', function() eq(56, opts.height) end) + + describe('vim.lsp.util.open_floating_preview', function() + local var_name = 'lsp_floating_preview' + local curbuf = api.nvim_get_current_buf() + + it('clean bufvar after fclose', function() + exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + end) + eq(true, api.nvim_win_is_valid(api.nvim_buf_get_var(curbuf, var_name))) + command('fclose') + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + + it('clean bufvar after CursorMoved', function() + local result = exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + local winnr = vim.b[vim.api.nvim_get_current_buf()].lsp_floating_preview + local result = vim.api.nvim_win_is_valid(winnr) + vim.api.nvim_feedkeys(vim.keycode('G'), 'txn', false) + return result + end) + eq(true, result) + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + end) + end) + end) + + it('open_floating_preview zindex greater than current window', function() + local screen = Screen.new() + exec_lua(function() + vim.api.nvim_open_win(0, true, { + relative = 'editor', + border = 'single', + height = 11, + width = 51, + row = 2, + col = 2, + }) + vim.keymap.set('n', 'K', function() + vim.lsp.util.open_floating_preview({ 'foo' }, '', { border = 'single' }) + end, {}) end) + feed('K') + screen:expect([[ + ┌───────────────────────────────────────────────────┐| + │{4:^ }│| + │┌───┐{11: }│| + ││{4:foo}│{11: }│| + │└───┘{11: }│| + │{11:~ }│|*7 + └───────────────────────────────────────────────────┘| + | + ]]) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5222216faf..db3ab8ed98 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -95,7 +95,7 @@ describe('LSP', function() exec_lua(function() _G.lsp = require('vim.lsp') function _G.test__start_client() - return vim.lsp.start_client { + return vim.lsp.start({ cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile, NVIM_APPNAME = 'nvim_lsp_test', @@ -112,7 +112,7 @@ describe('LSP', function() name = 'test_folder', }, }, - } + }, { attach = false }) end _G.TEST_CLIENT1 = _G.test__start_client() end) @@ -232,7 +232,7 @@ describe('LSP', function() -- client is a dummy object which will queue up commands to be run -- once the server initializes. It can't accept lua callbacks or -- other types that may be unserializable for now. - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -254,8 +254,8 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client) - client.notify('test') - client.stop() + client:notify('test') + client:stop() end, on_exit = function(code, signal) eq(101, code, 'exit code') -- See fake-lsp-server.lua @@ -275,7 +275,7 @@ describe('LSP', function() test_rpc_server({ test_name = 'basic_init_did_change_configuration', on_init = function(client, _) - client.stop() + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -333,9 +333,9 @@ describe('LSP', function() test_name = 'basic_init', on_init = function(client) eq(0, client.server_capabilities().textDocumentSync.change) - client.request('shutdown') - client.notify('exit') - client.stop() + client:request('shutdown') + client:notify('exit') + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -377,7 +377,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -395,7 +395,7 @@ describe('LSP', function() return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.stop() + client:stop() end end, } @@ -430,7 +430,7 @@ describe('LSP', function() return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.notify('finish') + client:notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then @@ -439,7 +439,7 @@ describe('LSP', function() return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) eq('basic_init', api.nvim_get_var('lsp_detached')) - client.stop() + client:stop() end end, } @@ -466,13 +466,20 @@ describe('LSP', function() true, exec_lua(function() local keymap --- @type table<string,any> + local called = false + local origin = vim.lsp.buf.hover + vim.lsp.buf.hover = function() + called = true + end vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, true) end) - return keymap.callback == vim.lsp.buf.hover + keymap.callback() + vim.lsp.buf.hover = origin + return called end) ) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -480,13 +487,13 @@ describe('LSP', function() eq('', get_buf_option('omnifunc')) eq('', get_buf_option('formatexpr')) eq( - '', + true, exec_lua(function() local keymap --- @type string vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, false) end) - return keymap + return keymap:match('<Lua %d+: .+/runtime/lua/vim/lsp%.lua:%d+>') ~= nil end) ) end, @@ -524,7 +531,7 @@ describe('LSP', function() eq('v:lua.vim.lsp.tagfunc', get_buf_option('tagfunc', BUFFER_1)) eq('v:lua.vim.lsp.omnifunc', get_buf_option('omnifunc', BUFFER_2)) eq('v:lua.vim.lsp.formatexpr()', get_buf_option('formatexpr', BUFFER_2)) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -554,7 +561,7 @@ describe('LSP', function() eq('tfu', get_buf_option('tagfunc')) eq('ofu', get_buf_option('omnifunc')) eq('fex', get_buf_option('formatexpr')) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -711,10 +718,10 @@ describe('LSP', function() ctx.method, result ) - client.notify('workspace/configuration', server_result) + client:notify('workspace/configuration', server_result) end if ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -756,7 +763,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_capabilities', on_init = function(client) - client.stop() + client:stop() local full_kind = exec_lua(function() return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full end) @@ -798,7 +805,7 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } @@ -898,7 +905,7 @@ describe('LSP', function() end) end) else - client.stop() + client:stop() end end, }) @@ -929,20 +936,20 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } end) - it('client.supports_methods() should validate capabilities', function() + it('client:supports_methods() should validate capabilities', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, } test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_init = function(client) - client.stop() + client:stop() local expected_sync_capabilities = { change = 1, openClose = true, @@ -958,11 +965,11 @@ describe('LSP', function() eq(true, client.server_capabilities().codeLensProvider.resolveProvider) -- known methods for resolved capabilities - eq(true, client.supports_method('textDocument/hover')) - eq(false, client.supports_method('textDocument/definition')) + eq(true, client:supports_method('textDocument/hover')) + eq(false, client:supports_method('textDocument/definition')) -- unknown methods are assumed to be supported. - eq(true, client.supports_method('unknown-method')) + eq(true, client:supports_method('unknown-method')) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -989,7 +996,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1018,7 +1025,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1042,7 +1049,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_request_cancelled', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1053,7 +1060,40 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() + end + end, + } + end) + + it('should forward ServerCancelled to callback', function() + local expected_handlers = { + { NIL, {}, { method = 'finish', client_id = 1 } }, + { + { code = -32802 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'check_forward_server_cancelled', + on_init = function(_client) + _client:request('error_code_test') + client = _client + end, + on_exit = function(code, signal) + eq(0, code, 'exit code') + eq(0, signal, 'exit signal') + eq(0, #expected_handlers, 'did not call expected handler') + end, + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') + if ctx.method ~= 'finish' then + client:notify('finish') + end + if ctx.method == 'finish' then + client:stop() end end, } @@ -1072,7 +1112,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_content_modified', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1082,12 +1122,11 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') - -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1103,13 +1142,13 @@ describe('LSP', function() test_name = 'check_pending_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1123,10 +1162,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1141,14 +1180,14 @@ describe('LSP', function() test_name = 'check_cancel_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') - client.cancel_request(2) + client:request('slow_request') + client:cancel_request(2) local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1162,7 +1201,7 @@ describe('LSP', function() end) eq(nil, request) if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1178,19 +1217,19 @@ describe('LSP', function() test_name = 'check_tracked_requests_cleared', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.cancel_request(2) + client:cancel_request(2) request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1204,10 +1243,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1225,11 +1264,11 @@ describe('LSP', function() command('let g:requests = 0') command('autocmd LspRequest * let g:requests+=1') client = _client - client.request('slow_request') + client:request('slow_request') eq(1, eval('g:requests')) - client.cancel_request(2) + client:cancel_request(2) eq(2, eval('g:requests')) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1240,10 +1279,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1277,7 +1316,7 @@ describe('LSP', function() end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1286,7 +1325,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1333,11 +1372,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1378,11 +1417,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1428,11 +1467,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1479,11 +1518,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1529,7 +1568,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - eq(true, client.supports_method('textDocument/inlayHint')) + eq(true, client:supports_method('textDocument/inlayHint')) exec_lua(function() assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) end) @@ -1545,11 +1584,11 @@ describe('LSP', function() end) end if ctx.method == 'textDocument/inlayHint' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1598,11 +1637,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1652,11 +1691,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1699,11 +1738,11 @@ describe('LSP', function() on_handler = function(err, result, ctx) if ctx.method == 'start' then n.command('normal! 1Go') - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1752,11 +1791,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1806,15 +1845,29 @@ describe('LSP', function() }) vim.api.nvim_command(_G.BUFFER .. 'bwipeout') end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } end) + + it('vim.lsp.start when existing client has no workspace_folders', function() + exec_lua(create_server_definition) + eq( + { 2, 'foo', 'foo' }, + exec_lua(function() + local server = _G._create_server() + vim.lsp.start { cmd = server.cmd, name = 'foo' } + vim.lsp.start { cmd = server.cmd, name = 'foo', root_dir = 'bar' } + local foos = vim.lsp.get_clients() + return { #foos, foos[1].name, foos[2].name } + end) + ) + end) end) describe('parsing tests', function() @@ -1830,7 +1883,7 @@ describe('LSP', function() on_setup = function() end, on_init = function(_client) client = _client - client.stop(true) + client:stop(true) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1882,7 +1935,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -2348,7 +2401,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client, _) - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -3448,6 +3501,19 @@ describe('LSP', function() end) ) end) + it('handles empty line', function() + exec_lua(function() + _G.contents = { + '', + } + end) + eq( + { 20, 1 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents, { width = 20 }) } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() @@ -3499,7 +3565,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'zig', { '(' }) end) - -- Note that although the higlight positions below are 0-indexed, the 2nd parameter + -- Note that although the highlight positions below are 0-indexed, the 2nd parameter -- corresponds to the 3rd line because the first line is the ``` from the -- Markdown block. local expected = { 3, 4, 3, 11 } @@ -4284,7 +4350,7 @@ describe('LSP', function() end) ) end - client.stop() + client:stop() end end, } @@ -4331,7 +4397,7 @@ describe('LSP', function() return type(vim.lsp.commands['dummy2']) end) ) - client.stop() + client:stop() end end, } @@ -4372,7 +4438,7 @@ describe('LSP', function() vim.lsp.buf.code_action() end) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, }) @@ -4447,7 +4513,7 @@ describe('LSP', function() return type(vim.lsp.commands['executed_type_annotate']) end) ) - client.stop() + client:stop() end end, } @@ -4564,7 +4630,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4653,7 +4719,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4762,7 +4828,7 @@ describe('LSP', function() return notify_msg end) eq('[LSP] Format request failed, no matching language servers.', notify_msg) - client.stop() + client:stop() end, } end) @@ -4795,7 +4861,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4836,7 +4902,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4883,7 +4949,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4930,7 +4996,7 @@ describe('LSP', function() end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -5145,8 +5211,8 @@ describe('LSP', function() local win = vim.api.nvim_get_current_win() vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' }) vim.api.nvim_win_set_cursor(win, { 3, 6 }) - local client_id1 = assert(vim.lsp.start({ name = 'dummy', cmd = server1.cmd })) - local client_id2 = assert(vim.lsp.start({ name = 'dummy', cmd = server2.cmd })) + local client_id1 = assert(vim.lsp.start({ name = 'dummy1', cmd = server1.cmd })) + local client_id2 = assert(vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })) local response vim.lsp.buf.definition({ on_list = function(r) @@ -5477,7 +5543,7 @@ describe('LSP', function() result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, { bufnr = bufnr }), + supported = client:supports_method(method, { bufnr = bufnr }), } end @@ -6059,15 +6125,6 @@ describe('LSP', function() end eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities(). - eq(false, check_registered(vim.empty_dict())) - eq( - false, - check_registered({ - workspace = { - ignoreMe = true, - }, - }) - ) eq( false, check_registered({ @@ -6090,4 +6147,154 @@ describe('LSP', function() ) end) end) + + describe('vim.lsp.config() and vim.lsp.enable()', function() + it('can merge settings from "*"', function() + eq( + { + name = 'foo', + cmd = { 'foo' }, + root_markers = { '.git' }, + }, + exec_lua(function() + vim.lsp.config('*', { root_markers = { '.git' } }) + vim.lsp.config('foo', { cmd = { 'foo' } }) + + return vim.lsp.config['foo'] + end) + ) + end) + + it('sets up an autocmd', function() + eq( + 1, + exec_lua(function() + vim.lsp.config('foo', { + cmd = { 'foo' }, + root_markers = { '.foorc' }, + }) + vim.lsp.enable('foo') + return #vim.api.nvim_get_autocmds({ + group = 'nvim.lsp.enable', + event = 'FileType', + }) + end) + ) + end) + + it('attaches to buffers', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + local tmp2 = t.tmpname(true) + + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.config('bar', { + cmd = server.cmd, + filetypes = { 'bar' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.enable('foo') + vim.lsp.enable('bar') + + vim.cmd.edit(tmp1) + vim.bo.filetype = 'foo' + _G.foo_buf = vim.api.nvim_get_current_buf() + + vim.cmd.edit(tmp2) + vim.bo.filetype = 'bar' + _G.bar_buf = vim.api.nvim_get_current_buf() + end) + + eq( + { 1, 'foo', 1, 'bar' }, + exec_lua(function() + local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) }) + local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) }) + return { #foos, foos[1].name, #bars, bars[1].name } + end) + ) + end) + + it('does not attach to buffers more than once if no root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + 1, + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { cmd = server.cmd, filetypes = { 'foo' } }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + vim.bo.filetype = 'foo' + + return #vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() }) + end) + ) + end) + + it('supports async function for root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_dir = function(cb) + vim.system({ 'sleep', '0' }, {}, function() + cb('some_dir') + end) + end, + }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + end) + + retry(nil, 1000, function() + eq( + 'some_dir', + exec_lua(function() + return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir + end) + ) + end) + end) + end) end) diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 8906e60dce..c1dbc6dac3 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -21,13 +21,12 @@ local function get_search_history(name) local man = require('man') local res = {} --- @diagnostic disable-next-line:duplicate-set-field - man.find_path = function(sect, name0) + man._find_path = function(name0, sect) table.insert(res, { sect, name0 }) return nil end - local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args) - assert(not ok) - assert(rv and rv:match('no manual entry')) + local err = man.open_page(-1, { tab = 0 }, args) + assert(err and err:match('no manual entry')) return res end) end @@ -225,7 +224,7 @@ describe(':Man', function() matches('^/.+', actual_file) local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' } matches( - ('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format( + ('Error detected while processing command line:\r\n' .. 'man.lua: no manual entry for %s'):format( pesc(actual_file) ), fn.system(args, { '' }) @@ -235,8 +234,8 @@ describe(':Man', function() it('tries variants with spaces, underscores #22503', function() eq({ - { '', 'NAME WITH SPACES' }, - { '', 'NAME_WITH_SPACES' }, + { vim.NIL, 'NAME WITH SPACES' }, + { vim.NIL, 'NAME_WITH_SPACES' }, }, get_search_history('NAME WITH SPACES')) eq({ { '3', 'some other man' }, @@ -255,12 +254,21 @@ describe(':Man', function() { 'n', 'some_other_man' }, }, get_search_history('n some other man')) eq({ - { '', '123some other man' }, - { '', '123some_other_man' }, + { vim.NIL, '123some other man' }, + { vim.NIL, '123some_other_man' }, }, get_search_history('123some other man')) eq({ { '1', 'other_man' }, { '1', 'other_man' }, }, get_search_history('other_man(1)')) end) + + it('can complete', function() + eq( + true, + exec_lua(function() + return #require('man').man_complete('f', 'Man f') > 0 + end) + ) + end) end) |