Commit: patch 9.2.1004: a completion function cannot tell why it was called

2 views
Skip to first unread message

Christian Brabandt

unread,
Aug 25, 2026, 3:45:14 PM (3 days ago) Aug 25
to vim...@googlegroups.com
patch 9.2.1004: a completion function cannot tell why it was called

Commit: https://github.com/vim/vim/commit/1f56c351dedad288f79c89b6b6f64be762ec3b9b
Author: Hirohito Higashi <h.eas...@gmail.com>
Date: Tue Aug 25 19:38:27 2026 +0000

patch 9.2.1004: a completion function cannot tell why it was called

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: #21143

Co-Authored-By: Claude Opus 5 (1M context) <nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index c37b7b0d0..6627469cd 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt* For Vim version 9.2. Last change: 2026 Aug 23
+*builtin.txt* For Vim version 9.2. Last change: 2026 Aug 25


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -2059,6 +2059,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/doc/version9.txt b/runtime/doc/version9.txt
index 768f9e12a..6ecfd04c0 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.2. Last change: 2026 Aug 23
+*version9.txt* For Vim version 9.2. Last change: 2026 Aug 25


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52707,6 +52707,8 @@ Changed ~
they were ignored with 'eventignore'.
- |ch_sendexpr()| accepts a non-Number "id" in "lsp" mode when no response is
expected, so a request from the server can be answered.
+- |complete_info()| can distinguish between manual and automatic triggered
+ completions via the "auto" item.

*added-9.3*
Added ~
diff --git a/src/edit.c b/src/edit.c
index 3b0cfcc88..029b402dc 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -108,6 +108,7 @@ static int dont_sync_undo = FALSE; // CTRL-G U prevents syncing undo for
update_screen(UPD_VALID); /* Show char (deletion) immediately */ \
out_flush(); \
ins_compl_enable_autocomplete(); \
+ ins_compl_arm_autostart(); \
if (!ins_compl_arm_autocomplete_delay())\
goto docomplete; \
} while (0)
@@ -631,6 +632,7 @@ edit(
if (vim_isprintc(c))
{
ins_compl_enable_autocomplete();
+ ins_compl_arm_autostart();
ins_compl_init_get_longest();
#ifdef FEAT_RIGHTLEFT
if (p_hkmap)
@@ -678,8 +680,11 @@ edit(
// Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V.
did_cursorhold = TRUE;
if (c != K_CURSORHOLD && c != K_COMPLETE_DELAY)
+ {
// Don't want delayed autocompletion from the previous key either.
ins_compl_clear_autocomplete_delay();
+ ins_compl_disarm_autostart();
+ }

#ifdef FEAT_RIGHTLEFT
if (p_hkmap && KeyTyped)
@@ -1189,6 +1194,7 @@ doESCkey:
// 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();
goto docomplete;

#ifdef FEAT_GUI_MSWIN
diff --git a/src/insexpand.c b/src/insexpand.c
index f63f4ff6b..2145fc199 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -252,6 +252,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 int 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;
#ifdef ELAPSED_FUNC
static elapsed_T compl_autocomplete_start_tv; // when the delay was armed
@@ -2452,6 +2454,7 @@ ins_compl_clear(void)
cpt_sources_clear();
compl_autocomplete = FALSE;
compl_from_nonkeyword = FALSE;
+ compl_autostarted = false;
compl_num_bests = 0;
#ifdef FEAT_EVAL
// clear v:completed_item
@@ -2819,6 +2822,7 @@ ins_compl_restart(void)
cpt_sources_clear();
compl_autocomplete = FALSE;
compl_from_nonkeyword = FALSE;
+ compl_autostarted = false;
compl_num_bests = 0;
}

@@ -3187,6 +3191,7 @@ ins_compl_stop(int c, int prev_mode, int retval)
}
compl_autocomplete = FALSE;
compl_from_nonkeyword = FALSE;
+ compl_autostarted = false;
compl_num_bests = 0;
compl_ins_end_col = 0;

@@ -4226,11 +4231,13 @@ 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;
@@ -4253,6 +4260,8 @@ 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;
}
}

@@ -4267,6 +4276,9 @@ get_complete_info(list_T *what_list, dict_T *retdict)
if (ret == OK && (what_flag & CI_WHAT_PUM_VISIBLE))
ret = dict_add_number(retdict, "pum_visible", pum_visible());

+ if (ret == OK && (what_flag & CI_WHAT_AUTO))
+ ret = dict_add_number(retdict, "auto", compl_autostarted);
+
if (ret == OK && (what_flag & CI_WHAT_PREINSERTED_TEXT))
{
char_u *line = ml_get_curline();
@@ -7419,6 +7431,9 @@ ins_complete(int c, int enable_pum)

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;
}
@@ -7502,6 +7517,25 @@ ins_compl_enable_autocomplete(void)
#endif
}

+/*
+ * 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.
diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro
index ebc8e25ac..254d6d1ca 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -76,6 +76,8 @@ void ins_compl_insert(int move_cursor, int insert_prefix);
void ins_compl_check_keys(int frequency, int in_compl_func);
int ins_complete(int c, int enable_pum);
void ins_compl_enable_autocomplete(void);
+void ins_compl_arm_autostart(void);
+void ins_compl_disarm_autostart(void);
bool ins_compl_arm_autocomplete_delay(void);
void ins_compl_clear_autocomplete_delay(void);
bool ins_compl_autocomplete_pending(void);
diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim
index b83e605aa..b345a45e4 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -6756,4 +6756,43 @@ 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 test_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 test_override("ALL", 0)
+ unlet g:auto
+ delfunc AutoOmni
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab nofoldenable
diff --git a/src/version.c b/src/version.c
index 38d66cf17..41ea63f51 100644
--- a/src/version.c
+++ b/src/version.c
@@ -763,6 +763,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1004,
/**/
1003,
/**/
Reply all
Reply to author
Forward
0 new messages