diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index d7efd564c8..e11ba31c42 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -1316,6 +1316,12 @@ complete_info([{what}]) *complete_info()* Returns a |Dictionary| with information about Insert mode completion. See |ins-completion|. The items are: + auto |TRUE| when Vim started this completion by + itself, which is what 'autocomplete' does, + and |FALSE| when a key asked for it, such as + |i_CTRL-X_CTRL-O|. A |complete-functions| + function can read this to tell the two apart. + Returned only when asked for in {what}. completed Return a dictionary containing the entries of the currently selected index item. items List of all completion candidates. Each item diff --git a/runtime/lua/vim/_meta/vimfn.gen.lua b/runtime/lua/vim/_meta/vimfn.gen.lua index d9b07b7bc9..e194ada41f 100644 --- a/runtime/lua/vim/_meta/vimfn.gen.lua +++ b/runtime/lua/vim/_meta/vimfn.gen.lua @@ -1135,6 +1135,12 @@ function vim.fn.complete_check() end --- Returns a |Dictionary| with information about Insert mode --- completion. See |ins-completion|. --- The items are: +--- auto |TRUE| when Vim started this completion by +--- itself, which is what 'autocomplete' does, +--- and |FALSE| when a key asked for it, such as +--- |i_CTRL-X_CTRL-O|. A |complete-functions| +--- function can read this to tell the two apart. +--- Returned only when asked for in {what}. --- completed Return a dictionary containing the entries of --- the currently selected index item. --- items List of all completion candidates. Each item diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index d034a38ca1..70b374b788 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -1489,6 +1489,12 @@ M.funcs = { Returns a |Dictionary| with information about Insert mode completion. See |ins-completion|. The items are: + auto |TRUE| when Vim started this completion by + itself, which is what 'autocomplete' does, + and |FALSE| when a key asked for it, such as + |i_CTRL-X_CTRL-O|. A |complete-functions| + function can read this to tell the two apart. + Returned only when asked for in {what}. completed Return a dictionary containing the entries of the currently selected index item. items List of all completion candidates. Each item diff --git a/src/nvim/insert.c b/src/nvim/insert.c index 8c843a003b..46c2b6acdd 100644 --- a/src/nvim/insert.c +++ b/src/nvim/insert.c @@ -134,6 +134,7 @@ static kvec_t(char) replace_stack = KV_INITIAL_VALUE; update_screen(); /* Show char deletion immediately */ \ ui_flush(); \ ins_compl_enable_autocomplete(); \ + ins_compl_arm_autostart(); \ if (!ins_compl_arm_autocomplete_delay()) { \ insert_do_complete(s); \ break; \ @@ -522,6 +523,7 @@ static int insert_check(VimState *state) s->c = char_before_cursor(); if (vim_isprintc(s->c)) { ins_compl_enable_autocomplete(); + ins_compl_arm_autostart(); ins_compl_init_get_longest(); // Defer until the delay expires (K_COMPLETE_DELAY), or // trigger now when no delay is in effect. @@ -563,6 +565,7 @@ static int insert_execute(VimState *state, int key) if (key != K_EVENT && key != K_COMPLETE_DELAY) { // Don't want delayed autocompletion from the previous key either. ins_compl_clear_autocomplete_delay(); + ins_compl_disarm_autostart(); } // Special handling of keys while the popup menu is visible or wanted @@ -1001,6 +1004,7 @@ static int insert_handle_key(InsertState *s) // The completion may have been cleared while waiting, so re-enable // autocomplete to match a zero delay. ins_compl_enable_autocomplete(); + ins_compl_arm_autostart(); insert_do_complete(s); break; diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 0969441a0d..21de3d6e64 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -329,6 +329,8 @@ static buf_T *compl_curr_buf = NULL; ///< buf where completion is active // longer fixed timeout is used (COMPL_FUNC_TIMEOUT_MS or // COMPL_FUNC_TIMEOUT_NON_KW_MS). - girish static bool compl_autocomplete = false; ///< whether autocompletion is active +static bool compl_autostarted = false; ///< Vim started this completion +static bool compl_autostart_pending = false; ///< the trigger armed one static bool compl_autocomplete_pending = false; static uint64_t compl_autocomplete_start_tv; ///< when the delay was armed static uint64_t compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS; @@ -2287,6 +2289,7 @@ void ins_compl_clear(void) cpt_sources_clear(); compl_autocomplete = false; compl_from_nonkeyword = false; + compl_autostarted = false; compl_num_bests = 0; // clear v:completed_item set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); @@ -2586,6 +2589,7 @@ static void ins_compl_restart(void) cpt_sources_clear(); compl_autocomplete = false; compl_from_nonkeyword = false; + compl_autostarted = false; compl_num_bests = 0; } @@ -2892,6 +2896,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) } compl_autocomplete = false; compl_from_nonkeyword = false; + compl_autostarted = false; compl_num_bests = 0; compl_ins_end_col = 0; @@ -3696,11 +3701,12 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) #define CI_WHAT_COMPLETED 0x10 #define CI_WHAT_MATCHES 0x20 #define CI_WHAT_PREINSERTED_TEXT 0x40 +#define CI_WHAT_AUTO 0x80 #define CI_WHAT_ALL 0xff int what_flag; if (what_list == NULL) { - what_flag = CI_WHAT_ALL & ~(CI_WHAT_MATCHES|CI_WHAT_COMPLETED); + what_flag = CI_WHAT_ALL & ~(CI_WHAT_MATCHES|CI_WHAT_COMPLETED|CI_WHAT_AUTO); } else { what_flag = 0; for (listitem_T *item = tv_list_first(what_list) @@ -3722,6 +3728,8 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) what_flag |= CI_WHAT_PREINSERTED_TEXT; } else if (strcmp(what, "matches") == 0) { what_flag |= CI_WHAT_MATCHES; + } else if (strcmp(what, "auto") == 0) { + what_flag |= CI_WHAT_AUTO; } } } @@ -3736,6 +3744,10 @@ 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_AUTO)) { + ret = tv_dict_add_nr(retdict, S_LEN("auto"), compl_autostarted); + } + if (ret == OK && (what_flag & CI_WHAT_PREINSERTED_TEXT)) { char *line = get_cursor_line_ptr(); int len = compl_ins_end_col - curwin->w_cursor.col; @@ -6317,6 +6329,9 @@ int ins_complete(int c, bool enable_pum) int insert_match = ins_compl_use_match(c); if (!compl_started) { + // Only what the automatic trigger armed counts as started by Vim. + compl_autostarted = compl_autostart_pending; + compl_autostart_pending = false; if (ins_compl_start() == FAIL) { return FAIL; } @@ -6401,6 +6416,19 @@ void ins_compl_enable_autocomplete(void) compl_get_longest = false; } +/// Remember that Vim is about to start a completion by itself, rather than +/// because a key was typed to ask for one. +void ins_compl_arm_autostart(void) +{ + compl_autostart_pending = true; +} + +/// Forget what the automatic trigger armed, another key was typed since. +void ins_compl_disarm_autostart(void) +{ + compl_autostart_pending = false; +} + /// Arm the 'autocompletedelay' timer when the delay is in effect. /// Return true when the popup should be deferred, false to trigger it now. bool ins_compl_arm_autocomplete_delay(void) diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index d9541fc919..bb60f93edd 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -6851,6 +6851,45 @@ func Test_complete_info_hlgroup() bwipe! endfunc +" Test that complete_info() tells a completion Vim started by itself from one +" that a key asked for. +func Test_complete_info_auto() + call Ntest_override("char_avail", 1) + new + func! AutoOmni(findstart, base) + if a:findstart + call add(g:auto, complete_info(['auto']).auto) + return col('.') - 1 + endif + return ['alpha', 'alphabet'] + endfunc + setlocal omnifunc=AutoOmni + setlocal complete=o + setlocal completeopt=menuone + call setline(1, ' al') + + " 'autocomplete' starts it, nothing was asked for. + setlocal autocomplete + let g:auto = [] + call feedkeys("A\", 'tx') + call assert_equal([1], g:auto) + + " The second call is the one CTRL-X CTRL-O asked for. + let g:auto = [] + call feedkeys("A\\\", 'tx') + call assert_equal([1, 0], g:auto) + + setlocal noautocomplete + let g:auto = [] + call feedkeys("A\\\", 'tx') + call assert_equal([0], g:auto) + + call Ntest_override("ALL", 0) + unlet g:auto + delfunc AutoOmni + bwipe! +endfunc + func Test_complete_fuzzy_resort() new set completeopt=menu,menuone,noselect,fuzzy