diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 0e3e09ea76..ff0c31ad58 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -800,6 +800,8 @@ complete_info([{what}]) *complete_info()* dictionary containing the entries "word", "abbr", "menu", "kind", "info" and "user_data". See |complete-items|. + pum_matches Indices of completion matches that are put + in the |ins-completion-menu|. First index is zero. selected Selected item index. First index is zero. Index is -1 if no item is selected (showing typed text only, or the last completion after diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 515aef753d..ba60431e0a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -199,6 +199,9 @@ UI globally set this, you can override vim.ui.open using the same approach described at |vim.paste()|. +• |complete_info()| returns a `pum_matches` item to indicate which items are put + in the |ins-completion-menu|. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 70a7503aac..bdcdedaef2 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1022,6 +1022,8 @@ function vim.fn.complete_check() end --- dictionary containing the entries "word", --- "abbr", "menu", "kind", "info" and "user_data". --- See |complete-items|. +--- pum_matches Indices of completion matches that are put +--- in the |ins-completion-menu|. First index is zero. --- selected Selected item index. First index is zero. --- Index is -1 if no item is selected (showing --- typed text only, or the last completion after diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 25c1fec1e4..4a92fc9f34 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -1371,6 +1371,8 @@ M.funcs = { dictionary containing the entries "word", "abbr", "menu", "kind", "info" and "user_data". See |complete-items|. + pum_matches Indices of completion matches that are put + in the |ins-completion-menu|. First index is zero. selected Selected item index. First index is zero. Index is -1 if no item is selected (showing typed text only, or the last completion after diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index c17bd27daa..acfc70d049 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -2848,8 +2848,9 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) #define CI_WHAT_MODE 0x01 #define CI_WHAT_PUM_VISIBLE 0x02 #define CI_WHAT_ITEMS 0x04 -#define CI_WHAT_SELECTED 0x08 -#define CI_WHAT_INSERTED 0x10 +#define CI_WHAT_PUM_MATCHES 0x08 +#define CI_WHAT_SELECTED 0x10 +#define CI_WHAT_INSERTED 0x20 #define CI_WHAT_ALL 0xff int what_flag; @@ -2868,6 +2869,8 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) what_flag |= CI_WHAT_PUM_VISIBLE; } else if (strcmp(what, "items") == 0) { what_flag |= CI_WHAT_ITEMS; + } else if (strcmp(what, "pum_matches") == 0) { + what_flag |= CI_WHAT_PUM_MATCHES; } else if (strcmp(what, "selected") == 0) { what_flag |= CI_WHAT_SELECTED; } else if (strcmp(what, "inserted") == 0) { @@ -2885,18 +2888,31 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible()); } - if (ret == OK && (what_flag & CI_WHAT_ITEMS || what_flag & CI_WHAT_SELECTED)) { - list_T *li = NULL; + if (ret == OK + && (what_flag & CI_WHAT_ITEMS || what_flag & CI_WHAT_PUM_MATCHES + || what_flag & CI_WHAT_SELECTED)) { + list_T *items = NULL; + list_T *pum_matches = NULL; int selected_idx = -1; + const int lead_len + = (what_flag & CI_WHAT_PUM_MATCHES) && compl_leader != NULL ? (int)strlen(compl_leader) : 0; + if (what_flag & CI_WHAT_ITEMS) { - li = tv_list_alloc(kListLenMayKnow); - ret = tv_dict_add_list(retdict, S_LEN("items"), li); + items = tv_list_alloc(kListLenMayKnow); + ret = tv_dict_add_list(retdict, S_LEN("items"), items); } - if (ret == OK && what_flag & CI_WHAT_SELECTED) { + + if (ret == OK && (what_flag & CI_WHAT_PUM_MATCHES)) { + pum_matches = tv_list_alloc(kListLenUnknown); + ret = tv_dict_add_list(retdict, S_LEN("pum_matches"), pum_matches); + } + + if (ret == OK && (what_flag & CI_WHAT_SELECTED)) { if (compl_curr_match != NULL && compl_curr_match->cp_number == -1) { ins_compl_update_sequence_numbers(); } } + if (ret == OK && compl_first_match != NULL) { int list_idx = 0; compl_T *match = compl_first_match; @@ -2904,7 +2920,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) if (!match_at_original_text(match)) { if (what_flag & CI_WHAT_ITEMS) { dict_T *di = tv_dict_alloc(); - tv_list_append_dict(li, di); + tv_list_append_dict(items, di); tv_dict_add_str(di, S_LEN("word"), match->cp_str); tv_dict_add_str(di, S_LEN("abbr"), match->cp_text[CPT_ABBR]); tv_dict_add_str(di, S_LEN("menu"), match->cp_text[CPT_MENU]); @@ -2917,6 +2933,12 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); } } + + if ((what_flag & CI_WHAT_PUM_MATCHES) + && (compl_leader == NULL || ins_compl_equal(match, compl_leader, (size_t)lead_len))) { + tv_list_append_number(pum_matches, list_idx); + } + if (compl_curr_match != NULL && compl_curr_match->cp_number == match->cp_number) { selected_idx = list_idx; diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index d543de4acd..9d745f1329 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -1192,6 +1192,34 @@ describe('completion', function() ]]) end) + describe('complete_info() pum_matches', function() + before_each(function() + source([[ + function! TestComplete() abort + call complete(1, ['abcd', 'acbd', 'xyz', 'foobar']) + return '' + endfunction + + set completeopt+=noinsert + inoremap =TestComplete() + ]]) + end) + + it('works', function() + feed('i') + eq({ pum_matches = { 0, 1, 2, 3 } }, fn.complete_info({ 'pum_matches' })) + + feed('a') + eq({ pum_matches = { 0, 1 } }, fn.complete_info({ 'pum_matches' })) + + feed('c') + eq({ pum_matches = { 1 } }, fn.complete_info({ 'pum_matches' })) + + feed('x') + eq({ pum_matches = {} }, fn.complete_info({ 'pum_matches' })) + end) + end) + -- oldtest: Test_complete_changed_complete_info() it('no crash calling complete_info() in CompleteChanged', function() source([[ diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 48319f5017..bf12ecdb60 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -382,7 +382,7 @@ func Test_completefunc_info() set completeopt=menuone set completefunc=CompleteTest call feedkeys("i\\\\=string(complete_info())\\", "tx") - call assert_equal("matched{'pum_visible': 1, 'mode': 'function', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1)) + call assert_equal("matched{'pum_matches': [0], 'pum_visible': 1, 'mode': 'function', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1)) bwipe! set completeopt& set completefunc& @@ -406,7 +406,8 @@ func CompleteInfoTestUserDefinedFn(mvmt, idx, noselect) set completefunc=CompleteInfoUserDefinedFn call feedkeys("i\\" . a:mvmt . "\\=string(complete_info())\\", "tx") let completed = a:idx != -1 ? ['foo', 'bar', 'baz', 'qux']->get(a:idx) : '' - call assert_equal(completed. "{'pum_visible': 1, 'mode': 'function', 'selected': " . a:idx . ", 'items': [" . + call assert_equal(completed. "{'pum_matches': [0, 1, 2, 3], 'pum_visible': 1, " . + \ "'mode': 'function', 'selected': " . a:idx . ", 'items': [" . \ "{'word': 'foo', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " . \ "{'word': 'bar', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " . \ "{'word': 'baz', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " . diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 2a038d7da4..9e6ab08924 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1139,6 +1139,7 @@ func Test_popup_complete_info_02() \ {'word': 'May', 'menu': 'May', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''} \ ], \ 'selected': 0, + \ 'pum_matches': [0, 1, 2, 3, 4], \ } let g:compl_what = [] @@ -1147,6 +1148,7 @@ func Test_popup_complete_info_02() let g:compl_what = ['mode', 'pum_visible', 'selected'] call remove(d, 'items') + call remove(d, 'pum_matches') call feedkeys("i\\\", 'tx') call assert_equal(d, g:compl_info) @@ -1167,6 +1169,7 @@ func Test_popup_complete_info_no_pum() \ 'pum_visible': 0, \ 'items': [], \ 'selected': -1, + \ 'pum_matches': [], \ } call assert_equal( d, complete_info() ) bwipe!