patch 9.1.1301: completion: cannot configure completion functions with 'complete'

Problem:  completion: cannot configure completion functions with
          'complete'
Solution: add support for setting completion functions using the f and o
          flag for 'complete' (Girish Palya)

This change adds two new values to the `'complete'` (`'cpt'`) option:
- `f` – invokes the function specified by the `'completefunc'` option
- `f{func}` – invokes a specific function `{func}` (can be a string or `Funcref`)

These new flags extend keyword completion behavior (e.g., via `<C-N>` /
`<C-P>`) by allowing function-based sources to participate in standard keyword
completion.

**Key behaviors:**

- Multiple `f{func}` values can be specified, and all will be called in order.
- Functions should follow the interface defined in `:help complete-functions`.
- When using `f{func}`, escaping is required for spaces (with `\`) and commas
  (with `\\`) in `Funcref` names.
- If a function sets `'refresh'` to `'always'`, it will be re-invoked on every
  change to the input text. Otherwise, Vim will attempt to reuse and filter
  existing matches as the input changes, which matches the default behavior of
  other completion sources.
- Matches are inserted at the keyword boundary for consistency with other completion methods.
- If finding matches is time-consuming, `complete_check()` can be used to
  maintain responsiveness.
- Completion matches are gathered in the sequence defined by the `'cpt'`
  option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may
reduce the need for external completion plugins for many users.

**Examples:**

Complete matches from [LSP](https://github.com/yegappan/lsp) client. Notice the use of `refresh: always` and `function()`.

```vim
set cpt+=ffunction("g:LspCompletor"\\,\ [5]). # maxitems = 5

def! g:LspCompletor(maxitems: number, findstart: number, base: string): any
    if findstart == 1
        return g:LspOmniFunc(findstart, base)
    endif
    return {words: g:LspOmniFunc(findstart, base)->slice(0, maxitems), refresh: 'always'}
enddef
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

Complete matches from `:iabbrev`.

```vim
set cpt+=fAbbrevCompletor

def! g:AbbrevCompletor(findstart: number, base: string): any
    if findstart > 0
        var prefix = getline('.')->strpart(0, col('.') - 1)->matchstr('\S\+$')
        if prefix->empty()
            return -2
        endif
        return col('.') - prefix->len() - 1
    endif
    var lines = execute('ia', 'silent!')
    if lines =~? gettext('No abbreviation found')
        return v:none  # Suppresses warning message
    endif
    var items = []
    for line in lines->split("\n")
        var m = line->matchlist('\v^i\s+\zs(\S+)\s+(.*)$')
        if m->len() > 2 && m[1]->stridx(base) == 0
            items->add({ word: m[1], info: m[2], dup: 1 })
        endif
    endfor
    return items->empty() ? v:none :
        items->sort((v1, v2) => v1.word < v2.word ? -1 : v1.word ==# v2.word ? 0 : 1)
enddef
```

**Auto-completion:**

Vim's standard completion frequently checks for user input while searching for
new matches. It is responsive irrespective of file size. This makes it
well-suited for smooth auto-completion. You can try with above examples:

```vim
set cot=menuone,popup,noselect inf

autocmd TextChangedI * InsComplete()

def InsComplete()
    if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
        SkipTextChangedIEvent()
        feedkeys("\<c-n>", "n")
    endif
enddef

inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedIEvent()<cr><c-e>

def SkipTextChangedIEvent(): string
    # Suppress next event caused by <c-e> (or <c-n> when no matches found)
    set eventignore+=TextChangedI
    timer_start(1, (_) => {
        set eventignore-=TextChangedI
    })
    return ''
enddef
```

closes: #17065

Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Co-authored-by: glepnir <glephunter@gmail.com>
Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Girish Palya
2025-04-14 22:13:15 +02:00
committed by Christian Brabandt
parent 10f69298b4
commit cbe53191d0
8 changed files with 1119 additions and 95 deletions

View File

@ -1,4 +1,4 @@
*insert.txt* For Vim version 9.1. Last change: 2025 Mar 09 *insert.txt* For Vim version 9.1. Last change: 2025 Apr 14
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -1167,6 +1167,9 @@ For example, the function can contain this: >
let matches = ... list of words ... let matches = ... list of words ...
return {'words': matches, 'refresh': 'always'} return {'words': matches, 'refresh': 'always'}
< <
If looking for matches is time-consuming, |complete_check()| may be used to
maintain responsiveness.
*complete-items* *complete-items*
Each list item can either be a string or a Dictionary. When it is a string it Each list item can either be a string or a Dictionary. When it is a string it
is used as the completion. When it is a Dictionary it can contain these is used as the completion. When it is a Dictionary it can contain these

View File

@ -1,4 +1,4 @@
*options.txt* For Vim version 9.1. Last change: 2025 Apr 13 *options.txt* For Vim version 9.1. Last change: 2025 Apr 14
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -2085,6 +2085,28 @@ A jump table for the options with a short description can be found at |Q_op|.
|i_CTRL-X_CTRL-D| |i_CTRL-X_CTRL-D|
] tag completion ] tag completion
t same as "]" t same as "]"
f{func} call the function {func}. Multiple "f" flags may be specified.
Refer to |complete-functions| for details on how the function
is invoked and what it should return. The value can be the
name of a function or a |Funcref|. For |Funcref| values,
spaces must be escaped with a backslash ('\'), and commas with
double backslashes ('\\') (see |option-backslash|).
If the Dict returned by the {func} includes {"refresh": "always"},
the function will be invoked again whenever the leading text
changes.
Completion matches are always inserted at the keyword
boundary, regardless of the column returned by {func} when
a:findstart is 1. This ensures compatibility with other
completion sources.
To make further modifications to the inserted text, {func}
can make use of |CompleteDonePre|.
If generating matches is potentially slow, |complete_check()|
should be used to avoid blocking and preserve editor
responsiveness.
f equivalent to using "f{func}", where the function is taken from
the 'completefunc' option.
o equivalent to using "f{func}", where the function is taken from
the 'omnifunc' option.
Unloaded buffers are not loaded, thus their autocmds |:autocmd| are Unloaded buffers are not loaded, thus their autocmds |:autocmd| are
not executed, this may lead to unexpected completions from some files not executed, this may lead to unexpected completions from some files

View File

@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.1. Last change: 2025 Apr 12 *version9.txt* For Vim version 9.1. Last change: 2025 Apr 14
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -41617,6 +41617,10 @@ Completion: ~
- improved commandline completion for the |:hi| command - improved commandline completion for the |:hi| command
- New option value for 'wildmode': - New option value for 'wildmode':
"noselect" - do not auto select an entry in the wildmenu "noselect" - do not auto select an entry in the wildmenu
- New flags for 'complete':
"f{func}" - complete using given function
"f" - complete using 'completefunc'
"o" - complete using 'omnifunc'
Options: ~ Options: ~
- the default for 'commentstring' contains whitespace padding to have - the default for 'commentstring' contains whitespace padding to have

View File

@ -109,6 +109,7 @@ struct compl_S
int cp_in_match_array; // collected by compl_match_array int cp_in_match_array; // collected by compl_match_array
int cp_user_abbr_hlattr; // highlight attribute for abbr int cp_user_abbr_hlattr; // highlight attribute for abbr
int cp_user_kind_hlattr; // highlight attribute for kind int cp_user_kind_hlattr; // highlight attribute for kind
int cp_cpt_value_idx; // index of this match's source in 'cpt' option
}; };
// values for cp_flags // values for cp_flags
@ -124,7 +125,7 @@ struct compl_S
* "compl_first_match" points to the start of the list. * "compl_first_match" points to the start of the list.
* "compl_curr_match" points to the currently selected entry. * "compl_curr_match" points to the currently selected entry.
* "compl_shown_match" is different from compl_curr_match during * "compl_shown_match" is different from compl_curr_match during
* ins_compl_get_exp(). * ins_compl_get_exp(), when new matches are added to the list.
* "compl_old_match" points to previous "compl_curr_match". * "compl_old_match" points to previous "compl_curr_match".
*/ */
static compl_T *compl_first_match = NULL; static compl_T *compl_first_match = NULL;
@ -171,7 +172,10 @@ static int compl_started = FALSE;
static int ctrl_x_mode = CTRL_X_NORMAL; static int ctrl_x_mode = CTRL_X_NORMAL;
static int compl_matches = 0; // number of completion matches static int compl_matches = 0; // number of completion matches
static string_T compl_pattern = {NULL, 0}; static string_T compl_pattern = {NULL, 0}; // search pattern for matching items
#ifdef FEAT_COMPL_FUNC
static string_T cpt_compl_pattern = {NULL, 0}; // pattern returned by func in 'cpt'
#endif
static int compl_direction = FORWARD; static int compl_direction = FORWARD;
static int compl_shows_dir = FORWARD; static int compl_shows_dir = FORWARD;
static int compl_pending = 0; // > 1 for postponed CTRL-N static int compl_pending = 0; // > 1 for postponed CTRL-N
@ -208,6 +212,10 @@ static int compl_selected_item = -1;
static int *compl_fuzzy_scores; static int *compl_fuzzy_scores;
static int *cpt_func_refresh_always; // array indicating which 'cpt' functions have 'refresh:always' set
static int cpt_value_count; // total number of completion sources specified in the 'cpt' option
static int cpt_value_idx; // index of the current completion source being expanded
// "compl_match_array" points the currently displayed list of entries in the // "compl_match_array" points the currently displayed list of entries in the
// popup menu. It is NULL when there is no popup menu. // popup menu. It is NULL when there is no popup menu.
static pumitem_T *compl_match_array = NULL; static pumitem_T *compl_match_array = NULL;
@ -227,7 +235,14 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg);
# if defined(FEAT_COMPL_FUNC) || defined(FEAT_EVAL) # if defined(FEAT_COMPL_FUNC) || defined(FEAT_EVAL)
static void ins_compl_add_list(list_T *list); static void ins_compl_add_list(list_T *list);
static void ins_compl_add_dict(dict_T *dict); static void ins_compl_add_dict(dict_T *dict);
static int get_userdefined_compl_info(colnr_T curs_col, callback_T *cb, int *startcol);
static callback_T *get_cpt_func_callback(char_u *funcname);
static void get_cpt_func_completion_matches(callback_T *cb);
# endif # endif
static int cpt_compl_src_init(char_u *p_cpt);
static int is_cpt_func_refresh_always(void);
static void cpt_compl_src_clear(void);
static void cpt_compl_refresh(void);
static int ins_compl_key2dir(int c); static int ins_compl_key2dir(int c);
static int ins_compl_pum_key(int c); static int ins_compl_pum_key(int c);
static int ins_compl_key2count(int c); static int ins_compl_key2count(int c);
@ -873,6 +888,7 @@ ins_compl_add(
match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1; match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1;
match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1; match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1;
match->cp_score = score; match->cp_score = score;
match->cp_cpt_value_idx = cpt_value_idx;
if (cptext != NULL) if (cptext != NULL)
{ {
@ -1937,6 +1953,26 @@ find_line_end(char_u *ptr)
return s; return s;
} }
/*
* Free a completion item in the list
*/
static void
ins_compl_item_free(compl_T *match)
{
int i;
VIM_CLEAR_STRING(match->cp_str);
// several entries may use the same fname, free it just once.
if (match->cp_flags & CP_FREE_FNAME)
vim_free(match->cp_fname);
for (i = 0; i < CPT_COUNT; ++i)
vim_free(match->cp_text[i]);
#ifdef FEAT_EVAL
clear_tv(&match->cp_user_data);
#endif
vim_free(match);
}
/* /*
* Free the list of completions * Free the list of completions
*/ */
@ -1944,7 +1980,6 @@ find_line_end(char_u *ptr)
ins_compl_free(void) ins_compl_free(void)
{ {
compl_T *match; compl_T *match;
int i;
VIM_CLEAR_STRING(compl_pattern); VIM_CLEAR_STRING(compl_pattern);
VIM_CLEAR_STRING(compl_leader); VIM_CLEAR_STRING(compl_leader);
@ -1960,16 +1995,7 @@ ins_compl_free(void)
{ {
match = compl_curr_match; match = compl_curr_match;
compl_curr_match = compl_curr_match->cp_next; compl_curr_match = compl_curr_match->cp_next;
VIM_CLEAR_STRING(match->cp_str); ins_compl_item_free(match);
// several entries may use the same fname, free it just once.
if (match->cp_flags & CP_FREE_FNAME)
vim_free(match->cp_fname);
for (i = 0; i < CPT_COUNT; ++i)
vim_free(match->cp_text[i]);
#ifdef FEAT_EVAL
clear_tv(&match->cp_user_data);
#endif
vim_free(match);
} while (compl_curr_match != NULL && !is_first_match(compl_curr_match)); } while (compl_curr_match != NULL && !is_first_match(compl_curr_match));
compl_first_match = compl_curr_match = NULL; compl_first_match = compl_curr_match = NULL;
compl_shown_match = NULL; compl_shown_match = NULL;
@ -1993,6 +2019,7 @@ ins_compl_clear(void)
edit_submode_extra = NULL; edit_submode_extra = NULL;
VIM_CLEAR_STRING(compl_orig_text); VIM_CLEAR_STRING(compl_orig_text);
compl_enter_selects = FALSE; compl_enter_selects = FALSE;
cpt_compl_src_clear();
#ifdef FEAT_EVAL #ifdef FEAT_EVAL
// clear v:completed_item // clear v:completed_item
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED)); set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED));
@ -2026,8 +2053,8 @@ ins_compl_win_active(win_T *wp UNUSED)
} }
/* /*
* Selected one of the matches. When FALSE the match was edited or using the * Selected one of the matches. When FALSE, the match was either edited or
* longest common string. * using the longest common string.
*/ */
int int
ins_compl_used_match(void) ins_compl_used_match(void)
@ -2185,7 +2212,11 @@ ins_compl_new_leader(void)
compl_used_match = FALSE; compl_used_match = FALSE;
if (compl_started) if (compl_started)
{
ins_compl_set_original_text(compl_leader.string, compl_leader.length); ins_compl_set_original_text(compl_leader.string, compl_leader.length);
if (is_cpt_func_refresh_always())
cpt_compl_refresh();
}
else else
{ {
#ifdef FEAT_SPELL #ifdef FEAT_SPELL
@ -2298,6 +2329,7 @@ ins_compl_restart(void)
compl_matches = 0; compl_matches = 0;
compl_cont_status = 0; compl_cont_status = 0;
compl_cont_mode = 0; compl_cont_mode = 0;
cpt_compl_src_clear();
} }
/* /*
@ -3051,24 +3083,30 @@ get_insert_callback(int type)
/* /*
* Execute user defined complete function 'completefunc', 'omnifunc' or * Execute user defined complete function 'completefunc', 'omnifunc' or
* 'thesaurusfunc', and get matches in "matches". * 'thesaurusfunc', and get matches in "matches".
* "type" is either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS. * "type" can be one of CTRL_X_OMNI, CTRL_X_FUNCTION, or CTRL_X_THESAURUS.
* Callback function "cb" is set if triggered by a function in the 'cpt'
* option; otherwise, it is NULL.
*/ */
static void static void
expand_by_function(int type, char_u *base) expand_by_function(int type, char_u *base, callback_T *cb)
{ {
list_T *matchlist = NULL; list_T *matchlist = NULL;
dict_T *matchdict = NULL; dict_T *matchdict = NULL;
typval_T args[3]; typval_T args[3];
char_u *funcname; char_u *funcname;
pos_T pos; pos_T pos;
callback_T *cb;
typval_T rettv; typval_T rettv;
int save_State = State; int save_State = State;
int retval; int retval;
int is_cpt_function = (cb != NULL);
funcname = get_complete_funcname(type); if (!is_cpt_function)
if (*funcname == NUL) {
return; funcname = get_complete_funcname(type);
if (*funcname == NUL)
return;
cb = get_insert_callback(type);
}
// Call 'completefunc' to obtain the list of matches. // Call 'completefunc' to obtain the list of matches.
args[0].v_type = VAR_NUMBER; args[0].v_type = VAR_NUMBER;
@ -3083,7 +3121,6 @@ expand_by_function(int type, char_u *base)
// Insert mode in another buffer. // Insert mode in another buffer.
++textlock; ++textlock;
cb = get_insert_callback(type);
retval = call_callback(cb, 0, &rettv, 2, args); retval = call_callback(cb, 0, &rettv, 2, args);
// Call a function, which returns a list or dict. // Call a function, which returns a list or dict.
@ -3650,6 +3687,7 @@ typedef struct
int found_all; // found all matches of a certain type. int found_all; // found all matches of a certain type.
char_u *dict; // dictionary file to search char_u *dict; // dictionary file to search
int dict_f; // "dict" is an exact file name or not int dict_f; // "dict" is an exact file name or not
callback_T *func_cb; // callback of function in 'cpt' option
} ins_compl_next_state_T; } ins_compl_next_state_T;
/* /*
@ -3763,6 +3801,19 @@ process_next_cpt_value(
st->dict_f = DICT_FIRST; st->dict_f = DICT_FIRST;
} }
} }
#ifdef FEAT_COMPL_FUNC
else if (*st->e_cpt == 'f' || *st->e_cpt == 'o')
{
compl_type = CTRL_X_FUNCTION;
if (*st->e_cpt == 'o')
st->func_cb = &curbuf->b_ofu_cb;
else
st->func_cb = (*++st->e_cpt != ',' && *st->e_cpt != NUL)
? get_cpt_func_callback(st->e_cpt) : &curbuf->b_cfu_cb;
if (!st->func_cb)
compl_type = -1;
}
#endif
#ifdef FEAT_FIND_ID #ifdef FEAT_FIND_ID
else if (*st->e_cpt == 'i') else if (*st->e_cpt == 'i')
compl_type = CTRL_X_PATH_PATTERNS; compl_type = CTRL_X_PATH_PATTERNS;
@ -3821,7 +3872,7 @@ get_next_dict_tsr_completion(int compl_type, char_u *dict, int dict_f)
{ {
#ifdef FEAT_COMPL_FUNC #ifdef FEAT_COMPL_FUNC
if (thesaurus_func_complete(compl_type)) if (thesaurus_func_complete(compl_type))
expand_by_function(compl_type, compl_pattern.string); expand_by_function(compl_type, compl_pattern.string, NULL);
else else
#endif #endif
ins_compl_dictionaries( ins_compl_dictionaries(
@ -4412,6 +4463,38 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
return found_new_match; return found_new_match;
} }
/*
* Return the callback function associated with "funcname".
*/
#ifdef FEAT_COMPL_FUNC
static callback_T *
get_cpt_func_callback(char_u *funcname)
{
static callback_T cb;
char_u buf[LSIZE];
int slen;
slen = copy_option_part(&funcname, buf, LSIZE, ",");
if (slen > 0 && option_set_callback_func(buf, &cb))
return &cb;
return NULL;
}
/*
* Retrieve new completion matches by invoking callback "cb".
*/
static void
expand_cpt_function(callback_T *cb)
{
// Re-insert the text removed by ins_compl_delete().
ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1);
// Get matches
get_cpt_func_completion_matches(cb);
// Undo insertion
ins_compl_delete();
}
#endif
/* /*
* get the next set of completion matches for "type". * get the next set of completion matches for "type".
* Returns TRUE if a new match is found. Otherwise returns FALSE. * Returns TRUE if a new match is found. Otherwise returns FALSE.
@ -4453,8 +4536,13 @@ get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini)
#ifdef FEAT_COMPL_FUNC #ifdef FEAT_COMPL_FUNC
case CTRL_X_FUNCTION: case CTRL_X_FUNCTION:
if (ctrl_x_mode_normal()) // Invoked by a func in 'cpt' option
expand_cpt_function(st->func_cb);
else
expand_by_function(type, compl_pattern.string, NULL);
break;
case CTRL_X_OMNI: case CTRL_X_OMNI:
expand_by_function(type, compl_pattern.string); expand_by_function(type, compl_pattern.string, NULL);
break; break;
#endif #endif
@ -4513,6 +4601,10 @@ ins_compl_get_exp(pos_T *ini)
? (char_u *)"." : curbuf->b_p_cpt); ? (char_u *)"." : curbuf->b_p_cpt);
st.e_cpt = st.e_cpt_copy == NULL ? (char_u *)"" : st.e_cpt_copy; st.e_cpt = st.e_cpt_copy == NULL ? (char_u *)"" : st.e_cpt_copy;
st.last_match_pos = st.first_match_pos = *ini; st.last_match_pos = st.first_match_pos = *ini;
if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval())
&& !cpt_compl_src_init(st.e_cpt))
return FAIL;
} }
else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf))
st.ins_buf = curbuf; // In case the buffer was wiped out. st.ins_buf = curbuf; // In case the buffer was wiped out.
@ -4522,7 +4614,7 @@ ins_compl_get_exp(pos_T *ini)
? &st.last_match_pos : &st.first_match_pos; ? &st.last_match_pos : &st.first_match_pos;
// For ^N/^P loop over all the flags/windows/buffers in 'complete'. // For ^N/^P loop over all the flags/windows/buffers in 'complete'.
for (;;) for (cpt_value_idx = 0;;)
{ {
found_new_match = FAIL; found_new_match = FAIL;
st.set_match_pos = FALSE; st.set_match_pos = FALSE;
@ -4538,7 +4630,10 @@ ins_compl_get_exp(pos_T *ini)
if (status == INS_COMPL_CPT_END) if (status == INS_COMPL_CPT_END)
break; break;
if (status == INS_COMPL_CPT_CONT) if (status == INS_COMPL_CPT_CONT)
{
cpt_value_idx++;
continue; continue;
}
} }
// If complete() was called then compl_pattern has been reset. The // If complete() was called then compl_pattern has been reset. The
@ -4549,6 +4644,9 @@ ins_compl_get_exp(pos_T *ini)
// get the next set of completion matches // get the next set of completion matches
found_new_match = get_next_completion_match(type, &st, ini); found_new_match = get_next_completion_match(type, &st, ini);
if (type > 0)
cpt_value_idx++;
// break the loop for specialized modes (use 'complete' just for the // break the loop for specialized modes (use 'complete' just for the
// generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new // generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new
// match // match
@ -4575,6 +4673,7 @@ ins_compl_get_exp(pos_T *ini)
compl_started = FALSE; compl_started = FALSE;
} }
} }
cpt_value_idx = -1;
compl_started = TRUE; compl_started = TRUE;
if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval())
@ -5478,9 +5577,13 @@ get_cmdline_compl_info(char_u *line, colnr_T curs_col)
* 'completefunc' and 'thesaurusfunc') * 'completefunc' and 'thesaurusfunc')
* Sets the global variables: compl_col, compl_length and compl_pattern. * Sets the global variables: compl_col, compl_length and compl_pattern.
* Uses the global variable: spell_bad_len * Uses the global variable: spell_bad_len
* Callback function "cb" is set if triggered by a function in the 'cpt'
* option; otherwise, it is NULL.
* "startcol", when not NULL, contains the column returned by function.
*/ */
static int static int
get_userdefined_compl_info(colnr_T curs_col UNUSED) get_userdefined_compl_info(colnr_T curs_col UNUSED, callback_T *cb UNUSED,
int *startcol UNUSED)
{ {
int ret = FAIL; int ret = FAIL;
@ -5493,16 +5596,22 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED)
char_u *funcname; char_u *funcname;
pos_T pos; pos_T pos;
int save_State = State; int save_State = State;
callback_T *cb; int len;
string_T *compl_pat;
int is_cpt_function = (cb != NULL);
// Call 'completefunc' or 'omnifunc' or 'thesaurusfunc' and get pattern if (!is_cpt_function)
// length as a string
funcname = get_complete_funcname(ctrl_x_mode);
if (*funcname == NUL)
{ {
semsg(_(e_option_str_is_not_set), ctrl_x_mode_function() // Call 'completefunc' or 'omnifunc' or 'thesaurusfunc' and get pattern
? "completefunc" : "omnifunc"); // length as a string
return FAIL; funcname = get_complete_funcname(ctrl_x_mode);
if (*funcname == NUL)
{
semsg(_(e_option_str_is_not_set), ctrl_x_mode_function()
? "completefunc" : "omnifunc");
return FAIL;
}
cb = get_insert_callback(ctrl_x_mode);
} }
args[0].v_type = VAR_NUMBER; args[0].v_type = VAR_NUMBER;
@ -5512,7 +5621,6 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED)
args[2].v_type = VAR_UNKNOWN; args[2].v_type = VAR_UNKNOWN;
pos = curwin->w_cursor; pos = curwin->w_cursor;
++textlock; ++textlock;
cb = get_insert_callback(ctrl_x_mode);
col = call_callback_retnr(cb, 2, args); col = call_callback_retnr(cb, 2, args);
--textlock; --textlock;
@ -5526,6 +5634,9 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED)
return FAIL; return FAIL;
} }
if (startcol != NULL)
*startcol = col;
// Return value -2 means the user complete function wants to cancel the // Return value -2 means the user complete function wants to cancel the
// complete without an error, do the same if the function did not execute // complete without an error, do the same if the function did not execute
// successfully. // successfully.
@ -5534,6 +5645,8 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED)
// Return value -3 does the same as -2 and leaves CTRL-X mode. // Return value -3 does the same as -2 and leaves CTRL-X mode.
if (col == -3) if (col == -3)
{ {
if (is_cpt_function)
return FAIL;
ctrl_x_mode = CTRL_X_NORMAL; ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL; edit_submode = NULL;
if (!shortmess(SHM_COMPLETIONMENU)) if (!shortmess(SHM_COMPLETIONMENU))
@ -5546,24 +5659,27 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED)
compl_opt_refresh_always = FALSE; compl_opt_refresh_always = FALSE;
compl_opt_suppress_empty = FALSE; compl_opt_suppress_empty = FALSE;
if (col < 0) if (col < 0 || col > curs_col)
col = curs_col; col = curs_col;
compl_col = col;
if (compl_col > curs_col)
compl_col = curs_col;
// Setup variables for completion. Need to obtain "line" again, // Setup variables for completion. Need to obtain "line" again,
// it may have become invalid. // it may have become invalid.
line = ml_get(curwin->w_cursor.lnum); line = ml_get(curwin->w_cursor.lnum);
compl_length = curs_col - compl_col; len = curs_col - col;
compl_pattern.string = vim_strnsave(line + compl_col, (size_t)compl_length); compl_pat = is_cpt_function ? &cpt_compl_pattern : &compl_pattern;
if (compl_pattern.string == NULL) compl_pat->string = vim_strnsave(line + col, (size_t)len);
if (compl_pat->string == NULL)
{ {
compl_pattern.length = 0; compl_pat->length = 0;
return FAIL; return FAIL;
} }
compl_pat->length = (size_t)compl_length;
compl_pattern.length = (size_t)compl_length; if (!is_cpt_function)
{
compl_col = col;
compl_length = len;
}
ret = OK; ret = OK;
#endif #endif
@ -5644,7 +5760,7 @@ compl_get_info(char_u *line, int startcol, colnr_T curs_col, int *line_invalid)
else if (ctrl_x_mode_function() || ctrl_x_mode_omni() else if (ctrl_x_mode_function() || ctrl_x_mode_omni()
|| thesaurus_func_complete(ctrl_x_mode)) || thesaurus_func_complete(ctrl_x_mode))
{ {
if (get_userdefined_compl_info(curs_col) == FAIL) if (get_userdefined_compl_info(curs_col, NULL, NULL) != OK)
return FAIL; return FAIL;
*line_invalid = TRUE; // "line" may have become invalid *line_invalid = TRUE; // "line" may have become invalid
} }
@ -6130,3 +6246,220 @@ spell_back_to_badword(void)
start_arrow(&tpos); start_arrow(&tpos);
} }
#endif #endif
/*
* Reset the info associated with completion sources.
*/
static void
cpt_compl_src_clear(void)
{
VIM_CLEAR(cpt_func_refresh_always);
cpt_value_idx = -1;
cpt_value_count = 0;
}
/*
* Initialize the info associated with completion sources.
*/
static int
cpt_compl_src_init(char_u *cpt_str)
{
int count = 0;
char_u *p = cpt_str;
while (*p)
{
while (*p == ',' || *p == ' ') // Skip delimiters
p++;
if (*p) // If not end of string, count this segment
{
count++;
copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p
}
}
cpt_compl_src_clear();
cpt_value_count = count;
if (count > 0)
{
cpt_func_refresh_always = ALLOC_CLEAR_MULT(int, count);
if (cpt_func_refresh_always == NULL)
{
cpt_value_count = 0;
return FAIL;
}
}
return OK;
}
/*
* Return TRUE if any of the completion sources have 'refresh' set to 'always'.
*/
static int
is_cpt_func_refresh_always(void)
{
#ifdef FEAT_COMPL_FUNC
int i;
for (i = 0; i < cpt_value_count; i++)
if (cpt_func_refresh_always[i])
return TRUE;
#endif
return FALSE;
}
/*
* Make the completion list non-cyclic.
*/
#ifdef FEAT_COMPL_FUNC
static void
ins_compl_make_linear(void)
{
compl_T *m;
if (compl_first_match == NULL || compl_first_match->cp_prev == NULL)
return;
m = compl_first_match->cp_prev;
m->cp_next = NULL;
compl_first_match->cp_prev = NULL;
}
#endif
/*
* Remove the matches linked to the current completion source (as indicated by
* cpt_value_idx) from the completion list.
*/
#ifdef FEAT_COMPL_FUNC
static compl_T *
remove_old_matches(void)
{
compl_T *sublist_start = NULL, *sublist_end = NULL, *insert_at = NULL;
compl_T *current, *next;
int compl_shown_removed = FALSE;
int forward = compl_dir_forward();
// Identify the sublist of old matches that needs removal
for (current = compl_first_match; current != NULL; current = current->cp_next)
{
if (current->cp_cpt_value_idx < cpt_value_idx && (forward || (!forward && !insert_at)))
insert_at = current;
if (current->cp_cpt_value_idx == cpt_value_idx)
{
if (!sublist_start)
sublist_start = current;
sublist_end = current;
if (!compl_shown_removed && compl_shown_match == current)
compl_shown_removed = TRUE;
}
if ((forward && current->cp_cpt_value_idx > cpt_value_idx) || (!forward && insert_at))
break;
}
// Re-assign compl_shown_match if necessary
if (compl_shown_removed)
{
if (forward)
compl_shown_match = compl_first_match;
else
{ // Last node will have the prefix that is being completed
for (current = compl_first_match; current->cp_next != NULL; current = current->cp_next)
;
compl_shown_match = current;
}
}
if (!sublist_start) // No nodes to remove
return insert_at;
// Update links to remove sublist
if (sublist_start->cp_prev)
sublist_start->cp_prev->cp_next = sublist_end->cp_next;
else
compl_first_match = sublist_end->cp_next;
if (sublist_end->cp_next)
sublist_end->cp_next->cp_prev = sublist_start->cp_prev;
// Free all nodes in the sublist
sublist_end->cp_next = NULL;
for (current = sublist_start; current != NULL; current = next)
{
next = current->cp_next;
ins_compl_item_free(current);
}
return insert_at;
}
#endif
/*
* Retrieve completion matches using the callback function "cb" and store the
* 'refresh:always' flag.
*/
#ifdef FEAT_COMPL_FUNC
static void
get_cpt_func_completion_matches(callback_T *cb UNUSED)
{
int ret;
int startcol;
VIM_CLEAR_STRING(cpt_compl_pattern);
ret = get_userdefined_compl_info(curwin->w_cursor.col, cb, &startcol);
if (ret == FAIL && startcol == -3)
cpt_func_refresh_always[cpt_value_idx] = FALSE;
else if (ret == OK)
{
expand_by_function(0, cpt_compl_pattern.string, cb);
cpt_func_refresh_always[cpt_value_idx] = compl_opt_refresh_always;
compl_opt_refresh_always = FALSE;
}
}
#endif
/*
* Retrieve completion matches from functions in the 'cpt' option where the
* 'refresh:always' flag is set.
*/
static void
cpt_compl_refresh(void)
{
#ifdef FEAT_COMPL_FUNC
char_u *cpt;
char_u *p;
callback_T *cb;
// Make the completion list linear (non-cyclic)
ins_compl_make_linear();
// Make a copy of 'cpt' in case the buffer gets wiped out
cpt = vim_strsave(curbuf->b_p_cpt);
cpt_value_idx = 0;
for (p = cpt; *p; cpt_value_idx++)
{
while (*p == ',' || *p == ' ') // Skip delimiters
p++;
if (cpt_func_refresh_always[cpt_value_idx])
{
if (*p == 'o')
cb = &curbuf->b_ofu_cb;
else if (*p == 'f')
cb = (*(p + 1) != ',' && *(p + 1) != NUL)
? get_cpt_func_callback(p + 1) : &curbuf->b_cfu_cb;
if (cb)
{
compl_curr_match = remove_old_matches();
get_cpt_func_completion_matches(cb);
}
}
copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p
}
cpt_value_idx = -1;
vim_free(cpt);
// Make the list cyclic
compl_matches = ins_compl_make_cyclic();
#endif
}

