Skip to content

Commit b21cd5b

Browse files
committed
fix(completion): keep isIncomplete=true info until not needed
Details: - This enables "not forgetting" about previously incomplete result during fast typing. I.e.: - Press `<C-Space>`. Wait for response which is incomplete. - Fast type `a` and `b` (so that the LSP request after `a` is cancelled). There should still be a force updated due to incomplete initial list. Previously it was forgotten after the first key press due to autocompletion stop.
1 parent f05a63b commit b21cd5b

4 files changed

+73
-12
lines changed

lua/mini/completion.lua

+15-11
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ MiniCompletion.completefunc_lsp = function(findstart, base)
433433
local process_items, is_incomplete = H.get_config().lsp_completion.process_items, false
434434
process_items = process_items or MiniCompletion.default_process_items
435435
local words = H.process_lsp_response(H.completion.lsp.result, function(response, client_id)
436-
is_incomplete = is_incomplete or response.isIncomplete
436+
is_incomplete = is_incomplete or (response.isIncomplete == true)
437437
-- Response can be `CompletionList` with 'items' field plus their
438438
-- defaults or `CompletionItem[]`
439439
local items = H.table_get(response, { 'items' }) or response
@@ -443,7 +443,8 @@ MiniCompletion.completefunc_lsp = function(findstart, base)
443443
return H.lsp_completion_response_items_to_complete_items(items, client_id)
444444
end)
445445

446-
H.completion.lsp.status = is_incomplete and 'done-isincomplete' or 'done'
446+
H.completion.lsp.status = 'done'
447+
H.completion.lsp.is_incomplete = is_incomplete
447448

448449
-- Maybe trigger fallback action
449450
if vim.tbl_isempty(words) and H.completion.fallback then return H.trigger_fallback() end
@@ -544,7 +545,8 @@ H.keys = {
544545
-- Field `lsp` is a table describing state of all used LSP requests. It has the
545546
-- following structure:
546547
-- - id: identifier (consecutive numbers).
547-
-- - status: one of 'sent', 'received', 'done', 'done-isincomplete', 'canceled'
548+
-- - status: one of 'sent', 'received', 'done', 'canceled'
549+
-- - is_incomplete: whether request was incomplete and require recomputing
548550
-- - result: result of request.
549551
-- - cancel_fun: function which cancels current request.
550552

@@ -555,7 +557,7 @@ H.completion = {
555557
source = nil,
556558
text_changed_id = 0,
557559
timer = vim.loop.new_timer(),
558-
lsp = { id = 0, status = nil, result = nil, cancel_fun = nil },
560+
lsp = { id = 0, status = nil, is_incomplete = false, result = nil, cancel_fun = nil },
559561
start_pos = {},
560562
}
561563

@@ -693,13 +695,15 @@ H.auto_completion = function()
693695

694696
H.completion.timer:stop()
695697

696-
local is_incomplete = H.completion.lsp.status == 'done-isincomplete'
698+
local is_incomplete = H.completion.lsp.is_incomplete
697699
local force = H.is_lsp_trigger(vim.v.char, 'completion') or is_incomplete
698700
if force then
699-
-- If character is LSP trigger, force fresh LSP completion later
700-
-- Check LSP trigger before checking for pumvisible because it should be
701-
-- forced even if there are visible candidates
702-
H.stop_completion(false)
701+
-- Force fresh LSP completion if needed. Check before checking pumvisible
702+
-- because it should be forced even if there are visible candidates.
703+
-- Keep positive `is_incomplete` to allow fast typing and not "forget" that
704+
-- list was incomplete after the second fast key press. This will force LSP
705+
-- completion until `isIncomplete=false` response or general `stop()`.
706+
H.stop_completion(false, is_incomplete)
703707
elseif H.pumvisible() then
704708
-- Do nothing if popup is visible. `H.pumvisible()` might be `true` even if
705709
-- there is no popup. It is common when manually typing candidate followed
@@ -874,11 +878,11 @@ end
874878
H.default_fallback_action = function() vim.api.nvim_feedkeys(H.keys.ctrl_n, 'n', false) end
875879

876880
-- Stop actions ---------------------------------------------------------------
877-
H.stop_completion = function(keep_source)
881+
H.stop_completion = function(keep_source, keep_lsp_is_incomplete)
878882
H.completion.timer:stop()
879883
H.cancel_lsp({ H.completion })
880884
H.completion.fallback, H.completion.force = true, false
881-
if H.completion.lsp.status == 'done-isincomplete' then H.completion.lsp.status = 'done' end
885+
if not keep_lsp_is_incomplete then H.completion.lsp.is_incomplete = false end
882886
if not keep_source then H.completion.source = nil end
883887
end
884888

tests/dir-completion/mock-months-lsp.lua

+6-1
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,17 @@ Months.requests = {
196196
}
197197

198198
-- Replace builtin functions with custom testable ones ========================
199-
vim.lsp.buf_request_all = function(bufnr, method, params, callback)
199+
local make_request = function(bufnr, method, params, callback)
200200
local requests = Months.requests[method]
201201
if requests == nil then return end
202202
callback(requests(params))
203203
end
204204

205+
vim.lsp.buf_request_all = function(bufnr, method, params, callback)
206+
if _G.mock_request_delay == nil then return make_request(bufnr, method, params, callback) end
207+
vim.defer_fn(function() make_request(bufnr, method, params, callback) end, _G.mock_request_delay)
208+
end
209+
205210
local get_lsp_clients = function() return { Months.client } end
206211

207212
if vim.fn.has('nvim-0.10') == 0 then vim.lsp.buf_get_clients = get_lsp_clients end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--|---------|---------|
2+
01|Jul
3+
02|July Function
4+
03|~
5+
04|~
6+
05|~
7+
06|~
8+
07|~
9+
08|~
10+
09|<ame] [+] 1,4 All
11+
10|-- INSERT --
12+
13+
--|---------|---------|
14+
01|00000000000000000000
15+
02|11111111111111122222
16+
03|22222222222222222222
17+
04|22222222222222222222
18+
05|22222222222222222222
19+
06|22222222222222222222
20+
07|22222222222222222222
21+
08|22222222222222222222
22+
09|33333333333333333333
23+
10|44444444444455555555

