mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	test(lsp): add completion tests
This commit is contained in:
		| @@ -1,9 +1,16 @@ | |||||||
| ---@diagnostic disable: no-unknown | ---@diagnostic disable: no-unknown | ||||||
| local t = require('test.testutil') | local t = require('test.testutil') | ||||||
|  | local t_lsp = require('test.functional.plugin.lsp.testutil') | ||||||
| local n = require('test.functional.testnvim')() | local n = require('test.functional.testnvim')() | ||||||
|  |  | ||||||
|  | local clear = n.clear | ||||||
| local eq = t.eq | local eq = t.eq | ||||||
|  | local neq = t.neq | ||||||
| local exec_lua = n.exec_lua | local exec_lua = n.exec_lua | ||||||
|  | local feed = n.feed | ||||||
|  | local retry = t.retry | ||||||
|  |  | ||||||
|  | local create_server_definition = t_lsp.create_server_definition | ||||||
|  |  | ||||||
| --- Convert completion results. | --- Convert completion results. | ||||||
| --- | --- | ||||||
| @@ -25,6 +32,7 @@ local function complete(line, candidates, lnum) | |||||||
|       line, |       line, | ||||||
|       lnum, |       lnum, | ||||||
|       cursor_col, |       cursor_col, | ||||||
|  |       1, | ||||||
|       client_start_boundary, |       client_start_boundary, | ||||||
|       nil, |       nil, | ||||||
|       result, |       result, | ||||||
| @@ -42,7 +50,7 @@ local function complete(line, candidates, lnum) | |||||||
|   ) |   ) | ||||||
| end | end | ||||||
|  |  | ||||||
| describe('vim.lsp.completion', function() | describe('vim.lsp.completion: item conversion', function() | ||||||
|   before_each(n.clear) |   before_each(n.clear) | ||||||
|  |  | ||||||
|   -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion |   -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion | ||||||
| @@ -187,6 +195,7 @@ describe('vim.lsp.completion', function() | |||||||
|       dup = 1, |       dup = 1, | ||||||
|       empty = 1, |       empty = 1, | ||||||
|       icase = 1, |       icase = 1, | ||||||
|  |       info = '', | ||||||
|       kind = 'Module', |       kind = 'Module', | ||||||
|       menu = '', |       menu = '', | ||||||
|       word = 'this_thread', |       word = 'this_thread', | ||||||
| @@ -241,6 +250,7 @@ describe('vim.lsp.completion', function() | |||||||
|       dup = 1, |       dup = 1, | ||||||
|       empty = 1, |       empty = 1, | ||||||
|       icase = 1, |       icase = 1, | ||||||
|  |       info = '', | ||||||
|       kind = 'Module', |       kind = 'Module', | ||||||
|       menu = '', |       menu = '', | ||||||
|       word = 'this_thread', |       word = 'this_thread', | ||||||
| @@ -279,4 +289,224 @@ describe('vim.lsp.completion', function() | |||||||
|     eq('item-property-has-priority', item.data) |     eq('item-property-has-priority', item.data) | ||||||
|     eq({ line = 1, character = 1 }, item.textEdit.range.start) |     eq({ line = 1, character = 1 }, item.textEdit.range.start) | ||||||
|   end) |   end) | ||||||
|  |  | ||||||
|  |   it( | ||||||
|  |     'uses insertText as textEdit.newText if there are editRange defaults but no textEditText', | ||||||
|  |     function() | ||||||
|  |       --- @type lsp.CompletionList | ||||||
|  |       local completion_list = { | ||||||
|  |         isIncomplete = false, | ||||||
|  |         itemDefaults = { | ||||||
|  |           editRange = { | ||||||
|  |             start = { line = 1, character = 1 }, | ||||||
|  |             ['end'] = { line = 1, character = 4 }, | ||||||
|  |           }, | ||||||
|  |           insertTextFormat = 2, | ||||||
|  |           data = 'foobar', | ||||||
|  |         }, | ||||||
|  |         items = { | ||||||
|  |           { | ||||||
|  |             insertText = 'the-insertText', | ||||||
|  |             label = 'hello', | ||||||
|  |             data = 'item-property-has-priority', | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |       local result = complete('|', completion_list) | ||||||
|  |       eq(1, #result.items) | ||||||
|  |       local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText | ||||||
|  |       eq('the-insertText', text) | ||||||
|  |     end | ||||||
|  |   ) | ||||||
|  | end) | ||||||
|  |  | ||||||
|  | describe('vim.lsp.completion: protocol', function() | ||||||
|  |   before_each(function() | ||||||
|  |     clear() | ||||||
|  |     exec_lua(create_server_definition) | ||||||
|  |     exec_lua([[ | ||||||
|  |       _G.capture = {} | ||||||
|  |       vim.fn.complete = function(col, matches) | ||||||
|  |         _G.capture.col = col | ||||||
|  |         _G.capture.matches = matches | ||||||
|  |       end | ||||||
|  |     ]]) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   after_each(clear) | ||||||
|  |  | ||||||
|  |   --- @param completion_result lsp.CompletionList | ||||||
|  |   --- @return integer | ||||||
|  |   local function create_server(completion_result) | ||||||
|  |     return exec_lua( | ||||||
|  |       [[ | ||||||
|  |       local result = ... | ||||||
|  |       local server = _create_server({ | ||||||
|  |         capabilities = { | ||||||
|  |           completionProvider = { | ||||||
|  |             triggerCharacters = { '.' } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         handlers = { | ||||||
|  |           ['textDocument/completion'] = function(_, _, callback) | ||||||
|  |             callback(nil, result) | ||||||
|  |           end | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       bufnr = vim.api.nvim_get_current_buf() | ||||||
|  |       vim.api.nvim_win_set_buf(0, bufnr) | ||||||
|  |       return vim.lsp.start({ name = 'dummy', cmd = server.cmd, on_attach = function(client, bufnr) | ||||||
|  |         vim.lsp.completion.enable(true, client.id, bufnr) | ||||||
|  |       end}) | ||||||
|  |     ]], | ||||||
|  |       completion_result | ||||||
|  |     ) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   local function assert_matches(fn) | ||||||
|  |     retry(nil, nil, function() | ||||||
|  |       fn(exec_lua('return _G.capture.matches')) | ||||||
|  |     end) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   --- @param pos { [1]: integer, [2]: integer } | ||||||
|  |   local function trigger_at_pos(pos) | ||||||
|  |     exec_lua( | ||||||
|  |       [[ | ||||||
|  |       local win = vim.api.nvim_get_current_win() | ||||||
|  |       vim.api.nvim_win_set_cursor(win, ...) | ||||||
|  |       vim.lsp.completion.trigger() | ||||||
|  |     ]], | ||||||
|  |       pos | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     retry(nil, nil, function() | ||||||
|  |       neq(nil, exec_lua('return _G.capture.col')) | ||||||
|  |     end) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   it('fetches completions and shows them using complete on trigger', function() | ||||||
|  |     create_server({ | ||||||
|  |       isIncomplete = false, | ||||||
|  |       items = { | ||||||
|  |         { | ||||||
|  |           label = 'hello', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     feed('ih') | ||||||
|  |     trigger_at_pos({ 1, 1 }) | ||||||
|  |  | ||||||
|  |     assert_matches(function(matches) | ||||||
|  |       eq({ | ||||||
|  |         { | ||||||
|  |           abbr = 'hello', | ||||||
|  |           dup = 1, | ||||||
|  |           empty = 1, | ||||||
|  |           icase = 1, | ||||||
|  |           info = '', | ||||||
|  |           kind = 'Unknown', | ||||||
|  |           menu = '', | ||||||
|  |           user_data = { | ||||||
|  |             nvim = { | ||||||
|  |               lsp = { | ||||||
|  |                 client_id = 1, | ||||||
|  |                 completion_item = { | ||||||
|  |                   label = 'hello', | ||||||
|  |                 }, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           word = 'hello', | ||||||
|  |         }, | ||||||
|  |       }, matches) | ||||||
|  |     end) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('merges results from multiple clients', function() | ||||||
|  |     create_server({ | ||||||
|  |       isIncomplete = false, | ||||||
|  |       items = { | ||||||
|  |         { | ||||||
|  |           label = 'hello', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |     create_server({ | ||||||
|  |       isIncomplete = false, | ||||||
|  |       items = { | ||||||
|  |         { | ||||||
|  |           label = 'hallo', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     feed('ih') | ||||||
|  |     trigger_at_pos({ 1, 1 }) | ||||||
|  |  | ||||||
|  |     assert_matches(function(matches) | ||||||
|  |       eq(2, #matches) | ||||||
|  |       eq('hello', matches[1].word) | ||||||
|  |       eq('hallo', matches[2].word) | ||||||
|  |     end) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   it('executes commands', function() | ||||||
|  |     local completion_list = { | ||||||
|  |       isIncomplete = false, | ||||||
|  |       items = { | ||||||
|  |         { | ||||||
|  |           label = 'hello', | ||||||
|  |           command = { | ||||||
|  |             arguments = { '1', '0' }, | ||||||
|  |             command = 'dummy', | ||||||
|  |             title = '', | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |     local client_id = create_server(completion_list) | ||||||
|  |  | ||||||
|  |     exec_lua( | ||||||
|  |       [[ | ||||||
|  |       _G.called = false | ||||||
|  |       local client = vim.lsp.get_client_by_id(...) | ||||||
|  |       client.commands.dummy = function () | ||||||
|  |         _G.called = true | ||||||
|  |       end | ||||||
|  |     ]], | ||||||
|  |       client_id | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     feed('ih') | ||||||
|  |     trigger_at_pos({ 1, 1 }) | ||||||
|  |  | ||||||
|  |     exec_lua( | ||||||
|  |       [[ | ||||||
|  |       local client_id, item = ... | ||||||
|  |       vim.v.completed_item = { | ||||||
|  |         user_data = { | ||||||
|  |           nvim = { | ||||||
|  |             lsp = { | ||||||
|  |               client_id = client_id, | ||||||
|  |               completion_item = item | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ]], | ||||||
|  |       client_id, | ||||||
|  |       completion_list.items[1] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     feed('<C-x><C-o><C-y>') | ||||||
|  |  | ||||||
|  |     assert_matches(function(matches) | ||||||
|  |       eq(1, #matches) | ||||||
|  |       eq('hello', matches[1].word) | ||||||
|  |       eq(true, exec_lua('return _G.called')) | ||||||
|  |     end) | ||||||
|  |   end) | ||||||
| end) | end) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Maria José Solano
					Maria José Solano