View File

@ -1552,48 +1552,57 @@ did_set_commentstring(optset_T *args)
#endif #endif
/* /*
* The 'complete' option is changed. * Check if value for 'complete' is valid when 'complete' option is changed.
*/ */
char * char *
did_set_complete(optset_T *args) did_set_complete(optset_T *args)
{ {
char_u **varp = (char_u **)args->os_varp; char_u **varp = (char_u **)args->os_varp;
char_u *s; char_u *p = NULL;
char_u buffer[LSIZE];
char_u *buf_ptr;
int escape;
// check if it is a valid value for 'complete' -- Acevedo for (p = *varp; *p; )
for (s = *varp; *s;)
{ {
while (*s == ',' || *s == ' ') vim_memset(buffer, 0, LSIZE);
s++; buf_ptr = buffer;
if (!*s) escape = 0;
break;
if (vim_strchr((char_u *)".wbuksid]tU", *s) == NULL) // Extract substring while handling escaped commas
return illegal_char(args->os_errbuf, args->os_errbuflen, *s); while (*p && (*p != ',' || escape) && buf_ptr < (buffer + LSIZE - 1))
if (*++s != NUL && *s != ',' && *s != ' ')
{ {
if (s[-1] == 'k' || s[-1] == 's') if (*p == '\\' && *(p + 1) == ',')
{ {
// skip optional filename after 'k' and 's' escape = 1; // Mark escape mode
while (*s && *s != ',' && *s != ' ') p++; // Skip '\'
{
if (*s == '\\' && s[1] != NUL)
++s;
++s;
}
} }
else else
{ {
if (args->os_errbuf != NULL) escape = 0;
{ *buf_ptr++ = *p;
vim_snprintf((char *)args->os_errbuf, args->os_errbuflen, }
_(e_illegal_character_after_chr), *--s); p++;
return args->os_errbuf; }
} *buf_ptr = NUL;
return "";
if (vim_strchr((char_u *)".wbuksid]tUfo", *buffer) == NULL)
return illegal_char(args->os_errbuf, args->os_errbuflen, *buffer);
if (!vim_strchr((char_u *)"ksf", *buffer) && *(buffer + 1) != NUL)
{
if (args->os_errbuf)
{
vim_snprintf((char *)args->os_errbuf, args->os_errbuflen,
_(e_illegal_character_after_chr), *buffer);
return args->os_errbuf;
} }
} }
}
// Skip comma and spaces
while (*p == ',' || *p == ' ')
p++;
}
return NULL; return NULL;
} }
@ -1601,7 +1610,7 @@ did_set_complete(optset_T *args)
expand_set_complete(optexpand_T *args, int *numMatches, char_u ***matches) expand_set_complete(optexpand_T *args, int *numMatches, char_u ***matches)
{ {
static char *(p_cpt_values[]) = { static char *(p_cpt_values[]) = {
".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", ".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", "f", "o",
NULL}; NULL};
return expand_set_opt_string( return expand_set_opt_string(
args, args,

View File

@ -130,10 +130,15 @@ func Test_omni_dash()
new new
exe "normal Gofind -\<C-x>\<C-o>" exe "normal Gofind -\<C-x>\<C-o>"
call assert_equal("find -help", getline('$')) call assert_equal("find -help", getline('$'))
%d
set complete=o
exe "normal Gofind -\<C-n>"
" 'complete' inserts at 'iskeyword' boundary (so you get --help)
call assert_equal("find --help", getline('$'))
bwipe! bwipe!
delfunc Omni delfunc Omni
set omnifunc= set omnifunc= complete&
endfunc endfunc
func Test_omni_throw() func Test_omni_throw()
@ -153,11 +158,21 @@ func Test_omni_throw()
call assert_exception('he he he') call assert_exception('he he he')
call assert_equal(1, g:CallCount) call assert_equal(1, g:CallCount)
endtry endtry
%d
set complete=o
let g:CallCount = 0
try
exe "normal ifoo\<C-n>"
call assert_false(v:true, 'command should have failed')
catch
call assert_exception('he he he')
call assert_equal(1, g:CallCount)
endtry
bwipe! bwipe!
delfunc Omni delfunc Omni
unlet g:CallCount unlet g:CallCount
set omnifunc= set omnifunc= complete&
endfunc endfunc
func Test_omni_autoload() func Test_omni_autoload()
@ -210,6 +225,16 @@ func Test_completefunc_args()
call assert_equal(0, s:args[1][0]) call assert_equal(0, s:args[1][0])
set omnifunc= set omnifunc=
set complete=fCompleteFunc
call feedkeys("i\<C-N>\<Esc>", 'x')
call assert_equal([1, 1], s:args[0])
call assert_equal(0, s:args[1][0])
set complete=o
call feedkeys("i\<C-N>\<Esc>", 'x')
call assert_equal([1, 1], s:args[0])
call assert_equal(0, s:args[1][0])
set complete&
bwipe! bwipe!
unlet s:args unlet s:args
delfunc CompleteFunc delfunc CompleteFunc
@ -255,7 +280,7 @@ func s:CompleteDone_CheckCompletedItemDict(pre)
call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] ) call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] )
if a:pre if a:pre
call assert_equal('function', complete_info().mode) call assert_equal(a:pre == 1 ? 'function' : 'keyword', complete_info().mode)
endif endif
let s:called_completedone = 1 let s:called_completedone = 1
@ -272,7 +297,15 @@ func Test_CompleteDoneNone()
call assert_true(s:called_completedone) call assert_true(s:called_completedone)
call assert_equal(oldline, newline) call assert_equal(oldline, newline)
let s:called_completedone = 0
set complete=f<SID>CompleteDone_CompleteFuncNone
execute "normal a\<C-N>\<C-Y>"
set complete&
let newline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '')
call assert_true(s:called_completedone)
call assert_equal(oldline, newline)
let s:called_completedone = 0 let s:called_completedone = 0
au! CompleteDone au! CompleteDone
endfunc endfunc
@ -293,6 +326,7 @@ func Test_CompleteDone_vevent_keys()
endfunc endfunc
set omnifunc=CompleteFunc set omnifunc=CompleteFunc
set completefunc=CompleteFunc set completefunc=CompleteFunc
set complete=.,fCompleteFunc
set completeopt+=menuone set completeopt+=menuone
new new
@ -316,7 +350,11 @@ func Test_CompleteDone_vevent_keys()
call assert_equal('vim', g:complete_word) call assert_equal('vim', g:complete_word)
call assert_equal('keyword', g:complete_type) call assert_equal('keyword', g:complete_type)
call feedkeys("Shello vim visual v\<C-X>\<C-N>\<C-Y>", 'tx') call feedkeys("Shello vim visual v\<C-N>\<ESC>", 'tx')
call assert_equal('', g:complete_word)
call assert_equal('keyword', g:complete_type)
call feedkeys("Shello vim visual v\<C-N>\<C-Y>", 'tx')
call assert_equal('vim', g:complete_word) call assert_equal('vim', g:complete_word)
call assert_equal('keyword', g:complete_type) call assert_equal('keyword', g:complete_type)
@ -374,6 +412,21 @@ func Test_CompleteDoneDict()
call assert_true(s:called_completedone) call assert_true(s:called_completedone)
let s:called_completedone = 0 let s:called_completedone = 0
au! CompleteDonePre
au! CompleteDone
au CompleteDonePre * :call <SID>CompleteDone_CheckCompletedItemDict(2)
au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict(0)
set complete=.,f<SID>CompleteDone_CompleteFuncDict
execute "normal a\<C-N>\<C-Y>"
set complete&
call assert_equal(['one', 'two'], v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone)
let s:called_completedone = 0
au! CompleteDonePre
au! CompleteDone au! CompleteDone
endfunc endfunc
@ -416,6 +469,15 @@ func Test_CompleteDoneDictNoUserData()
call assert_equal('', v:completed_item[ 'user_data' ]) call assert_equal('', v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone) call assert_true(s:called_completedone)
let s:called_completedone = 0
set complete=.,f<SID>CompleteDone_CompleteFuncDictNoUserData
execute "normal a\<C-N>\<C-Y>"
set complete&
call assert_equal('', v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone)
let s:called_completedone = 0 let s:called_completedone = 0
au! CompleteDone au! CompleteDone
endfunc endfunc
@ -449,6 +511,24 @@ func Test_CompleteDoneList()
call assert_equal('', v:completed_item[ 'user_data' ]) call assert_equal('', v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone) call assert_true(s:called_completedone)
let s:called_completedone = 0
set complete=.,f<SID>CompleteDone_CompleteFuncList
execute "normal a\<C-N>\<C-Y>"
set complete&
call assert_equal('', v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone)
let s:called_completedone = 0
set complete=.,f
execute "normal a\<C-N>\<C-Y>"
set complete&
call assert_equal('', v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone)
let s:called_completedone = 0 let s:called_completedone = 0
au! CompleteDone au! CompleteDone
endfunc endfunc
@ -492,11 +572,51 @@ func Test_completefunc_info()
set completefunc=CompleteTest set completefunc=CompleteTest
call feedkeys("i\<C-X>\<C-U>\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx") call feedkeys("i\<C-X>\<C-U>\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
call assert_equal("matched{'pum_visible': 1, 'mode': 'function', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1)) call assert_equal("matched{'pum_visible': 1, 'mode': 'function', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1))
bwipe! %d
set complete=.,fCompleteTest
call feedkeys("i\<C-N>\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
call assert_equal("matched{'pum_visible': 1, 'mode': 'keyword', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1))
%d
set complete=.,f
call feedkeys("i\<C-N>\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
call assert_equal("matched{'pum_visible': 1, 'mode': 'keyword', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1))
set completeopt& set completeopt&
set complete&
set completefunc& set completefunc&
endfunc endfunc
func Test_cpt_func_cursorcol()
func CptColTest(findstart, query)
if a:findstart
call assert_equal("foo bar", getline(1))
call assert_equal(8, col('.'))
return col('.')
endif
call assert_equal("foo bar", getline(1))
call assert_equal(8, col('.'))
return v:none
endfunc
set complete=fCptColTest
new
call feedkeys("ifoo bar\<C-N>", "tx")
bwipe!
new
set completeopt=longest
call feedkeys("ifoo bar\<C-N>", "tx")
bwipe!
new
set completeopt=menuone
call feedkeys("ifoo bar\<C-N>", "tx")
bwipe!
new
set completeopt=menuone,preinsert
call feedkeys("ifoo bar\<C-N>", "tx")
bwipe!
set complete& completeopt&
delfunc CptColTest
endfunc
func ScrollInfoWindowUserDefinedFn(findstart, query) func ScrollInfoWindowUserDefinedFn(findstart, query)
" User defined function (i_CTRL-X_CTRL-U) " User defined function (i_CTRL-X_CTRL-U)
if a:findstart if a:findstart
@ -552,24 +672,34 @@ func CompleteInfoUserDefinedFn(findstart, query)
endfunc endfunc
func CompleteInfoTestUserDefinedFn(mvmt, idx, noselect) func CompleteInfoTestUserDefinedFn(mvmt, idx, noselect)
new
if a:noselect if a:noselect
set completeopt=menuone,popup,noinsert,noselect set completeopt=menuone,popup,noinsert,noselect
else else
set completeopt=menu,preview set completeopt=menu,preview
endif endif
set completefunc=CompleteInfoUserDefinedFn let items = "[" .
call feedkeys("i\<C-X>\<C-U>" . a:mvmt . "\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
let completed = a:idx != -1 ? ['foo', 'bar', 'baz', 'qux']->get(a:idx) : ''
call assert_equal(completed. "{'pum_visible': 1, 'mode': 'function', 'selected': " . a:idx . ", 'items': [" .
\ "{'word': 'foo', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " . \ "{'word': 'foo', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " .
\ "{'word': 'bar', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " . \ "{'word': 'bar', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " .
\ "{'word': 'baz', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " . \ "{'word': 'baz', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " .
\ "{'word': 'qux', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}" . \ "{'word': 'qux', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}" .
\ "]}", getline(1)) \ "]"
new
set completefunc=CompleteInfoUserDefinedFn
call feedkeys("i\<C-X>\<C-U>" . a:mvmt . "\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
let completed = a:idx != -1 ? ['foo', 'bar', 'baz', 'qux']->get(a:idx) : ''
call assert_equal(completed. "{'pum_visible': 1, 'mode': 'function', 'selected': " . a:idx . ", 'items': " . items . "}", getline(1))
%d
set complete=.,fCompleteInfoUserDefinedFn
call feedkeys("i\<C-N>" . a:mvmt . "\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
let completed = a:idx != -1 ? ['foo', 'bar', 'baz', 'qux']->get(a:idx) : ''
call assert_equal(completed. "{'pum_visible': 1, 'mode': 'keyword', 'selected': " . a:idx . ", 'items': " . items . "}", getline(1))
%d
set complete=.,f
call feedkeys("i\<C-N>" . a:mvmt . "\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
let completed = a:idx != -1 ? ['foo', 'bar', 'baz', 'qux']->get(a:idx) : ''
call assert_equal(completed. "{'pum_visible': 1, 'mode': 'keyword', 'selected': " . a:idx . ", 'items': " . items . "}", getline(1))
bwipe! bwipe!
set completeopt& set completeopt& completefunc& complete&
set completefunc&
endfunc endfunc
func Test_complete_info_user_defined_fn() func Test_complete_info_user_defined_fn()
@ -839,6 +969,10 @@ func Test_completefunc_error()
set completefunc=CompleteFunc set completefunc=CompleteFunc
call setline(1, ['', 'abcd', '']) call setline(1, ['', 'abcd', ''])
call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:') call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:')
set complete=fCompleteFunc
call assert_fails('exe "normal 2G$a\<C-N>"', 'E565:')
set complete=f
call assert_fails('exe "normal 2G$a\<C-N>"', 'E565:')
" delete text when called for the second time " delete text when called for the second time
func CompleteFunc2(findstart, base) func CompleteFunc2(findstart, base)
@ -851,6 +985,10 @@ func Test_completefunc_error()
set completefunc=CompleteFunc2 set completefunc=CompleteFunc2
call setline(1, ['', 'abcd', '']) call setline(1, ['', 'abcd', ''])
call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:') call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:')
set complete=fCompleteFunc2
call assert_fails('exe "normal 2G$a\<C-N>"', 'E565:')
set complete=f
call assert_fails('exe "normal 2G$a\<C-N>"', 'E565:')
" Jump to a different window from the complete function " Jump to a different window from the complete function
func CompleteFunc3(findstart, base) func CompleteFunc3(findstart, base)
@ -863,9 +1001,15 @@ func Test_completefunc_error()
set completefunc=CompleteFunc3 set completefunc=CompleteFunc3
new new
call assert_fails('exe "normal a\<C-X>\<C-U>"', 'E565:') call assert_fails('exe "normal a\<C-X>\<C-U>"', 'E565:')
%d
set complete=fCompleteFunc3
call assert_fails('exe "normal a\<C-N>"', 'E565:')
%d
set complete=f
call assert_fails('exe "normal a\<C-N>"', 'E565:')
close! close!
set completefunc& set completefunc& complete&
delfunc CompleteFunc delfunc CompleteFunc
delfunc CompleteFunc2 delfunc CompleteFunc2
delfunc CompleteFunc3 delfunc CompleteFunc3
@ -884,7 +1028,15 @@ func Test_completefunc_invalid_data()
set completefunc=CompleteFunc set completefunc=CompleteFunc
exe "normal i\<C-X>\<C-U>" exe "normal i\<C-X>\<C-U>"
call assert_equal('moon', getline(1)) call assert_equal('moon', getline(1))
set completefunc& %d
set complete=fCompleteFunc
exe "normal i\<C-N>"
call assert_equal('moon', getline(1))
%d
set complete=f
exe "normal i\<C-N>"
call assert_equal('moon', getline(1))
set completefunc& complete&
close! close!
endfunc endfunc
@ -1557,18 +1709,363 @@ func Test_complete_item_refresh_always()
return #{words: res, refresh: 'always'} return #{words: res, refresh: 'always'}
endif endif
endfunc endfunc
new
set completeopt=menu,longest set completeopt=menu,longest
set completefunc=Tcomplete set completefunc=Tcomplete
new
exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>" exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>"
call assert_equal('up', getline(1)) call assert_equal('up', getline(1))
call assert_equal(6, g:CallCount) call assert_equal(6, g:CallCount)
set completeopt& %d
set completefunc& let g:CallCount = 0
set complete=fTcomplete
exe "normal! iup\<C-N>\<BS>\<BS>\<BS>\<BS>\<BS>"
call assert_equal('up', getline(1))
call assert_equal(6, g:CallCount)
%d
let g:CallCount = 0
set complete=f
exe "normal! iup\<C-N>\<BS>\<BS>\<BS>\<BS>\<BS>"
call assert_equal('up', getline(1))
call assert_equal(6, g:CallCount)
%d
let g:CallCount = 0
set omnifunc=Tcomplete
set complete=o
exe "normal! iup\<C-N>\<BS>\<BS>\<BS>\<BS>\<BS>"
call assert_equal('up', getline(1))
call assert_equal(6, g:CallCount)
bw! bw!
set completeopt&
set complete&
set completefunc&
delfunc Tcomplete delfunc Tcomplete
endfunc endfunc
" Test for 'cpt' user func that fails (return -2/-3) when refresh:always
func Test_cpt_func_refresh_always_fail()
func! CompleteFail(retval, findstart, base)
if a:findstart
return a:retval
endif
call assert_equal(-999, a:findstart) " Should not reach here
endfunc
new
set complete=ffunction('CompleteFail'\\,\ [-2])
exe "normal! ia\<C-N>"
%d
set complete=ffunction('CompleteFail'\\,\ [-3])
exe "normal! ia\<C-N>"
bw!
func! CompleteFailIntermittent(retval, findstart, base)
if a:findstart
if g:CallCount == 2
let g:CallCount += 1
return a:retval
endif
return col('.') - 1
endif
let g:CallCount += 1
let res = [[], ['foo', 'fbar'], ['foo1', 'foo2'], ['foofail'], ['fooo3']]
return #{words: res[g:CallCount], refresh: 'always'}
endfunc
new
set completeopt=menuone,noselect
set complete=ffunction('CompleteFailIntermittent'\\,\ [-2])
let g:CallCount = 0
exe "normal! if\<C-N>\<c-r>=complete_info([\"items\"])\<cr>"
call assert_match('''word'': ''foo''.*''word'': ''fbar''', getline(1))
call assert_equal(1, g:CallCount)
%d
let g:CallCount = 0
exe "normal! if\<C-N>o\<c-r>=complete_info([\"items\", \"selected\"])\<cr>"
call assert_match('''selected'': -1.*''word'': ''foo1''.*''word'': ''foo2''', getline(1))
call assert_equal(2, g:CallCount)
%d
set complete=ffunction('CompleteFailIntermittent'\\,\ [-3])
let g:CallCount = 0
exe "normal! if\<C-N>o\<c-r>=complete_info([\"items\", \"selected\"])\<cr>"
call assert_match('''selected'': -1.*''word'': ''foo1''.*''word'': ''foo2''', getline(1))
call assert_equal(2, g:CallCount)
%d
set complete=ffunction('CompleteFailIntermittent'\\,\ [-2])
" completion mode is dismissed when there are no matches in list
let g:CallCount = 0
exe "normal! if\<C-N>oo\<c-r>=complete_info([\"items\"])\<cr>"
call assert_equal('foo{''items'': []}', getline(1))
call assert_equal(3, g:CallCount)
%d
let g:CallCount = 0
exe "normal! if\<C-N>oo\<bs>\<c-r>=complete_info([\"items\"])\<cr>"
call assert_equal('fo{''items'': []}', getline(1))
call assert_equal(3, g:CallCount)
%d
" completion mode continues when matches from other sources present
set complete=.,ffunction('CompleteFailIntermittent'\\,\ [-2])
call setline(1, 'fooo1')
let g:CallCount = 0
exe "normal! Gof\<C-N>oo\<c-r>=complete_info([\"items\", \"selected\"])\<cr>"
call assert_equal('foo{''selected'': -1, ''items'': [{''word'': ''fooo1'', ''menu'': '''', '
\ . '''user_data'': '''', ''info'': '''', ''kind'': '''', ''abbr'': ''''}]}',
\ getline(2))
call assert_equal(3, g:CallCount)
%d
call setline(1, 'fooo1')
let g:CallCount = 0
exe "normal! Gof\<C-N>oo\<bs>\<c-r>=complete_info([\"items\"])\<cr>"
call assert_match('''word'': ''fooo1''.*''word'': ''fooo3''', getline(2))
call assert_equal(4, g:CallCount)
%d
" refresh will stop when -3 is returned
set complete=.,,\ ffunction('CompleteFailIntermittent'\\,\ [-3])
call setline(1, 'fooo1')
let g:CallCount = 0
exe "normal! Gof\<C-N>o\<bs>\<c-r>=complete_info([\"items\", \"selected\"])\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [{''word'': ''fooo1'', ''menu'': '''', '
\ . '''user_data'': '''', ''info'': '''', ''kind'': '''', ''abbr'': ''''}]}',
\ getline(2))
call assert_equal(3, g:CallCount)
%d
call setline(1, 'fooo1')
let g:CallCount = 0
exe "normal! Gof\<C-N>oo\<bs>\<c-r>=complete_info([\"items\", \"selected\"])\<cr>"
call assert_equal('fo{''selected'': -1, ''items'': [{''word'': ''fooo1'', ''menu'': '''', '
\ . '''user_data'': '''', ''info'': '''', ''kind'': '''', ''abbr'': ''''}]}',
\ getline(2))
call assert_equal(3, g:CallCount)
bw!
set complete& completeopt&
delfunc CompleteFail
delfunc CompleteFailIntermittent
endfunc
" Select items before they are removed by refresh:always
func Test_cpt_select_item_refresh_always()
func CompleteMenuWords()
let info = complete_info(["items", "selected"])
call map(info.items, {_, v -> v.word})
return info
endfunc
func! CompleteItemsSelect(compl, findstart, base)
if a:findstart
return col('.') - 1
endif
let g:CallCount += 1
if g:CallCount == 2
return #{words: a:compl, refresh: 'always'}
endif
let res = [[], ['fo', 'foobar'], [], ['foo1', 'foo2']]
return #{words: res[g:CallCount], refresh: 'always'}
endfunc
new
set complete=.,ffunction('CompleteItemsSelect'\\,\ [[]])
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<c-n>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('fo{''selected'': 1, ''items'': [''foobarbar'', ''fo'', ''foobar'']}', getline(2))
call assert_equal(1, g:CallCount)
%d
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-p>\<c-p>\<c-p>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('fo{''selected'': 0, ''items'': [''fo'', ''foobar'', ''foobarbar'']}', getline(2))
call assert_equal(1, g:CallCount)
%d
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<c-n>o\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('foo{''selected'': -1, ''items'': []}' , getline(2))
call assert_equal(1, g:CallCount)
%d
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<c-n>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [''foobarbar'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-p>\<c-p>\<c-p>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [''foobarbar'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
set complete=.,ffunction('CompleteItemsSelect'\\,\ [['foonext']])
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<c-n>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [''foobarbar'', ''foonext'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-p>\<c-p>\<c-p>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [''foonext'', ''foobarbar'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
call setline(1, "foob")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('foo{''selected'': 0, ''items'': [''foob'', ''foonext'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
call setline(1, "foob")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<bs>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('fo{''selected'': 0, ''items'': [''foob'', ''foo1'', ''foo2'']}', getline(2))
call assert_equal(3, g:CallCount)
%d
call setline(1, "foob")
let g:CallCount = 0
exe "normal! Gof\<c-p>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('foo{''selected'': 1, ''items'': [''foonext'', ''foob'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
call setline(1, "foob")
let g:CallCount = 0
exe "normal! Gof\<c-p>\<bs>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('fo{''selected'': 2, ''items'': [''foo1'', ''foo2'', ''foob'']}', getline(2))
call assert_equal(3, g:CallCount)
%d
set complete=.,ffunction('CompleteItemsSelect'\\,\ [['fo'\\,\ 'foonext']])
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-n>\<c-n>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [''foobarbar'', ''fo'', ''foonext'']}', getline(2))
call assert_equal(2, g:CallCount)
%d
call setline(1, "foobarbar")
let g:CallCount = 0
exe "normal! Gof\<c-p>\<c-p>\<c-p>\<bs>\<c-r>=CompleteMenuWords()\<cr>"
call assert_equal('f{''selected'': -1, ''items'': [''fo'', ''foonext'', ''foobarbar'']}', getline(2))
call assert_equal(2, g:CallCount)
bw!
set complete&
delfunc CompleteMenuWords
delfunc CompleteItemsSelect
endfunc
" Test two functions together, each returning refresh:always
func Test_cpt_multi_func_refresh_always()
func CompleteMenuMatches()
let info = complete_info(["matches", "selected"])
call map(info.matches, {_, v -> v.word})
return info
endfunc
func! CompleteItems1(findstart, base)
if a:findstart
return col('.') - 1
endif
let g:CallCount1 += 1
let res = [[], [], ['foo1', 'foobar1'], [], ['foo11', 'foo12'], [], ['foo13', 'foo14']]
return #{words: res[g:CallCount1], refresh: 'always'}
endfunc
func! CompleteItems2(findstart, base)
if a:findstart
return col('.') - 1
endif
let g:CallCount2 += 1
let res = [[], [], [], ['foo2', 'foobar2'], ['foo21', 'foo22'], ['foo23'], []]
return #{words: res[g:CallCount2], refresh: 'always'}
endfunc
set complete=
exe "normal! if\<C-N>\<c-r>=CompleteMenuMatches()\<cr>"
" \x0e is <c-n>
call assert_equal("f\x0e" . '{''matches'': [], ''selected'': -1}', getline(1))
set completeopt=menuone,noselect
set complete=fCompleteItems1,fCompleteItems2
new
let g:CallCount1 = 0
let g:CallCount2 = 0
exe "normal! if\<c-n>o\<c-n>o\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('foo{''matches'': [''foo2'', ''foobar2''], ''selected'': -1}', getline(1))
call assert_equal(3, g:CallCount1)
call assert_equal(3, g:CallCount2)
%d
let g:CallCount1 = 0
let g:CallCount2 = 0
exe "normal! if\<c-p>o\<c-p>o\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('foo{''matches'': [''foo2'', ''foobar2''], ''selected'': -1}', getline(1))
call assert_equal(3, g:CallCount1)
call assert_equal(3, g:CallCount2)
%d
let g:CallCount1 = 0
let g:CallCount2 = 0
exe "normal! if\<c-p>\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('f{''matches'': [], ''selected'': -1}', getline(1))
call assert_equal(1, g:CallCount1)
call assert_equal(1, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-n>\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('f{''matches'': [''foo1'', ''foobar1''], ''selected'': -1}', getline(1))
call assert_equal(2, g:CallCount2)
call assert_equal(2, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-n>o\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('fo{''matches'': [''foo2'', ''foobar2''], ''selected'': -1}', getline(1))
call assert_equal(3, g:CallCount2)
call assert_equal(3, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-p>o\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('fo{''matches'': [''foo2'', ''foobar2''], ''selected'': -1}', getline(1))
call assert_equal(3, g:CallCount2)
call assert_equal(3, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-n>oo\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('foo{''matches'': [''foo11'', ''foo12'', ''foo21'', ''foo22''], ''selected'': -1}', getline(1))
call assert_equal(4, g:CallCount2)
call assert_equal(4, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-n>oo\<bs>\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('fo{''matches'': [''foo23''], ''selected'': -1}', getline(1))
call assert_equal(5, g:CallCount2)
call assert_equal(5, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-p>oo\<bs>\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('fo{''matches'': [''foo23''], ''selected'': -1}', getline(1))
call assert_equal(5, g:CallCount2)
call assert_equal(5, g:CallCount2)
%d
let g:CallCount1 = 1
let g:CallCount2 = 1
exe "normal! if\<c-n>oo\<bs>o\<c-r>=CompleteMenuMatches()\<cr>"
call assert_equal('foo{''matches'': [''foo13'', ''foo14''], ''selected'': -1}', getline(1))
call assert_equal(6, g:CallCount2)
call assert_equal(6, g:CallCount2)
bw!
set complete& completeopt&
delfunc CompleteMenuMatches
delfunc CompleteItems1
delfunc CompleteItems2
endfunc
" Test for completing from a thesaurus file without read permission " Test for completing from a thesaurus file without read permission
func Test_complete_unreadable_thesaurus_file() func Test_complete_unreadable_thesaurus_file()
CheckUnix CheckUnix
@ -1608,6 +2105,143 @@ func Test_no_mapping_for_ctrl_x_key()
bwipe! bwipe!
endfunc endfunc
" Test for different ways of setting a function in 'complete' option
func Test_cpt_func_callback()
func CompleteFunc1(callnr, findstart, base)
call add(g:CompleteFunc1Args, [a:callnr, a:findstart, a:base])
return a:findstart ? 0 : []
endfunc
func CompleteFunc2(findstart, base)
call add(g:CompleteFunc2Args, [a:findstart, a:base])
return a:findstart ? 0 : []
endfunc
let lines =<< trim END
#" Test for using a global function name
set complete=fg:CompleteFunc2
new
call setline(1, 'global')
LET g:CompleteFunc2Args = []
call feedkeys("A\<C-N>\<Esc>", 'x')
call assert_equal([[1, ''], [0, 'global']], g:CompleteFunc2Args)
set complete&
bw!
#" Test for using a function()
set complete=ffunction('g:CompleteFunc1'\\,\ [10])
new
call setline(1, 'one')
LET g:CompleteFunc1Args = []
call feedkeys("A\<C-N>\<Esc>", 'x')
call assert_equal([[10, 1, ''], [10, 0, 'one']], g:CompleteFunc1Args)
set complete&
bw!
#" Using a funcref variable
set complete=ffuncref('g:CompleteFunc1'\\,\ [11])
new
call setline(1, 'two')
LET g:CompleteFunc1Args = []
call feedkeys("A\<C-N>\<Esc>", 'x')
call assert_equal([[11, 1, ''], [11, 0, 'two']], g:CompleteFunc1Args)
set complete&
bw!
END
call v9.CheckLegacyAndVim9Success(lines)
" Test for using a script-local function name
func s:CompleteFunc3(findstart, base)
call add(g:CompleteFunc3Args, [a:findstart, a:base])
return a:findstart ? 0 : []
endfunc
set complete=fs:CompleteFunc3
new
call setline(1, 'script1')
let g:CompleteFunc3Args = []
call feedkeys("A\<C-N>\<Esc>", 'x')
call assert_equal([[1, ''], [0, 'script1']], g:CompleteFunc3Args)
set complete&
bw!
let &complete = 'fs:CompleteFunc3'
new
call setline(1, 'script2')
let g:CompleteFunc3Args = []
call feedkeys("A\<C-N>\<Esc>", 'x')
call assert_equal([[1, ''], [0, 'script2']], g:CompleteFunc3Args)
bw!
delfunc s:CompleteFunc3
set complete&
" In Vim9 script s: can be omitted
let lines =<< trim END
vim9script
var CompleteFunc4Args = []
def CompleteFunc4(findstart: bool, base: string): any
add(CompleteFunc4Args, [findstart, base])
return findstart ? 0 : []
enddef
set complete=fCompleteFunc4
new
setline(1, 'script1')
feedkeys("A\<C-N>\<Esc>", 'x')
assert_equal([[1, ''], [0, 'script1']], CompleteFunc4Args)
set complete&
bw!
END
call v9.CheckScriptSuccess(lines)
" Vim9 tests
let lines =<< trim END
vim9script
def Vim9CompleteFunc(callnr: number, findstart: number, base: string): any
add(g:Vim9completeFuncArgs, [callnr, findstart, base])
return findstart ? 0 : []
enddef
# Test for using a def function with completefunc
set complete=ffunction('Vim9CompleteFunc'\\,\ [60])
new | only
setline(1, 'one')
g:Vim9completeFuncArgs = []
feedkeys("A\<C-N>\<Esc>", 'x')
assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs)
bw!
# Test for using a global function name
&complete = 'fg:CompleteFunc2'
new | only
setline(1, 'two')
g:CompleteFunc2Args = []
feedkeys("A\<C-N>\<Esc>", 'x')
assert_equal([[1, ''], [0, 'two']], g:CompleteFunc2Args)
bw!
# Test for using a script-local function name
def LocalCompleteFunc(findstart: number, base: string): any
add(g:LocalCompleteFuncArgs, [findstart, base])
return findstart ? 0 : []
enddef
&complete = 'fLocalCompleteFunc'
new | only
setline(1, 'three')
g:LocalCompleteFuncArgs = []
feedkeys("A\<C-N>\<Esc>", 'x')
assert_equal([[1, ''], [0, 'three']], g:LocalCompleteFuncArgs)
bw!
END
call v9.CheckScriptSuccess(lines)
" cleanup
set completefunc& complete&
delfunc CompleteFunc1
delfunc CompleteFunc2
unlet g:CompleteFunc1Args g:CompleteFunc2Args
%bw!
endfunc
" Test for different ways of setting the 'completefunc' option " Test for different ways of setting the 'completefunc' option
func Test_completefunc_callback() func Test_completefunc_callback()
func CompleteFunc1(callnr, findstart, base) func CompleteFunc1(callnr, findstart, base)
@ -2484,10 +3118,19 @@ endfunc
func Test_complete_smartindent() func Test_complete_smartindent()
new new
setlocal smartindent completefunc=FooBarComplete setlocal smartindent completefunc=FooBarComplete
exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>" exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>"
let result = getline(1,'$') let result = getline(1,'$')
call assert_equal(['', '{','}',''], result) call assert_equal(['', '{','}',''], result)
%d
setlocal complete=fFooBarComplete
exe "norm! o{\<cr>\<c-n>\<c-p>}\<cr>\<esc>"
let result = getline(1,'$')
call assert_equal(['', '{','}',''], result)
%d
setlocal complete=f
exe "norm! o{\<cr>\<c-n>\<c-p>}\<cr>\<esc>"
let result = getline(1,'$')
call assert_equal(['', '{','}',''], result)
bw! bw!
delfunction! FooBarComplete delfunction! FooBarComplete
endfunc endfunc

View File

@ -272,6 +272,14 @@ func Test_complete()
call feedkeys("i\<C-N>\<Esc>", 'xt') call feedkeys("i\<C-N>\<Esc>", 'xt')
bwipe! bwipe!
call assert_fails('set complete=ix', 'E535:') call assert_fails('set complete=ix', 'E535:')
call assert_fails('set complete=x', 'E539:')
call assert_fails('set complete=..', 'E535:')
set complete=.,w,b,u,k,\ s,i,d,],t,U,f,o
set complete=.
set complete+=ffuncref('foo'\\,\ [10])
set complete=ffuncref('foo'\\,\ [10])
set complete&
set complete+=ffunction('foo'\\,\ [10\\,\ 20])
set complete& set complete&
endfun endfun

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1301,
/**/ /**/
1300, 1300,
/**/ /**/