Skip to content

Commit

Permalink
feat(completion): add v:complete_items to get completion items
Browse files Browse the repository at this point in the history
Problem: Currently there is no way of getting the list of completion items that are actually shown in the popupmenu. Which means completion providers have no way of knowing if a completion was exhausted.

Solution: Add `v:complete_items` which contains a list of completion items that were last shown in the popupmenu.

Ref: vim/vim#10007
  • Loading branch information
famiu committed Apr 30, 2024
1 parent 05e75da commit b673df5
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 0 deletions.
3 changes: 3 additions & 0 deletions runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ The following new APIs and features were added.
|CompleteDone| now sets the `reason` key in `v:event` which specifies the reason
for completion being done.

|v:complete_items| contains the most recent |complete-items| that are shown in
the |ins-completion-menu|.

==============================================================================
CHANGED FEATURES *news-changed*

Expand Down
6 changes: 6 additions & 0 deletions runtime/doc/vvars.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ v:collate
command.
See |multi-lang|.

*v:complete_items* *complete_items-variable*
v:complete_items
Dictionary containing the most recent |complete-items| shown
in the |ins-completion-menu|. Empty if there was no matching
completion item, or after leaving and re-entering insert mode.

*v:completed_item* *completed_item-variable*
v:completed_item
Dictionary containing the |complete-items| for the most
Expand Down
6 changes: 6 additions & 0 deletions runtime/lua/vim/_meta/vvars.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/nvim/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ static struct vimvar {
VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0),
VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX),
VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO),
VV(VV_COMPLETE_ITEMS, "complete_items", VAR_LIST, 0),
VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, 0),
VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO),
VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO),
Expand Down
1 change: 1 addition & 0 deletions src/nvim/eval.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ typedef enum {
VV_OLDFILES,
VV_WINDOWID,
VV_PROGPATH,
VV_COMPLETE_ITEMS,
VV_COMPLETED_ITEM,
VV_OPTION_NEW,
VV_OPTION_OLD,
Expand Down
8 changes: 8 additions & 0 deletions src/nvim/eval/typval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3113,6 +3113,14 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
return l;
}

list_T *tv_list_alloc_lock(const ptrdiff_t len, VarLockStatus lock)
FUNC_ATTR_NONNULL_RET
{
list_T *const l = tv_list_alloc(len);
l->lv_lock = VAR_FIXED;
return l;
}

dict_T *tv_dict_alloc_lock(VarLockStatus lock)
FUNC_ATTR_NONNULL_RET
{
Expand Down
30 changes: 30 additions & 0 deletions src/nvim/insexpand.c
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,31 @@ static bool pum_enough_matches(void)
return i >= 2;
}

/// Update v:complete_items to reflect completion items shown in the popupmenu
///
/// @param[in] first First match
static void update_v_complete_items(void)
{
list_T *compl_items = tv_list_alloc_lock(kListLenUnknown, VAR_FIXED);
compl_T *comp = compl_first_match;
const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0;

do {
// Ensure that completion item isn't original text, and also make sure that the completion item
// matches the completion leader, so only the items shown in the popupmenu are added to the
// list.
if (!match_at_original_text(comp)
&& (compl_leader == NULL || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
dict_T *compl_item = ins_compl_dict_alloc(comp);
tv_list_append_dict(compl_items, compl_item);
}

comp = comp->cp_next;
} while (comp != NULL && !is_first_match(comp));

set_vim_var_list(VV_COMPLETE_ITEMS, compl_items);
}

/// Convert to complete item dict
static dict_T *ins_compl_dict_alloc(compl_T *match)
{
Expand Down Expand Up @@ -1258,6 +1283,9 @@ static int ins_compl_build_pum(void)
cur = -1;
}

// Update v:complete_items
update_v_complete_items();

return cur;
}

Expand Down Expand Up @@ -1634,6 +1662,8 @@ void ins_compl_clear(void)
kv_destroy(compl_orig_extmarks);
XFREE_CLEAR(compl_orig_text);
compl_enter_selects = false;
// clear v:complete_items
set_vim_var_list(VV_COMPLETE_ITEMS, tv_list_alloc_lock(0, VAR_FIXED));
// clear v:completed_item
set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
Expand Down
7 changes: 7 additions & 0 deletions src/nvim/vvars.lua
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ M.vars = {
can be used.
]=],
},
complete_items = {
desc = [=[
Dictionary containing the most recent |complete-items| shown
in the |ins-completion-menu|. Empty if there was no matching
completion item, or after leaving and re-entering insert mode.
]=],
},
completed_item = {
desc = [=[
Dictionary containing the |complete-items| for the most
Expand Down
44 changes: 44 additions & 0 deletions test/functional/editor/completion_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,50 @@ describe('completion', function()
})
end)

describe('v:complete_items', function()
before_each(function()
source([[
function! TestComplete() abort
call complete(1, ['abcd', 'acbd', 'xyz', 'foobar'])
return ''
endfunction
set completeopt+=noinsert
inoremap <C-x> <c-r>=TestComplete()<cr>
]])
end)

it('is empty until completion', function()
eq({}, eval('v:complete_items'))
end)
it('works', function()
feed('i<C-x>')
eq({
{ word = 'abcd', abbr = '', menu = '', info = '', kind = '', user_data = '' },
{ word = 'acbd', abbr = '', menu = '', info = '', kind = '', user_data = '' },
{ word = 'xyz', abbr = '', menu = '', info = '', kind = '', user_data = '' },
{ word = 'foobar', abbr = '', menu = '', info = '', kind = '', user_data = '' },
}, eval('v:complete_items'))
end)
it('works after completion is accepted', function()
feed('i<C-x>a<C-y>')
eq({
{ word = 'abcd', abbr = '', menu = '', info = '', kind = '', user_data = '' },
{ word = 'acbd', abbr = '', menu = '', info = '', kind = '', user_data = '' },
}, eval('v:complete_items'))
end)
it('works after completion is aborted', function()
feed('i<C-x>x<C-e>')
eq({
{ word = 'xyz', abbr = '', menu = '', info = '', kind = '', user_data = '' },
}, eval('v:complete_items'))
end)
it('is empty after leaving and re-entering insert mode', function()
feed('i<C-x>a<ESC>i')
eq({}, eval('v:complete_items'))
end)
end)

describe('v:completed_item', function()
it('is empty dict until completion', function()
eq({}, eval('v:completed_item'))
Expand Down

0 comments on commit b673df5

Please sign in to comment.