tests/test_completion.lua

+29
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,35 @@ T['Autocompletion']['forces new LSP completion in case of `isIncomplete`'] = fun
526526
eq(child.lua_get('_G.lines_at_request'), { 'J', 'Ju', 'J' })
527527
end
528528

529+
T['Autocompletion']['forces new LSP completion for `isIncomplete` even if canceled'] = function()
530+
child.lua([[
531+
_G.lines_at_request = {}
532+
local buf_request_all_orig = vim.lsp.buf_request_all
533+
vim.lsp.buf_request_all = function(bufnr, method, params, callback)
534+
table.insert(_G.lines_at_request, vim.api.nvim_get_current_line())
535+
return buf_request_all_orig(bufnr, method, params, callback)
536+
end
537+
]])
538+
child.set_size(10, 20)
539+
child.api.nvim_set_current_buf(child.api.nvim_create_buf(true, false))
540+
541+
child.lua('_G.mock_request_delay = ' .. small_time)
542+
543+
-- Mock incomplete completion list which contains only months 1-6
544+
child.lua('_G.mock_isincomplete = true')
545+
type_keys('i', 'J', '<C-Space>')
546+
sleep(small_time + small_time)
547+
eq(child.lua_get('_G.lines_at_request'), { 'J' })
548+
549+
-- Should force two requests even though the first one was canceled due to
550+
-- fast typing
551+
child.lua('_G.mock_isincomplete = false')
552+
type_keys('u', 'l')
553+
sleep(small_time + small_time)
554+
child.expect_screenshot()
555+
eq(child.lua_get('_G.lines_at_request'), { 'J', 'Ju', 'Jul' })
556+
end
557+
529558
T['Autocompletion']['respects `config.delay.completion`'] = function()
530559
child.lua('MiniCompletion.config.delay.completion = ' .. (2 * default_completion_delay))
531560

0 commit comments

Comments
 (0)