fix(completion): "preselect" ignores "noinsert", menu order #41007

Problem:  A preselected item is inserted even with "noinsert", and the
          first added preselected item wins over the first one shown
          in the menu.
Solution: Use K_DOWN when "noinsert" is set; pick the first preselected
          item that made it into the menu.
This commit is contained in:
glepnir 2026-07-27 20:49:12 +08:00 committed by GitHub
parent 80cd482577
commit 0b3b12da8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 28 deletions

View File

@ -1617,6 +1617,7 @@ static int ins_compl_build_pum(void)
}
bool did_find_shown_match = false;
bool preselect_ok = false;
compl_T *comp;
compl_T *shown_compl = NULL;
int i = 0;
@ -1689,10 +1690,11 @@ static int ins_compl_build_pum(void)
shown_match_ok = true;
}
}
if (comp == compl_preselect_match) {
if (compl_preselect_match != NULL && comp->cp_preselect && !preselect_ok) {
cur = i;
compl_shown_match = comp;
shown_match_ok = true;
preselect_ok = true;
}
i++;
}
@ -3530,7 +3532,7 @@ static void set_completion(colnr_T startcol, list_T *list)
bool no_select = compl_no_select || compl_longest;
if (compl_preselect_match && !no_select) {
compl_curr_match = compl_preselect_match->cp_prev;
ins_complete(Ctrl_N, false);
ins_complete(compl_no_insert ? K_DOWN : Ctrl_N, false);
} else if (compl_no_insert || no_select) {
ins_complete(K_DOWN, false);
if (no_select) {

View File

@ -270,30 +270,25 @@ describe('completion', function()
end)
describe('"preselect"', function()
it('selects first item.preselect item', function()
it('respects "preselect" of complete items', function()
source([[
function! TestComplete() abort
call complete(1, [
\ {'word': 'aaa'},
\ {'word': 'bbb'},
\ {'word': 'ccc', 'preselect': v:true},
\ {'word': 'ddd'},
\ ])
return ''
endfunction
function! TestCompleteMany() abort
let items = []
for i in range(20)
call add(items, {'word': 'item' .. i})
endfor
let items[15].preselect = v:true
call complete(1, items)
call complete(1, g:comp_items)
return ''
endfunction
]])
local function complete(items)
api.nvim_set_var('comp_items', items)
feed('<Esc>S<C-r>=TestComplete()<CR>')
end
local items = {
{ word = 'aaa' },
{ word = 'bbb' },
{ word = 'ccc', preselect = true },
{ word = 'ddd' },
}
command('set completeopt=menuone,preselect')
feed('i<C-r>=TestComplete()<CR>')
complete(items)
screen:expect([[
ccc^ |
{4:aaa }{1: }|
@ -304,10 +299,14 @@ describe('completion', function()
{5:-- INSERT --} |
]])
-- scrolls pum when preselect item is far down
feed('<Esc>S')
-- scrolls pum when preselected item is far down
command('set pumheight=5')
feed('<C-r>=TestCompleteMany()<CR>')
local many = {} ---@type table[]
for i = 0, 19 do
many[i + 1] = { word = 'item' .. i }
end
many[16].preselect = true ---@type boolean
complete(many)
screen:expect([[
item15^ |
{4:item13 }{12: }{1: }|
@ -319,9 +318,9 @@ describe('completion', function()
{5:-- INSERT --} |
]])
feed('<Esc>S')
-- preselected item is selected but not inserted with "noselect"
command('set completeopt=menuone,noselect,preselect pumheight=0')
feed('<C-r>=TestComplete()<CR>')
complete(items)
screen:expect([[
^ |
{4:aaa }{1: }|
@ -332,12 +331,33 @@ describe('completion', function()
{5:-- INSERT --} |
]])
-- without "preselect" in completeopt, preselect attribute is ignored
feed('<Esc>S')
-- selects the first preselected item that still matches
complete({
{ word = 'aaa', preselect = true },
{ word = 'abb' },
{ word = 'abc', preselect = true },
})
feed('ab<C-Y>')
eq('abc', api.nvim_get_current_line())
-- without "preselect" in 'completeopt' the attribute is ignored
command('set completeopt-=preselect')
feed('<C-r>=TestComplete()<CR><C-N><C-Y>')
complete(items)
feed('<C-N><C-Y>')
eq('aaa', api.nvim_get_current_line())
end)
it('does not insert text if noinsert', function()
source([[
function! TestComplete() abort
call complete(1, [{'word': 'aaa', 'preselect': v:true}, {'word': 'bbb'}])
return ''
endfunction
]])
command('set completeopt=menuone,noinsert,preselect')
feed('i<C-r>=TestComplete()<CR>')
eq('', api.nvim_get_current_line())
end)
end)
end)