commit ed40d89d7c55fdc318095ee0d0f181ddafaca8e1
parent 639f9f4cdaa39918b2a182924cdeefd348ec6bdf
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sat, 23 Aug 2025 21:24:17 +0800
Merge pull request #35427 from zeertzjq/vim-9.1.1638
vim-patch: 'autocompletedelay'
Diffstat:
12 files changed, 288 insertions(+), 23 deletions(-)
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
@@ -1117,8 +1117,8 @@ CTRL-X CTRL-Z Stop completion without changing the text.
AUTOCOMPLETION *ins-autocompletion*
Vim can display a completion menu as you type, similar to using |i_CTRL-N|,
-but triggered automatically. See 'autocomplete'. The menu items are collected
-from the sources listed in the 'complete' option.
+but triggered automatically. See 'autocomplete' and 'autocompletedelay'.
+The menu items are collected from the sources listed in the 'complete' option.
Unlike manual |i_CTRL-N| completion, this mode uses a decaying timeout to keep
Vim responsive. Sources earlier in the 'complete' list are given more time
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -747,6 +747,13 @@ A jump table for the options with a short description can be found at |Q_op|.
When on, Vim shows a completion menu as you type, similar to using
|i_CTRL-N|, but triggered automatically. See |ins-autocompletion|.
+ *'autocompletedelay'* *'acl'*
+'autocompletedelay' 'acl' number (default 0)
+ global
+ Delay in milliseconds before the autocomplete menu appears after
+ typing. If you prefer it not to open too quickly, set this value
+ slightly above your typing speed. See |ins-autocompletion|.
+
*'autoindent'* *'ai'* *'noautoindent'* *'noai'*
'autoindent' 'ai' boolean (default on)
local to buffer
diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt
@@ -1485,11 +1485,11 @@ Fuzzy matching scores how well a string matches a pattern when the pattern
characters appear in order but not necessarily contiguously.
Example: >
- Pattern: "vim"
- Candidates: "vim" -> perfect
- "vimeo" -> good (v i m)
- "voice mail" -> weaker (v _ i _ _ _ m)
- "vintage" -> no match (no "m")
+ Pattern: "vim"
+ Candidates: "vim" -> perfect
+ "vimeo" -> good (v i m)
+ "voice mail" -> weaker (v _ i _ _ _ m)
+ "vintage" -> no match (no "m")
<
If the search string has multiple words, each word is matched separately and
may appear in any order in the candidate. For example "get pat" matches
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
@@ -626,6 +626,7 @@ Short explanation of each option: *option-list*
'arabicshape' 'arshape' do shaping for Arabic characters
'autochdir' 'acd' change directory to the file in the current window
'autocomplete' 'ac' enable automatic completion in insert mode
+'autocompletedelay' 'acl' delay in msec before menu appears after typing
'autoindent' 'ai' take indent for new line from previous line
'autoread' 'ar' autom. read file when changed outside of Vim
'autowrite' 'aw' automatically write file if changed
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -120,6 +120,16 @@ vim.o.ac = vim.o.autocomplete
vim.go.autocomplete = vim.o.autocomplete
vim.go.ac = vim.go.autocomplete
+--- Delay in milliseconds before the autocomplete menu appears after
+--- typing. If you prefer it not to open too quickly, set this value
+--- slightly above your typing speed. See `ins-autocompletion`.
+---
+--- @type integer
+vim.o.autocompletedelay = 0
+vim.o.acl = vim.o.autocompletedelay
+vim.go.autocompletedelay = vim.o.autocompletedelay
+vim.go.acl = vim.go.autocompletedelay
+
--- Copy indent from current line when starting a new line (typing <CR>
--- in Insert mode or when using the "o" or "O" command). If you do not
--- type anything on the new line except <BS> or CTRL-D and then type
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
@@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
-" Last Change: 2025 Aug 07
+" Last Change: 2025 Aug 16
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one.
@@ -737,6 +737,8 @@ if has("insert_expand")
call <SID>OptionL("cpt")
call <SID>AddOption("autocomplete", gettext("automatic completion in insert mode"))
call <SID>BinOptionG("ac", &ac)
+ call <SID>AddOption("autocompletedelay", gettext("delay in msec before menu appears after typing"))
+ call append("$", " \tset acl=" . &acl)
call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion"))
call <SID>OptionL("cot")
call <SID>AddOption("completeitemalign", gettext("popup menu item align order"))
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
@@ -849,10 +849,11 @@ static int insert_handle_key(InsertState *s)
auto_format(false, true);
if (s->did_backspace && p_ac && !char_avail() && curwin->w_cursor.col > 0) {
s->c = char_before_cursor();
- if (ins_compl_setup_autocompl(s->c)) {
+ if (vim_isprintc(s->c)) {
redraw_later(curwin, UPD_VALID);
update_screen(); // Show char deletion immediately
ui_flush();
+ ins_compl_enable_autocomplete();
insert_do_complete(s); // Trigger autocompletion
return 1;
}
@@ -1233,10 +1234,11 @@ normalchar:
// closed fold.
foldOpenCursor();
// Trigger autocompletion
- if (p_ac && !char_avail() && ins_compl_setup_autocompl(s->c)) {
+ if (p_ac && !char_avail() && vim_isprintc(s->c)) {
redraw_later(curwin, UPD_VALID);
update_screen(); // Show character immediately
ui_flush();
+ ins_compl_enable_autocomplete();
insert_do_complete(s);
}
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
@@ -2206,6 +2206,13 @@ static void ins_compl_new_leader(void)
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1);
compl_used_match = false;
+ if (p_acl > 0) {
+ pum_undisplay(true);
+ redraw_later(curwin, UPD_VALID);
+ update_screen(); // Show char (deletion) immediately
+ ui_flush();
+ }
+
if (compl_started) {
ins_compl_set_original_text(compl_leader.data, compl_leader.size);
if (is_cpt_func_refresh_always()) {
@@ -4811,10 +4818,10 @@ static int ins_compl_get_exp(pos_T *ini)
found_new_match = FAIL;
}
- int i = -1; // total of matches, unknown
+ int match_count = -1; // total of matches, unknown
if (found_new_match == FAIL
|| (ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())) {
- i = ins_compl_make_cyclic();
+ match_count = ins_compl_make_cyclic();
}
if (cfc_has_mode() && compl_get_longest && compl_num_bests > 0) {
@@ -4838,7 +4845,7 @@ static int ins_compl_get_exp(pos_T *ini)
sort_compl_match_list(cp_compare_nearest);
}
- return i;
+ return match_count;
}
/// Update "compl_shown_match" to the actually shown match, it may differ when
@@ -5288,7 +5295,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
/// collecting, and halve the timeout.
static void check_elapsed_time(void)
{
- if (cpt_sources_array == NULL) {
+ if (cpt_sources_array == NULL || cpt_sources_index < 0) {
return;
}
@@ -6015,6 +6022,10 @@ static void ins_compl_show_statusmsg(void)
/// Returns OK if completion was done, FAIL if something failed.
int ins_complete(int c, bool enable_pum)
{
+ const bool disable_ac_delay = compl_started && ctrl_x_mode_normal()
+ && (c == Ctrl_N || c == Ctrl_P || c == Ctrl_R
+ || ins_compl_pum_key(c));
+
compl_direction = ins_compl_key2dir(c);
int insert_match = ins_compl_use_match(c);
@@ -6026,6 +6037,10 @@ int ins_complete(int c, bool enable_pum)
return FAIL;
}
+ uint64_t compl_start_tv = 0; ///< Time when match collection starts
+ if (compl_autocomplete && p_acl > 0 && !disable_ac_delay) {
+ compl_start_tv = os_hrtime();
+ }
compl_curr_win = curwin;
compl_curr_buf = curwin->w_buffer;
compl_shown_match = compl_curr_match;
@@ -6050,7 +6065,8 @@ int ins_complete(int c, bool enable_pum)
}
// we found no match if the list has only the "compl_orig_text"-entry
- if (is_first_match(compl_first_match->cp_next)) {
+ bool no_matches_found = is_first_match(compl_first_match->cp_next);
+ if (no_matches_found) {
// remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode,
// because we couldn't expand anything at first place, but if we used
// ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word
@@ -6074,6 +6090,22 @@ int ins_complete(int c, bool enable_pum)
ins_compl_show_statusmsg();
}
+ // Wait for the autocompletion delay to expire
+ if (compl_autocomplete && p_acl > 0 && !disable_ac_delay && !no_matches_found
+ && (os_hrtime() - compl_start_tv) / 1000000 < (uint64_t)p_acl) {
+ setcursor();
+ ui_flush();
+ do {
+ if (char_avail()) {
+ ins_compl_restart();
+ compl_interrupted = true;
+ break;
+ } else {
+ os_delay(2L, true);
+ }
+ } while ((os_hrtime() - compl_start_tv) / 1000000 < (uint64_t)p_acl);
+ }
+
// Show the popup menu, unless we got interrupted.
if (enable_pum && !compl_interrupted) {
show_pum(save_w_wrow, save_w_leftcol);
@@ -6084,15 +6116,10 @@ int ins_complete(int c, bool enable_pum)
return OK;
}
-/// Returns true if the given character 'c' can be used to trigger
-/// autocompletion.
-bool ins_compl_setup_autocompl(int c)
+/// Enable autocompletion
+void ins_compl_enable_autocomplete(void)
{
- if (vim_isprintc(c)) {
- compl_autocomplete = true;
- return true;
- }
- return false;
+ compl_autocomplete = true;
}
/// Remove (if needed) and show the popup menu
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
@@ -301,6 +301,7 @@ EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign'
EXTERN char *p_cot; ///< 'completeopt'
EXTERN unsigned cot_flags; ///< flags from 'completeopt'
EXTERN int p_ac; ///< 'autocomplete'
+EXTERN OptInt p_acl; ///< 'autocompletedelay'
#ifdef BACKSLASH_IN_FILENAME
EXTERN char *p_csl; ///< 'completeslash'
#endif
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -242,6 +242,20 @@ local options = {
varname = 'p_ac',
},
{
+ abbreviation = 'acl',
+ defaults = 0,
+ desc = [=[
+ Delay in milliseconds before the autocomplete menu appears after
+ typing. If you prefer it not to open too quickly, set this value
+ slightly above your typing speed. See |ins-autocompletion|.
+ ]=],
+ full_name = 'autocompletedelay',
+ scope = { 'global' },
+ short_desc = N_('delay in msec before menu appears after typing'),
+ type = 'number',
+ varname = 'p_acl',
+ },
+ {
abbreviation = 'ai',
defaults = true,
desc = [=[
diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
@@ -1409,4 +1409,157 @@ describe('completion', function()
{5:-- INSERT --} 4,6 All |
]])
end)
+
+ -- oldtest: Test_autocompletedelay()
+ it("'autocompletedelay' option", function()
+ source([[
+ call setline(1, ['foo', 'foobar', 'foobarbaz'])
+ set autocomplete
+ ]])
+ screen:try_resize(60, 10)
+ screen:expect([[
+ ^foo |
+ foobar |
+ foobarbaz |
+ {1:~ }|*6
+ |
+ ]])
+ screen.timeout = 200
+
+ feed('Gof')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^ |
+ {4:foobarbaz }{1: }|
+ {4:foobar }{1: }|
+ {4:foo }{1: }|
+ {1:~ }|*2
+ {5:-- INSERT --} |
+ ]])
+
+ feed('<Esc>')
+ command('set autocompletedelay=500')
+ feed('Sf')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^ |
+ {1:~ }|*5
+ {5:-- INSERT --} |
+ ]])
+ feed('o')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ fo^ |
+ {1:~ }|*5
+ {5:-- INSERT --} |
+ ]])
+ vim.uv.sleep(500)
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ fo^ |
+ {4:foobarbaz }{1: }|
+ {4:foobar }{1: }|
+ {4:foo }{1: }|
+ {1:~ }|*2
+ {5:-- INSERT --} |
+ ]])
+ feed('<BS>')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^ |
+ {1:~ }|*5
+ {5:-- INSERT --} |
+ ]])
+ vim.uv.sleep(500)
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^ |
+ {4:foobarbaz }{1: }|
+ {4:foobar }{1: }|
+ {4:foo }{1: }|
+ {1:~ }|*2
+ {5:-- INSERT --} |
+ ]])
+
+ -- During delay wait, user can open menu using CTRL_N completion
+ feed('<Esc>')
+ command('set completeopt=menuone,preinsert')
+ feed('Sf<C-N>')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^oo |
+ {12:foo }{1: }|
+ {4:foobar }{1: }|
+ {4:foobarbaz }{1: }|
+ {1:~ }|*2
+ {5:-- Keyword completion (^N^P) }{6:match 1 of 3} |
+ ]])
+
+ -- After the menu is open, ^N/^P and Up/Down should not delay
+ feed('<Esc>')
+ command('set completeopt=menu')
+ feed('Sf')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^ |
+ {1:~ }|*5
+ {5:-- INSERT --} |
+ ]])
+ vim.uv.sleep(500)
+ feed('<C-N>')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ foobarbaz^ |
+ {12:foobarbaz }{1: }|
+ {4:foobar }{1: }|
+ {4:foo }{1: }|
+ {1:~ }|*2
+ {5:-- INSERT --} |
+ ]])
+ feed('<Down>')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ foobarbaz^ |
+ {4:foobarbaz }{1: }|
+ {12:foobar }{1: }|
+ {4:foo }{1: }|
+ {1:~ }|*2
+ {5:-- INSERT --} |
+ ]])
+
+ -- When menu is not open Up/Down moves cursor to different line
+ feed('<Esc>Sf')
+ screen:expect([[
+ foo |
+ foobar |
+ foobarbaz |
+ f^ |
+ {1:~ }|*5
+ {5:-- INSERT --} |
+ ]])
+ feed('<Down>')
+ screen:expect_unchanged()
+
+ feed('<esc>')
+ end)
end)
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
@@ -5573,4 +5573,52 @@ func Test_omni_start_invalid_col()
set omnifunc& complete&
endfunc
+func Test_autocompletedelay()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ call setline(1, ['foo', 'foobar', 'foobarbaz'])
+ set autocomplete
+ [SCRIPT]
+ call writefile(lines, 'XTest_autocomplete_delay', 'D')
+ let buf = RunVimInTerminal('-S XTest_autocomplete_delay', {'rows': 10})
+
+ call term_sendkeys(buf, "Gof")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_1', {})
+
+ call term_sendkeys(buf, "\<Esc>:set autocompletedelay=500\<CR>")
+ call term_sendkeys(buf, "Sf")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_2', {})
+ call term_sendkeys(buf, "o")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_3', {})
+ sleep 500m
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_4', {})
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_5', {})
+ sleep 500m
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_6', {})
+
+ " During delay wait, user can open menu using CTRL_N completion
+ call term_sendkeys(buf, "\<Esc>:set completeopt=menuone,preinsert\<CR>")
+ call term_sendkeys(buf, "Sf\<C-N>")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_7', {})
+
+ " After the menu is open, ^N/^P and Up/Down should not delay
+ call term_sendkeys(buf, "\<Esc>:set completeopt=menu noruler\<CR>")
+ call term_sendkeys(buf, "\<Esc>Sf")
+ sleep 500ms
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_8', {})
+ call term_sendkeys(buf, "\<Down>")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_9', {})
+
+ " When menu is not open Up/Down moves cursor to different line
+ call term_sendkeys(buf, "\<Esc>Sf")
+ call term_sendkeys(buf, "\<Down>")
+ call VerifyScreenDump(buf, 'Test_autocompletedelay_10', {})
+
+ call term_sendkeys(buf, "\<esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab nofoldenable