vim-patch:9.2.1004: a completion function cannot tell why it was called (#41500)

Problem:  A function used through 'omnifunc' or 'complete' is called the
          same way whether 'autocomplete' started the completion or a
          key asked for one, so it cannot answer differently.
Solution: Report which of the two it is in complete_info() as "auto". It
          is returned only when asked for in {what}, so what
          complete_info() says by itself does not change
          (Hirohito Higashi).

closes: vim/vim#21143

1f56c351de

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
This commit is contained in:
zeertzjq
2026-08-26 15:21:22 +08:00
committed by GitHub
parent 0f0e89fd90
commit ec982dfb93
6 changed files with 90 additions and 1 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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\<Esc>", '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\<C-X>\<C-O>\<Esc>", 'tx')
call assert_equal([1, 0], g:auto)
setlocal noautocomplete
let g:auto = []
call feedkeys("A\<C-X>\<C-O>\<Esc>", '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