vim-patch:9.1.1672: completion: cannot add timeouts for 'cpt' sources (#35447)

Problem:  completion: cannot add timeouts for 'cpt' sources
          (Evgeni Chasnovski)
Solution: Add the 'autocompletetimeout' and 'completetimeout' options
          (Girish Palya)

fixes: vim/vim#17908
closes: vim/vim#17967

69a337edc1

Co-authored-by: Girish Palya <girishji@gmail.com>
This commit is contained in:
zeertzjq
2025-08-24 13:16:55 +08:00
committed by GitHub
parent c1fa3c7c37
commit 810a234978
9 changed files with 190 additions and 23 deletions

View File

@@ -1117,18 +1117,20 @@ 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' and 'autocompletedelay'.
The menu items are collected from the sources listed in the 'complete' option.
but triggered automatically. See 'autocomplete'. The menu items are collected
from the sources listed in the 'complete' option, in order.
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
(higher priority), but every source is guaranteed a time slice, however small.
A decaying timeout keeps Vim responsive. Sources earlier in the 'complete'
list get more time (higher priority), but all sources receive at least a small
time slice.
This mode is fully compatible with other completion modes. You can invoke
any of them at any time by typing |CTRL-X|, which temporarily suspends
autocompletion. To use |i_CTRL-N| specifically, press |CTRL-E| first to
dismiss the popup menu (see |complete_CTRL-E|).
See also 'autocomplete', 'autocompletetimeout' and 'autocompletedelay'.
To get LSP-driven auto-completion, see |lsp-completion|.

View File

@@ -754,6 +754,20 @@ A jump table for the options with a short description can be found at |Q_op|.
typing. If you prefer it not to open too quickly, set this value
slightly above your typing speed. See |ins-autocompletion|.
*'autocompletetimeout'* *'act'*
'autocompletetimeout' 'act' number (default 80)
global
Initial timeout (in milliseconds) for the decaying time-sliced
completion algorithm. Starts at this value, halves for each slower
source until a minimum is reached. All sources run, but slower ones
are quickly de-prioritized. The default is tuned so the popup menu
opens within ~200ms even with multiple slow sources on a slow system.
Changing this value is rarely needed. Only 80 or higher is valid.
Special case: when 'complete' contains "F" or "o" (function sources),
a longer timeout is used, allowing up to ~1s for sources such as LSP
servers that may sometimes take longer (e.g., while loading modules).
See |ins-autocompletion|.
*'autoindent'* *'ai'* *'noautoindent'* *'noai'*
'autoindent' 'ai' boolean (default on)
local to buffer
@@ -1688,6 +1702,12 @@ A jump table for the options with a short description can be found at |Q_op|.
For Insert mode completion the buffer-local value is used. For
command line completion the global value is used.
*'completetimeout'* *'cto'*
'completetimeout' 'cto' number (default 0)
global
Like 'autocompletetimeout', but applies to |i_CTRL-N| and |i_CTRL-P|
completion. Value of 0 disables the timeout; positive values allowed.
*'concealcursor'* *'cocu'*
'concealcursor' 'cocu' string (default "")
local to window

View File

@@ -627,6 +627,7 @@ Short explanation of each option: *option-list*
'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
'autocompletetimeout' 'act' initial decay timeout for autocompletion algorithm
'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
@@ -670,6 +671,7 @@ Short explanation of each option: *option-list*
'completefunc' 'cfu' function to be used for Insert mode completion
'completeopt' 'cot' options for Insert mode completion
'completeslash' 'csl' like 'shellslash' for completion
'completetimeout' 'cto' initial decay timeout for CTRL-N and CTRL-P
'concealcursor' 'cocu' whether concealable text is hidden in cursor line
'conceallevel' 'cole' whether concealable text is shown or hidden
'confirm' 'cf' ask what to do about unsaved/read-only files

View File

@@ -130,6 +130,23 @@ vim.o.acl = vim.o.autocompletedelay
vim.go.autocompletedelay = vim.o.autocompletedelay
vim.go.acl = vim.go.autocompletedelay
--- Initial timeout (in milliseconds) for the decaying time-sliced
--- completion algorithm. Starts at this value, halves for each slower
--- source until a minimum is reached. All sources run, but slower ones
--- are quickly de-prioritized. The default is tuned so the popup menu
--- opens within ~200ms even with multiple slow sources on a slow system.
--- Changing this value is rarely needed. Only 80 or higher is valid.
--- Special case: when 'complete' contains "F" or "o" (function sources),
--- a longer timeout is used, allowing up to ~1s for sources such as LSP
--- servers that may sometimes take longer (e.g., while loading modules).
--- See `ins-autocompletion`.
---
--- @type integer
vim.o.autocompletetimeout = 80
vim.o.act = vim.o.autocompletetimeout
vim.go.autocompletetimeout = vim.o.autocompletetimeout
vim.go.act = vim.go.autocompletetimeout
--- 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
@@ -1239,6 +1256,15 @@ vim.o.csl = vim.o.completeslash
vim.bo.completeslash = vim.o.completeslash
vim.bo.csl = vim.bo.completeslash
--- Like 'autocompletetimeout', but applies to `i_CTRL-N` and `i_CTRL-P`
--- completion. Value of 0 disables the timeout; positive values allowed.
---
--- @type integer
vim.o.completetimeout = 0
vim.o.cto = vim.o.completetimeout
vim.go.completetimeout = vim.o.completetimeout
vim.go.cto = vim.go.completetimeout
--- Sets the modes in which text in the cursor line can also be concealed.
--- When the current mode is listed then concealing happens just like in
--- other lines.

View File

@@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2025 Aug 16
" Last Change: 2025 Aug 23
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one.
@@ -737,6 +737,10 @@ 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("autocompletetimeout", gettext("initial decay timeout for 'autocomplete' algorithm"))
call append("$", " \tset act=" . &act)
call <SID>AddOption("completetimeout", gettext("initial decay timeout for CTRL-N and CTRL-P completion"))
call append("$", " \tset cto=" . &cto)
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"))

View File

@@ -289,6 +289,9 @@ static buf_T *compl_curr_buf = NULL; ///< buf where completion is active
// if the current source exceeds its timeout, it is interrupted and the next
// begins with half the time. A small minimum timeout ensures every source
// gets at least a brief chance.
// Special case: when 'complete' contains "F" or "o" (function sources), a
// 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 uint64_t compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS;
static bool compl_time_slice_expired = false; ///< time budget exceeded for current source
@@ -303,6 +306,10 @@ static bool compl_from_nonkeyword = false; ///< completion started from non-
} \
} while (0)
// Timeout values for F{func}, F and o values in 'complete'
#define COMPL_FUNC_TIMEOUT_MS 300
#define COMPL_FUNC_TIMEOUT_NON_KW_MS 1000
// List of flags for method of completion.
static int compl_cont_status = 0;
#define CONT_ADDING 1 ///< "normal" or "adding" expansion
@@ -4640,7 +4647,7 @@ static void prepare_cpt_compl_funcs(void)
/// Start the timer for the current completion source.
static void compl_source_start_timer(int source_idx)
{
if (compl_autocomplete && cpt_sources_array != NULL) {
if (compl_autocomplete || p_cto > 0) {
cpt_sources_array[source_idx].compl_start_tv = os_hrtime();
compl_time_slice_expired = false;
}
@@ -4657,8 +4664,6 @@ static int advance_cpt_sources_index_safe(void)
return FAIL;
}
#define COMPL_FUNC_TIMEOUT_MS 300
#define COMPL_FUNC_TIMEOUT_NON_KW_MS 1000
/// Get the next expansion(s), using "compl_pattern".
/// The search starts at position "ini" in curbuf and in the direction
/// compl_direction.
@@ -4708,12 +4713,17 @@ static int ins_compl_get_exp(pos_T *ini)
compl_old_match = compl_curr_match; // remember the last current match
st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos;
if (cpt_sources_array != NULL && ctrl_x_mode_normal() && !ctrl_x_mode_line_or_eval()
&& !(compl_cont_status & CONT_LOCAL)) {
bool normal_mode_strict = ctrl_x_mode_normal() && !ctrl_x_mode_line_or_eval()
&& !(compl_cont_status & CONT_LOCAL)
&& cpt_sources_array != NULL;
if (normal_mode_strict) {
cpt_sources_index = 0;
if (compl_autocomplete) {
if (compl_autocomplete || p_cto > 0) {
compl_source_start_timer(0);
compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS;
compl_time_slice_expired = false;
compl_timeout_ms = compl_autocomplete
? (uint64_t)MAX(COMPL_INITIAL_TIMEOUT_MS, p_act)
: (uint64_t)p_cto;
}
}
@@ -4743,12 +4753,15 @@ static int ins_compl_get_exp(pos_T *ini)
}
}
if (compl_autocomplete && type == CTRL_X_FUNCTION) {
uint64_t compl_timeout_save = 0;
if (normal_mode_strict && type == CTRL_X_FUNCTION
&& (compl_autocomplete || p_cto > 0)) {
// LSP servers may sporadically take >1s to respond (e.g., while
// loading modules), but other sources might already have matches.
// To show results quickly use a short timeout for keyword
// completion. Allow longer timeout for non-keyword completion
// where only function based sources (e.g. LSP) are active.
compl_timeout_save = compl_timeout_ms;
compl_timeout_ms = compl_from_nonkeyword
? COMPL_FUNC_TIMEOUT_NON_KW_MS : COMPL_FUNC_TIMEOUT_MS;
}
@@ -4796,9 +4809,10 @@ static int ins_compl_get_exp(pos_T *ini)
compl_started = false;
}
// Reset the timeout after collecting matches from function source
if (compl_autocomplete && type == CTRL_X_FUNCTION) {
compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS;
// Restore the timeout after collecting matches from function source
if (normal_mode_strict && type == CTRL_X_FUNCTION
&& (compl_autocomplete || p_cto > 0)) {
compl_timeout_ms = compl_timeout_save;
}
// For `^P` completion, reset `compl_curr_match` to the head to avoid
@@ -5295,10 +5309,6 @@ 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 || cpt_sources_index < 0) {
return;
}
uint64_t start_tv = cpt_sources_array[cpt_sources_index].compl_start_tv;
uint64_t elapsed_ms = (os_hrtime() - start_tv) / 1000000;
@@ -5355,8 +5365,13 @@ void ins_compl_check_keys(int frequency, bool in_compl_func)
vungetc(c);
}
}
} else if (compl_autocomplete) {
check_elapsed_time();
} else {
bool normal_mode_strict = ctrl_x_mode_normal() && !ctrl_x_mode_line_or_eval()
&& !(compl_cont_status & CONT_LOCAL)
&& cpt_sources_array != NULL && cpt_sources_index >= 0;
if (normal_mode_strict && (compl_autocomplete || p_cto > 0)) {
check_elapsed_time();
}
}
if (compl_pending != 0 && !got_int && !(cot_flags & kOptCotFlagNoinsert)

View File

@@ -292,6 +292,7 @@ EXTERN OptInt p_cwh; ///< 'cmdwinheight'
EXTERN OptInt p_ch; ///< 'cmdheight'
EXTERN char *p_cms; ///< 'commentstring'
EXTERN char *p_cpt; ///< 'complete'
EXTERN OptInt p_cto; ///< 'completetimeout'
EXTERN OptInt p_columns; ///< 'columns'
EXTERN int p_confirm; ///< 'confirm'
EXTERN char *p_cfc; ///< 'completefuzzycollect'
@@ -301,6 +302,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_act; ///< 'autocompletetimeout'
EXTERN OptInt p_acl; ///< 'autocompletedelay'
#ifdef BACKSLASH_IN_FILENAME
EXTERN char *p_csl; ///< 'completeslash'

View File

@@ -255,6 +255,27 @@ local options = {
type = 'number',
varname = 'p_acl',
},
{
abbreviation = 'act',
defaults = 80,
desc = [=[
Initial timeout (in milliseconds) for the decaying time-sliced
completion algorithm. Starts at this value, halves for each slower
source until a minimum is reached. All sources run, but slower ones
are quickly de-prioritized. The default is tuned so the popup menu
opens within ~200ms even with multiple slow sources on a slow system.
Changing this value is rarely needed. Only 80 or higher is valid.
Special case: when 'complete' contains "F" or "o" (function sources),
a longer timeout is used, allowing up to ~1s for sources such as LSP
servers that may sometimes take longer (e.g., while loading modules).
See |ins-autocompletion|.
]=],
full_name = 'autocompletetimeout',
scope = { 'global' },
short_desc = N_('initial decay timeout for autocompletion algorithm'),
type = 'number',
varname = 'p_act',
},
{
abbreviation = 'ai',
defaults = true,
@@ -1722,6 +1743,19 @@ local options = {
type = 'string',
varname = 'p_csl',
},
{
abbreviation = 'cto',
defaults = 0,
desc = [=[
Like 'autocompletetimeout', but applies to |i_CTRL-N| and |i_CTRL-P|
completion. Value of 0 disables the timeout; positive values allowed.
]=],
full_name = 'completetimeout',
scope = { 'global' },
short_desc = N_('initial decay timeout for CTRL-N and CTRL-P'),
type = 'number',
varname = 'p_cto',
},
{
abbreviation = 'cocu',
cb = 'did_set_concealcursor',

View File

@@ -5573,6 +5573,68 @@ func Test_omni_start_invalid_col()
set omnifunc& complete&
endfunc
func Test_completetimeout_autocompletetimeout()
func OmniFunc(findstart, base)
if a:findstart
return 1
else
return ['fooOmni']
endif
endfunc
set omnifunc=OmniFunc
call Ntest_override("char_avail", 1)
inoremap <F2> <Cmd>let b:matches = complete_info(["matches"]).matches<CR>
call setline(1, ['foobar', 'foobarbaz'])
new
call setline(1, ['foo', 'foobaz', ''])
set complete=.,o,w
call feedkeys("G", 'xt!')
set autocomplete
for tt in [1, 80, 1000, -1, 0]
exec $'set autocompletetimeout={tt}'
call feedkeys("\<Esc>Sf\<F2>\<Esc>0", 'xt!')
call assert_equal(['foobaz', 'foo', 'fooOmni', 'foobar', 'foobarbaz'], b:matches->mapnew('v:val.word'))
endfor
set autocomplete&
for tt in [80, 1000, -1, 0]
exec $'set completetimeout={tt}'
call feedkeys("\<Esc>Sf\<C-N>\<F2>\<Esc>0", 'xt!')
call assert_equal(['foo', 'foobaz', 'fooOmni', 'foobar', 'foobarbaz'], b:matches->mapnew('v:val.word'))
endfor
" Clock does not have fine granularity, so checking 'elapsed time' is only
" approximate. We can only test that some type of timeout is enforced.
call feedkeys("\<Esc>", 'xt!')
call setline(1, map(range(60000), '"foo" . v:val'))
set completetimeout=1
call feedkeys("Gof\<C-N>\<F2>\<Esc>0", 'xt!')
let match_count = len(b:matches->mapnew('v:val.word'))
call assert_true(match_count < 2000)
set completetimeout=1000
call feedkeys("\<Esc>Sf\<C-N>\<F2>\<Esc>0", 'xt!')
let match_count = len(b:matches->mapnew('v:val.word'))
call assert_true(match_count > 2000)
set autocomplete
set autocompletetimeout=81
call feedkeys("\<Esc>Sf\<F2>\<Esc>0", 'xt!')
let match_count = len(b:matches->mapnew('v:val.word'))
call assert_true(match_count < 50000)
call feedkeys("\<Esc>", 'xt!')
set complete& omnifunc& autocomplete& autocompletetimeout& completetimeout&
bwipe!
%d
call Ntest_override("char_avail", 0)
iunmap <F2>
delfunc OmniFunc
endfunc
func Test_autocompletedelay()
CheckScreendump