From a84b7e555fe382c1859e6ca46b4983c822a1f77f Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Tue, 18 Feb 2025 17:14:00 +0200 Subject: [PATCH] fix(completion): make completion request directly where possible Details: - Do not force 'completefunc_lsp' call to make a completion LSP request. Instead make it explicitly and allow its callback to later trigger necessary keys. Among other things, this reduces flickering in case of `isIncomplete` update (as emulating 'completefunc_lsp' keys forces completion popup to immediately hide only to slightly later to reappear again). - Being able to still make an LSP request directly inside `completefunc_lsp` is crucial to make it work with explicit `...` and being able to retrigger completion at ``. --- lua/mini/completion.lua | 18 ++++- ...n.lua---Autocompletion---works-with-`-BS-` | 23 ++++++ ...a---Autocompletion---works-with-`-BS-`-002 | 23 ++++++ ...a---Autocompletion---works-with-`-BS-`-003 | 23 ++++++ tests/test_completion.lua | 79 ++++++++++++++++++- 5 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 tests/screenshots/tests-test_completion.lua---Autocompletion---works-with-`-BS-` create mode 100644 tests/screenshots/tests-test_completion.lua---Autocompletion---works-with-`-BS-`-002 create mode 100644 tests/screenshots/tests-test_completion.lua---Autocompletion---works-with-`-BS-`-003 diff --git a/lua/mini/completion.lua b/lua/mini/completion.lua index 6e92f558..cf7429ad 100644 --- a/lua/mini/completion.lua +++ b/lua/mini/completion.lua @@ -367,7 +367,7 @@ MiniCompletion.completefunc_lsp = function(findstart, base) end -- NOTE: having code for request inside this function enables its use - -- directly with `<...>`. + -- directly with `<...>` and as a reaction to ``. if H.completion.lsp.status ~= 'received' then -- NOTE: it is CRUCIAL to make LSP request on the first call to -- 'complete-function' (as in Vim's help). This is due to the fact that @@ -656,6 +656,11 @@ H.auto_signature = function() end H.on_completedonepre = function() + -- Do nothing if it is triggered inside `trigger_lsp()` as a result of + -- emulating 'completefunc'/'omnifunc' keys. This can happen if popup is + -- visible and pressing keys first hides it with 'CompleteDonePre' event. + if H.completion.lsp.status == 'received' then return end + -- Try to apply additional text edits H.apply_additional_text_edits() @@ -713,6 +718,15 @@ H.trigger_lsp = function() -- Do not trigger if not needed and/or allowed if vim.fn.mode() ~= 'i' or (H.pumvisible() and not H.completion.force) then return end + -- Overall idea: first make LSP request and re-trigger this same function + -- inside its callback to take the "received" route. This reduces flickering + -- in case popup is visible (like for `isIncomplete` and trigger characters) + -- as pressing 'completefunc'/'omnifunc' keys first hides completion menu. + -- There are still minor visual defects: typing new character reduces number + -- of matched items which can visually shrink popup while later increase it + -- again after LSP response is received. This is usually fine (especially + -- with not huge 'pumheight'). + if H.completion.lsp.status ~= 'received' then return H.make_completion_request() end local keys = H.keys[H.get_config().lsp_completion.source_func] vim.api.nvim_feedkeys(keys, 'n', false) end @@ -845,7 +859,7 @@ H.make_completion_request = function() H.completion.lsp.status = 'received' H.completion.lsp.result = result - -- Trigger LSP completion to use "received" route + -- Trigger LSP completion to use completefunc/omnifunc route H.trigger_lsp() end) diff --git a/tests/screenshots/tests-test_completion.lua---Autocompletion---works-with-`-BS-` b/tests/screenshots/tests-test_completion.lua---Autocompletion---works-with-`-BS-` new file mode 100644 index 00000000..c985b3d5 --- /dev/null +++ b/tests/screenshots/tests-test_completion.lua---Autocompletion---works-with-`-BS-` @@ -0,0 +1,23 @@ +--|---------|---------| +01|Jun +02|June Function +03|~ +04|~ +05|~ +06|~ +07|~ +08|~ +09|') eq(get_completion(), all_months) + -- - Should only call two times, as per `:h complete-functions`, i.e. not + -- use it to actually make request (which would have made it 3 times). + eq(child.lua_get('_G.n_completfunc_lsp'), 2) type_keys('May.') + eq(child.lua_get('_G.n_completfunc_lsp'), 2) eq(child.fn.pumvisible(), 0) sleep(default_completion_delay - small_time) eq(child.fn.pumvisible(), 0) sleep(small_time + small_time) eq(get_completion(), all_months) + eq(child.lua_get('_G.n_completfunc_lsp'), 4) child.expect_screenshot() -- Should show only LSP without fallback, i.e. typing LSP trigger should @@ -405,34 +421,74 @@ T['Autocompletion']['forces new LSP completion at LSP trigger'] = new_set( } ) +T['Autocompletion']['works with ``'] = function() + mock_completefunc_lsp_tracking() + child.set_size(10, 20) + child.api.nvim_set_current_buf(child.api.nvim_create_buf(true, false)) + + type_keys('i', 'J', 'u', '') + -- - Should only call two times, as per `:h complete-functions`: first to + -- find the start column, second to return completion suggestions. + eq(child.lua_get('_G.n_completfunc_lsp'), 2) + + type_keys('n') + child.expect_screenshot() + eq(child.lua_get('_G.n_completfunc_lsp'), 2) + + -- Should keep completion menu and adjust items without extra 'completefunc' + -- calls (as it is still at or after initial start column) + type_keys('') + child.expect_screenshot() + eq(child.lua_get('_G.n_completfunc_lsp'), 2) + + -- Should reevaluate completion list as it is past initial start column + type_keys('') + child.expect_screenshot() + -- - Should call three times: first to make a request, second and third to + -- act as a regular 'completefunc'/'omnifunc' + eq(child.lua_get('_G.n_completfunc_lsp'), 5) +end + T['Autocompletion']['forces new LSP completion in case of `isIncomplete`'] = function() + mock_completefunc_lsp_tracking() child.set_size(10, 20) child.api.nvim_set_current_buf(child.api.nvim_create_buf(true, false)) -- Mock incomplete completion list which contains only months 1-6 child.lua('_G.mock_isincomplete = true') type_keys('i', 'J', '') - -- Should not contain `July` as it is not in the response + -- - Should not contain `July` as it is not in the response child.expect_screenshot() eq(child.lua_get('_G.n_textdocument_completion'), 1) + -- - Should only call two times, as per `:h complete-functions`: first to + -- find the start column, second to return completion suggestions. + eq(child.lua_get('_G.n_completfunc_lsp'), 2) -- Should force new request which this time will be complete child.lua('_G.mock_isincomplete = false') type_keys('u') child.expect_screenshot() eq(child.lua_get('_G.n_textdocument_completion'), 2) + -- - NOTE: not using completefunc to make an LSP request is a key to reduce + -- flickering in this use case (as executing `...` forces popup hide). + eq(child.lua_get('_G.n_completfunc_lsp'), 4) - -- Should *not* force new requests for complete responses + -- Shouldn't force new requests or call 'completefunc' for complete responses type_keys('n') eq(child.lua_get('_G.n_textdocument_completion'), 2) + eq(child.lua_get('_G.n_completfunc_lsp'), 4) type_keys('') eq(child.lua_get('_G.n_textdocument_completion'), 2) + eq(child.lua_get('_G.n_completfunc_lsp'), 4) -- Should force new request if deleting past the start of previous request. -- This time response will be complete. type_keys('') child.expect_screenshot() eq(child.lua_get('_G.n_textdocument_completion'), 3) + -- - Should call three times: first to make a request, second and third to + -- act as a regular 'completefunc'/'omnifunc' + eq(child.lua_get('_G.n_completfunc_lsp'), 7) end T['Autocompletion']['respects `config.delay.completion`'] = function() @@ -565,6 +621,23 @@ T['Manual completion']['works with fallback action'] = function() eq(get_completion(), { 'Jackpot' }) end +T['Manual completion']['works with explicit `...`'] = new_set( + { parametrize = { { 'completefunc' }, { 'omnifunc' } } }, + { + test = function(source_func) + reload_module({ lsp_completion = { source_func = source_func } }) + mock_completefunc_lsp_tracking() + child.api.nvim_set_current_buf(child.api.nvim_create_buf(true, false)) + + local source_keys = '' .. (source_func == 'completefunc' and '' or '') + type_keys('i', 'J', source_keys) + eq(get_completion(), { 'January', 'June', 'July' }) + -- Should call three times: first to initiate request, second/third to + -- perform its actions (as per `:h complete-functions`) + eq(child.lua_get('_G.n_completfunc_lsp'), 3) + end, + } +) T['Manual completion']['respects `config.mappings'] = function() reload_module({ mappings = { force_twostep = '', force_fallback = '' } }) type_keys('i', 'J', '')