mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
vim-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: vim/vim#17065
cbe53191d0
Temporarily remove bufname completion with #if 0 to make merging easier.
Co-authored-by: Girish Palya <girishji@gmail.com>
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: glepnir <glephunter@gmail.com>
This commit is contained in:
@@ -1178,6 +1178,9 @@ For example, the function can contain this: >
|
||||
let matches = ... list of words ...
|
||||
return {'words': matches, 'refresh': 'always'}
|
||||
<
|
||||
If looking for matches is time-consuming, |complete_check()| may be used to
|
||||
maintain responsiveness.
|
||||
|
||||
*complete-items*
|
||||
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
|
||||
|
@@ -178,6 +178,10 @@ OPTIONS
|
||||
• 'chistory' and 'lhistory' set size of the |quickfix-stack|.
|
||||
• 'completefuzzycollect' enables fuzzy collection of candidates for (some)
|
||||
|ins-completion| modes.
|
||||
• 'complete' new flags:
|
||||
• "f{func}" complete using given function
|
||||
• "f" complete using 'completefunc'
|
||||
• "o" complete using 'omnifunc'
|
||||
• 'completeopt' flag "nearset" sorts completion results by distance to cursor.
|
||||
• 'diffopt' `inline:` configures diff highlighting for changes within a line.
|
||||
• 'grepformat' is now a |global-local| option.
|
||||
|
@@ -1506,6 +1506,28 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
] tag completion
|
||||
t same as "]"
|
||||
f scan the buffer names (as opposed to buffer contents)
|
||||
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
|
||||
not executed, this may lead to unexpected completions from some files
|
||||
|
22
runtime/lua/vim/_meta/options.lua
generated
22
runtime/lua/vim/_meta/options.lua
generated
@@ -1027,6 +1027,28 @@ vim.bo.cms = vim.bo.commentstring
|
||||
--- ] tag completion
|
||||
--- t same as "]"
|
||||
--- f scan the buffer names (as opposed to buffer contents)
|
||||
--- 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
|
||||
--- not executed, this may lead to unexpected completions from some files
|
||||
|
@@ -174,6 +174,7 @@ struct compl_S {
|
||||
bool cp_in_match_array; ///< collected by compl_match_array
|
||||
int cp_user_abbr_hlattr; ///< highlight attribute for abbr
|
||||
int cp_user_kind_hlattr; ///< highlight attribute for kind
|
||||
int cp_cpt_value_idx; ///< index of this match's source in 'cpt' option
|
||||
};
|
||||
|
||||
/// state information used for getting the next set of insert completion
|
||||
@@ -190,6 +191,7 @@ typedef struct {
|
||||
bool found_all; ///< found all matches of a certain type.
|
||||
char *dict; ///< dictionary file to search
|
||||
int dict_f; ///< "dict" is an exact file name or not
|
||||
Callback *func_cb; ///< callback of function in 'cpt' option
|
||||
} ins_compl_next_state_T;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
@@ -213,7 +215,7 @@ static const char e_compldel[] = N_("E840: Completion function deleted text");
|
||||
// "compl_first_match" points to the start of the list.
|
||||
// "compl_curr_match" points to the currently selected entry.
|
||||
// "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".
|
||||
|
||||
static compl_T *compl_first_match = NULL;
|
||||
@@ -259,7 +261,8 @@ static bool compl_started = false;
|
||||
static int ctrl_x_mode = CTRL_X_NORMAL;
|
||||
|
||||
static int compl_matches = 0; ///< number of completion matches
|
||||
static String compl_pattern = STRING_INIT;
|
||||
static String compl_pattern = STRING_INIT; ///< search pattern for matching items
|
||||
static String cpt_compl_pattern = STRING_INIT; ///< pattern returned by func in 'cpt'
|
||||
static Direction compl_direction = FORWARD;
|
||||
static Direction compl_shows_dir = FORWARD;
|
||||
static int compl_pending = 0; ///< > 1 for postponed CTRL-N
|
||||
@@ -302,6 +305,13 @@ static int compl_selected_item = -1;
|
||||
|
||||
static int *compl_fuzzy_scores;
|
||||
|
||||
/// array indicating which 'cpt' functions have 'refresh:always' set
|
||||
static bool *cpt_func_refresh_always;
|
||||
/// total number of completion sources specified in the 'cpt' option
|
||||
static int cpt_value_count;
|
||||
/// index of the current completion source being expanded
|
||||
static int cpt_value_idx;
|
||||
|
||||
// "compl_match_array" points the currently displayed list of entries in the
|
||||
// popup menu. It is NULL when there is no popup menu.
|
||||
static pumitem_T *compl_match_array = NULL;
|
||||
@@ -975,7 +985,6 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
|
||||
match = xcalloc(1, sizeof(compl_T));
|
||||
match->cp_number = flags & CP_ORIGINAL_TEXT ? 0 : -1;
|
||||
match->cp_str = cbuf_to_string(str, (size_t)len);
|
||||
match->cp_score = score;
|
||||
|
||||
// match-fname is:
|
||||
// - compl_curr_match->cp_fname if it is a string equal to fname.
|
||||
@@ -995,6 +1004,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
|
||||
match->cp_flags = flags;
|
||||
match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1;
|
||||
match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1;
|
||||
match->cp_score = score;
|
||||
match->cp_cpt_value_idx = cpt_value_idx;
|
||||
|
||||
if (cptext != NULL) {
|
||||
for (int i = 0; i < CPT_COUNT; i++) {
|
||||
@@ -1887,6 +1898,19 @@ char *find_line_end(char *ptr)
|
||||
return s;
|
||||
}
|
||||
|
||||
/// Free a completion item in the list
|
||||
static void ins_compl_item_free(compl_T *match)
|
||||
{
|
||||
API_CLEAR_STRING(match->cp_str);
|
||||
// several entries may use the same fname, free it just once.
|
||||
if (match->cp_flags & CP_FREE_FNAME) {
|
||||
xfree(match->cp_fname);
|
||||
}
|
||||
free_cptext(match->cp_text);
|
||||
tv_clear(&match->cp_user_data);
|
||||
xfree(match);
|
||||
}
|
||||
|
||||
/// Free the list of completions
|
||||
static void ins_compl_free(void)
|
||||
{
|
||||
@@ -1904,14 +1928,7 @@ static void ins_compl_free(void)
|
||||
do {
|
||||
compl_T *match = compl_curr_match;
|
||||
compl_curr_match = compl_curr_match->cp_next;
|
||||
API_CLEAR_STRING(match->cp_str);
|
||||
// several entries may use the same fname, free it just once.
|
||||
if (match->cp_flags & CP_FREE_FNAME) {
|
||||
xfree(match->cp_fname);
|
||||
}
|
||||
free_cptext(match->cp_text);
|
||||
tv_clear(&match->cp_user_data);
|
||||
xfree(match);
|
||||
ins_compl_item_free(match);
|
||||
} while (compl_curr_match != NULL && !is_first_match(compl_curr_match));
|
||||
compl_first_match = compl_curr_match = NULL;
|
||||
compl_shown_match = NULL;
|
||||
@@ -1935,6 +1952,7 @@ void ins_compl_clear(void)
|
||||
kv_destroy(compl_orig_extmarks);
|
||||
API_CLEAR_STRING(compl_orig_text);
|
||||
compl_enter_selects = false;
|
||||
cpt_compl_src_clear();
|
||||
// clear v:completed_item
|
||||
set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
|
||||
}
|
||||
@@ -1952,8 +1970,8 @@ bool ins_compl_win_active(win_T *wp)
|
||||
return ins_compl_active() && wp == compl_curr_win && wp->w_buffer == compl_curr_buf;
|
||||
}
|
||||
|
||||
/// Selected one of the matches. When false the match was edited or using the
|
||||
/// longest common string.
|
||||
/// Selected one of the matches. When false, the match was edited or
|
||||
/// using the longest common string.
|
||||
bool ins_compl_used_match(void)
|
||||
{
|
||||
return compl_used_match;
|
||||
@@ -2087,6 +2105,9 @@ static void ins_compl_new_leader(void)
|
||||
|
||||
if (compl_started) {
|
||||
ins_compl_set_original_text(compl_leader.data, compl_leader.size);
|
||||
if (is_cpt_func_refresh_always()) {
|
||||
cpt_compl_refresh();
|
||||
}
|
||||
} else {
|
||||
spell_bad_len = 0; // need to redetect bad word
|
||||
// Matches were cleared, need to search for them now.
|
||||
@@ -2170,6 +2191,7 @@ static void ins_compl_restart(void)
|
||||
compl_matches = 0;
|
||||
compl_cont_status = 0;
|
||||
compl_cont_mode = 0;
|
||||
cpt_compl_src_clear();
|
||||
}
|
||||
|
||||
/// Set the first match, the original text.
|
||||
@@ -2791,8 +2813,9 @@ static Callback *get_insert_callback(int type)
|
||||
/// Execute user defined complete function 'completefunc', 'omnifunc' or
|
||||
/// 'thesaurusfunc', and get matches in "matches".
|
||||
///
|
||||
/// @param type either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS
|
||||
static void expand_by_function(int type, char *base)
|
||||
/// @param type one of CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS
|
||||
/// @param cb set if triggered by a function in 'cpt' option, otherwise NULL
|
||||
static void expand_by_function(int type, char *base, Callback *cb)
|
||||
{
|
||||
list_T *matchlist = NULL;
|
||||
dict_T *matchdict = NULL;
|
||||
@@ -2800,9 +2823,14 @@ static void expand_by_function(int type, char *base)
|
||||
const int save_State = State;
|
||||
|
||||
assert(curbuf != NULL);
|
||||
char *funcname = get_complete_funcname(type);
|
||||
if (*funcname == NUL) {
|
||||
return;
|
||||
|
||||
const bool is_cpt_function = (cb != NULL);
|
||||
if (!is_cpt_function) {
|
||||
char *funcname = get_complete_funcname(type);
|
||||
if (*funcname == NUL) {
|
||||
return;
|
||||
}
|
||||
cb = get_insert_callback(type);
|
||||
}
|
||||
|
||||
// Call 'completefunc' to obtain the list of matches.
|
||||
@@ -2819,8 +2847,6 @@ static void expand_by_function(int type, char *base)
|
||||
// Insert mode in another buffer.
|
||||
textlock++;
|
||||
|
||||
Callback *cb = get_insert_callback(type);
|
||||
|
||||
// Call a function, which returns a list or dict.
|
||||
if (callback_call(cb, 2, args, &rettv)) {
|
||||
switch (rettv.v_type) {
|
||||
@@ -3492,12 +3518,25 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
|
||||
st->dict = st->e_cpt;
|
||||
st->dict_f = DICT_FIRST;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
} else if (*st->e_cpt == 'i') {
|
||||
compl_type = CTRL_X_PATH_PATTERNS;
|
||||
} else if (*st->e_cpt == 'd') {
|
||||
compl_type = CTRL_X_PATH_DEFINES;
|
||||
#if 0
|
||||
} else if (*st->e_cpt == 'f') {
|
||||
compl_type = CTRL_X_BUFNAMES;
|
||||
#endif
|
||||
} else if (*st->e_cpt == ']' || *st->e_cpt == 't') {
|
||||
compl_type = CTRL_X_TAGS;
|
||||
if (!shortmess(SHM_COMPLETIONSCAN)) {
|
||||
@@ -3539,7 +3578,7 @@ static void get_next_include_file_completion(int compl_type)
|
||||
static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f)
|
||||
{
|
||||
if (thesaurus_func_complete(compl_type)) {
|
||||
expand_by_function(compl_type, compl_pattern.data);
|
||||
expand_by_function(compl_type, compl_pattern.data, NULL);
|
||||
} else {
|
||||
ins_compl_dictionaries(dict != NULL
|
||||
? dict
|
||||
@@ -4102,6 +4141,30 @@ static void get_register_completion(void)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the callback function associated with "funcname".
|
||||
static Callback *get_cpt_func_callback(char *funcname)
|
||||
{
|
||||
static Callback cb;
|
||||
char buf[LSIZE];
|
||||
|
||||
size_t 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 *cb)
|
||||
{
|
||||
// Re-insert the text removed by ins_compl_delete().
|
||||
ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1);
|
||||
// Get matches
|
||||
get_cpt_func_completion_matches(cb);
|
||||
// Undo insertion
|
||||
ins_compl_delete(false);
|
||||
}
|
||||
|
||||
/// get the next set of completion matches for "type".
|
||||
/// @return true if a new match is found, otherwise false.
|
||||
static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini)
|
||||
@@ -4136,8 +4199,14 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
|
||||
break;
|
||||
|
||||
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.data, NULL);
|
||||
}
|
||||
break;
|
||||
case CTRL_X_OMNI:
|
||||
expand_by_function(type, compl_pattern.data);
|
||||
expand_by_function(type, compl_pattern.data, NULL);
|
||||
break;
|
||||
|
||||
case CTRL_X_SPELL:
|
||||
@@ -4210,6 +4279,10 @@ static int ins_compl_get_exp(pos_T *ini)
|
||||
st.e_cpt_copy = xstrdup((compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt);
|
||||
st.e_cpt = st.e_cpt_copy;
|
||||
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);
|
||||
}
|
||||
} else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) {
|
||||
st.ins_buf = curbuf; // In case the buffer was wiped out.
|
||||
}
|
||||
@@ -4219,7 +4292,7 @@ static int ins_compl_get_exp(pos_T *ini)
|
||||
st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos;
|
||||
|
||||
// For ^N/^P loop over all the flags/windows/buffers in 'complete'
|
||||
while (true) {
|
||||
for (cpt_value_idx = 0;;) {
|
||||
found_new_match = FAIL;
|
||||
st.set_match_pos = false;
|
||||
|
||||
@@ -4233,6 +4306,7 @@ static int ins_compl_get_exp(pos_T *ini)
|
||||
break;
|
||||
}
|
||||
if (status == INS_COMPL_CPT_CONT) {
|
||||
cpt_value_idx++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -4246,6 +4320,10 @@ static int ins_compl_get_exp(pos_T *ini)
|
||||
// get the next set of completion matches
|
||||
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
|
||||
// generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new match
|
||||
if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())
|
||||
@@ -4273,6 +4351,7 @@ static int ins_compl_get_exp(pos_T *ini)
|
||||
compl_started = false;
|
||||
}
|
||||
}
|
||||
cpt_value_idx = -1;
|
||||
compl_started = true;
|
||||
|
||||
if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval())
|
||||
@@ -5025,17 +5104,25 @@ static int get_cmdline_compl_info(char *line, colnr_T curs_col)
|
||||
/// 'completefunc' and 'thesaurusfunc')
|
||||
/// Sets the global variables: compl_col, compl_length and compl_pattern.
|
||||
/// Uses the global variable: spell_bad_len
|
||||
static int get_userdefined_compl_info(colnr_T curs_col)
|
||||
///
|
||||
/// @param cb set if triggered by a function in the 'cpt' option, otherwise NULL
|
||||
/// @param startcol when not NULL, contains the column returned by function.
|
||||
static int get_userdefined_compl_info(colnr_T curs_col, Callback *cb, int *startcol)
|
||||
{
|
||||
// Call user defined function 'completefunc' with "a:findstart"
|
||||
// set to 1 to obtain the length of text to use for completion.
|
||||
const int save_State = State;
|
||||
|
||||
// Call 'completefunc' or 'omnifunc' and get pattern length as a string
|
||||
char *funcname = get_complete_funcname(ctrl_x_mode);
|
||||
if (*funcname == NUL) {
|
||||
semsg(_(e_notset), ctrl_x_mode_function() ? "completefunc" : "omnifunc");
|
||||
return FAIL;
|
||||
const bool is_cpt_function = (cb != NULL);
|
||||
if (!is_cpt_function) {
|
||||
// Call 'completefunc' or 'omnifunc' or 'thesaurusfunc' and get pattern
|
||||
// length as a string
|
||||
char *funcname = get_complete_funcname(ctrl_x_mode);
|
||||
if (*funcname == NUL) {
|
||||
semsg(_(e_notset), ctrl_x_mode_function() ? "completefunc" : "omnifunc");
|
||||
return FAIL;
|
||||
}
|
||||
cb = get_insert_callback(ctrl_x_mode);
|
||||
}
|
||||
|
||||
typval_T args[3];
|
||||
@@ -5047,7 +5134,6 @@ static int get_userdefined_compl_info(colnr_T curs_col)
|
||||
|
||||
pos_T pos = curwin->w_cursor;
|
||||
textlock++;
|
||||
Callback *cb = get_insert_callback(ctrl_x_mode);
|
||||
colnr_T col = (colnr_T)callback_call_retnr(cb, 2, args);
|
||||
textlock--;
|
||||
|
||||
@@ -5060,6 +5146,10 @@ static int get_userdefined_compl_info(colnr_T curs_col)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
if (startcol != NULL) {
|
||||
*startcol = col;
|
||||
}
|
||||
|
||||
// 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
|
||||
// successfully.
|
||||
@@ -5069,6 +5159,9 @@ static int get_userdefined_compl_info(colnr_T curs_col)
|
||||
|
||||
// Return value -3 does the same as -2 and leaves CTRL-X mode.
|
||||
if (col == -3) {
|
||||
if (is_cpt_function) {
|
||||
return FAIL;
|
||||
}
|
||||
ctrl_x_mode = CTRL_X_NORMAL;
|
||||
edit_submode = NULL;
|
||||
if (!shortmess(SHM_COMPLETIONMENU)) {
|
||||
@@ -5081,20 +5174,22 @@ static int get_userdefined_compl_info(colnr_T curs_col)
|
||||
// completion.
|
||||
compl_opt_refresh_always = false;
|
||||
|
||||
if (col < 0) {
|
||||
if (col < 0 || 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,
|
||||
// it may have become invalid.
|
||||
char *line = ml_get(curwin->w_cursor.lnum);
|
||||
compl_length = curs_col - compl_col;
|
||||
compl_pattern = cbuf_to_string(line + compl_col, (size_t)compl_length);
|
||||
int len = curs_col - col;
|
||||
String *compl_pat = is_cpt_function ? &cpt_compl_pattern : &compl_pattern;
|
||||
compl_pat->data = xstrnsave(line + col, (size_t)len);
|
||||
compl_pat->size = (size_t)compl_length;
|
||||
|
||||
if (!is_cpt_function) {
|
||||
compl_col = col;
|
||||
compl_length = len;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -5146,7 +5241,7 @@ static int compl_get_info(char *line, int startcol, colnr_T curs_col, bool *line
|
||||
return get_cmdline_compl_info(line, curs_col);
|
||||
} else if (ctrl_x_mode_function() || ctrl_x_mode_omni()
|
||||
|| 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;
|
||||
}
|
||||
*line_invalid = true; // "line" may have become invalid
|
||||
@@ -5576,3 +5671,175 @@ static void spell_back_to_badword(void)
|
||||
start_arrow(&tpos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the info associated with completion sources.
|
||||
static void cpt_compl_src_clear(void)
|
||||
{
|
||||
XFREE_CLEAR(cpt_func_refresh_always);
|
||||
cpt_value_idx = -1;
|
||||
cpt_value_count = 0;
|
||||
}
|
||||
|
||||
/// Initialize the info associated with completion sources.
|
||||
static void cpt_compl_src_init(char *cpt_str)
|
||||
{
|
||||
int count = 0;
|
||||
char *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 = xcalloc((size_t)count, sizeof(bool));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if any of the completion sources have 'refresh' set to 'always'.
|
||||
static bool is_cpt_func_refresh_always(void)
|
||||
{
|
||||
for (int i = 0; i < cpt_value_count; i++) {
|
||||
if (cpt_func_refresh_always[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Make the completion list non-cyclic.
|
||||
static void ins_compl_make_linear(void)
|
||||
{
|
||||
if (compl_first_match == NULL || compl_first_match->cp_prev == NULL) {
|
||||
return;
|
||||
}
|
||||
compl_T *m = compl_first_match->cp_prev;
|
||||
m->cp_next = NULL;
|
||||
compl_first_match->cp_prev = NULL;
|
||||
}
|
||||
|
||||
/// Remove the matches linked to the current completion source (as indicated by
|
||||
/// cpt_value_idx) from the completion list.
|
||||
static compl_T *remove_old_matches(void)
|
||||
{
|
||||
compl_T *sublist_start = NULL, *sublist_end = NULL, *insert_at = NULL;
|
||||
compl_T *current, *next;
|
||||
bool 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;
|
||||
}
|
||||
|
||||
/// Retrieve completion matches using the callback function "cb" and store the
|
||||
/// 'refresh:always' flag.
|
||||
static void get_cpt_func_completion_matches(Callback *cb)
|
||||
{
|
||||
API_CLEAR_STRING(cpt_compl_pattern);
|
||||
int startcol;
|
||||
int 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.data, cb);
|
||||
cpt_func_refresh_always[cpt_value_idx] = compl_opt_refresh_always;
|
||||
compl_opt_refresh_always = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve completion matches from functions in the 'cpt' option where the
|
||||
/// 'refresh:always' flag is set.
|
||||
static void cpt_compl_refresh(void)
|
||||
{
|
||||
Callback *cb;
|
||||
|
||||
// Make the completion list linear (non-cyclic)
|
||||
ins_compl_make_linear();
|
||||
// Make a copy of 'cpt' in case the buffer gets wiped out
|
||||
char *cpt = xstrdup(curbuf->b_p_cpt);
|
||||
|
||||
cpt_value_idx = 0;
|
||||
for (char *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;
|
||||
|
||||
xfree(cpt);
|
||||
// Make the list cyclic
|
||||
compl_matches = ins_compl_make_cyclic();
|
||||
}
|
||||
|
@@ -1412,7 +1412,7 @@ local options = {
|
||||
abbreviation = 'cpt',
|
||||
cb = 'did_set_complete',
|
||||
defaults = '.,w,b,u,t',
|
||||
values = { '.', 'w', 'b', 'u', 'k', 'kspell', 's', 'i', 'd', ']', 't', 'U', 'f' },
|
||||
values = { '.', 'w', 'b', 'u', 'k', 'kspell', 's', 'i', 'd', ']', 't', 'U', 'f', 'o' },
|
||||
deny_duplicates = true,
|
||||
desc = [=[
|
||||
This option specifies how keyword completion |ins-completion| works
|
||||
@@ -1438,6 +1438,28 @@ local options = {
|
||||
] tag completion
|
||||
t same as "]"
|
||||
f scan the buffer names (as opposed to buffer contents)
|
||||
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
|
||||
not executed, this may lead to unexpected completions from some files
|
||||
|
@@ -45,6 +45,7 @@
|
||||
#include "nvim/spellfile.h"
|
||||
#include "nvim/spellsuggest.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/tag.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/vim_defs.h"
|
||||
@@ -54,10 +55,12 @@
|
||||
# include "optionstr.c.generated.h"
|
||||
#endif
|
||||
|
||||
static const char e_unclosed_expression_sequence[]
|
||||
= N_("E540: Unclosed expression sequence");
|
||||
static const char e_illegal_character_after_chr[]
|
||||
= N_("E535: Illegal character after <%c>");
|
||||
static const char e_comma_required[]
|
||||
= N_("E536: Comma required");
|
||||
static const char e_unclosed_expression_sequence[]
|
||||
= N_("E540: Unclosed expression sequence");
|
||||
static const char e_unbalanced_groups[]
|
||||
= N_("E542: Unbalanced groups");
|
||||
static const char e_backupext_and_patchmode_are_equal[]
|
||||
@@ -836,40 +839,45 @@ const char *did_set_commentstring(optset_T *args)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// The 'complete' option is changed.
|
||||
/// Check if value for 'complete' is valid when 'complete' option is changed.
|
||||
const char *did_set_complete(optset_T *args)
|
||||
{
|
||||
char **varp = (char **)args->os_varp;
|
||||
char buffer[LSIZE];
|
||||
|
||||
// check if it is a valid value for 'complete' -- Acevedo
|
||||
for (char *s = *varp; *s;) {
|
||||
while (*s == ',' || *s == ' ') {
|
||||
s++;
|
||||
}
|
||||
if (!*s) {
|
||||
break;
|
||||
}
|
||||
if (vim_strchr(".wbuksid]tUf", (uint8_t)(*s)) == NULL) {
|
||||
return illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s));
|
||||
}
|
||||
if (*++s != NUL && *s != ',' && *s != ' ') {
|
||||
if (s[-1] == 'k' || s[-1] == 's') {
|
||||
// skip optional filename after 'k' and 's'
|
||||
while (*s && *s != ',' && *s != ' ') {
|
||||
if (*s == '\\' && s[1] != NUL) {
|
||||
s++;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
for (char *p = *varp; *p;) {
|
||||
memset(buffer, 0, LSIZE);
|
||||
char *buf_ptr = buffer;
|
||||
int escape = 0;
|
||||
|
||||
// Extract substring while handling escaped commas
|
||||
while (*p && (*p != ',' || escape) && buf_ptr < (buffer + LSIZE - 1)) {
|
||||
if (*p == '\\' && *(p + 1) == ',') {
|
||||
escape = 1; // Mark escape mode
|
||||
p++; // Skip '\'
|
||||
} else {
|
||||
if (args->os_errbuf != NULL) {
|
||||
vim_snprintf(args->os_errbuf, args->os_errbuflen,
|
||||
_("E535: Illegal character after <%c>"),
|
||||
*--s);
|
||||
return args->os_errbuf;
|
||||
}
|
||||
return "";
|
||||
escape = 0;
|
||||
*buf_ptr++ = *p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
*buf_ptr = NUL;
|
||||
|
||||
if (vim_strchr(".wbuksid]tUfo", (uint8_t)(*buffer)) == NULL) {
|
||||
return illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*buffer));
|
||||
}
|
||||
|
||||
if (!vim_strchr("ksf", (uint8_t)(*buffer)) && *(buffer + 1) != NUL) {
|
||||
if (args->os_errbuf != NULL) {
|
||||
vim_snprintf(args->os_errbuf, args->os_errbuflen,
|
||||
_(e_illegal_character_after_chr), (uint8_t)(*buffer));
|
||||
return args->os_errbuf;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip comma and spaces
|
||||
while (*p == ',' || *p == ' ') {
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
@@ -134,10 +134,15 @@ func Test_omni_dash()
|
||||
new
|
||||
exe "normal Gofind -\<C-x>\<C-o>"
|
||||
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!
|
||||
delfunc Omni
|
||||
set omnifunc=
|
||||
set omnifunc= complete&
|
||||
endfunc
|
||||
|
||||
func Test_omni_throw()
|
||||
@@ -157,11 +162,21 @@ func Test_omni_throw()
|
||||
call assert_exception('he he he')
|
||||
call assert_equal(1, g:CallCount)
|
||||
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!
|
||||
delfunc Omni
|
||||
unlet g:CallCount
|
||||
set omnifunc=
|
||||
set omnifunc= complete&
|
||||
endfunc
|
||||
|
||||
func Test_completefunc_args()
|
||||
@@ -184,6 +199,16 @@ func Test_completefunc_args()
|
||||
call assert_equal(0, s:args[1][0])
|
||||
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!
|
||||
unlet s:args
|
||||
delfunc CompleteFunc
|
||||
@@ -230,7 +255,7 @@ func s:CompleteDone_CheckCompletedItemDict(pre)
|
||||
call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] )
|
||||
|
||||
if a:pre
|
||||
call assert_equal('function', complete_info().mode)
|
||||
call assert_equal(a:pre == 1 ? 'function' : 'keyword', complete_info().mode)
|
||||
endif
|
||||
|
||||
let s:called_completedone = 1
|
||||
@@ -248,7 +273,15 @@ func Test_CompleteDoneNone()
|
||||
|
||||
call assert_true(s:called_completedone)
|
||||
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
|
||||
au! CompleteDone
|
||||
endfunc
|
||||
@@ -269,6 +302,7 @@ func Test_CompleteDone_vevent_keys()
|
||||
endfunc
|
||||
set omnifunc=CompleteFunc
|
||||
set completefunc=CompleteFunc
|
||||
set complete=.,fCompleteFunc
|
||||
set completeopt+=menuone
|
||||
|
||||
new
|
||||
@@ -292,7 +326,11 @@ func Test_CompleteDone_vevent_keys()
|
||||
call assert_equal('vim', g:complete_word)
|
||||
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('keyword', g:complete_type)
|
||||
|
||||
@@ -350,6 +388,21 @@ func Test_CompleteDoneDict()
|
||||
call assert_true(s:called_completedone)
|
||||
|
||||
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
|
||||
endfunc
|
||||
|
||||
@@ -392,6 +445,15 @@ func Test_CompleteDoneDictNoUserData()
|
||||
call assert_equal('', v:completed_item[ 'user_data' ])
|
||||
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
|
||||
au! CompleteDone
|
||||
endfunc
|
||||
@@ -425,6 +487,24 @@ func Test_CompleteDoneList()
|
||||
call assert_equal('', v:completed_item[ 'user_data' ])
|
||||
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
|
||||
au! CompleteDone
|
||||
endfunc
|
||||
@@ -468,11 +548,51 @@ func Test_completefunc_info()
|
||||
set completefunc=CompleteTest
|
||||
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))
|
||||
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 complete&
|
||||
set completefunc&
|
||||
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)
|
||||
" User defined function (i_CTRL-X_CTRL-U)
|
||||
if a:findstart
|
||||
@@ -529,24 +649,34 @@ func CompleteInfoUserDefinedFn(findstart, query)
|
||||
endfunc
|
||||
|
||||
func CompleteInfoTestUserDefinedFn(mvmt, idx, noselect)
|
||||
new
|
||||
if a:noselect
|
||||
set completeopt=menuone,popup,noinsert,noselect
|
||||
else
|
||||
set completeopt=menu,preview
|
||||
endif
|
||||
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': [" .
|
||||
let items = "[" .
|
||||
\ "{'word': 'foo', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " .
|
||||
\ "{'word': 'bar', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, " .
|
||||
\ "{'word': 'baz', '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!
|
||||
set completeopt&
|
||||
set completefunc&
|
||||
set completeopt& completefunc& complete&
|
||||
endfunc
|
||||
|
||||
func Test_complete_info_user_defined_fn()
|
||||
@@ -914,6 +1044,10 @@ func Test_completefunc_error()
|
||||
set completefunc=CompleteFunc
|
||||
call setline(1, ['', 'abcd', ''])
|
||||
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
|
||||
func CompleteFunc2(findstart, base)
|
||||
@@ -926,6 +1060,10 @@ func Test_completefunc_error()
|
||||
set completefunc=CompleteFunc2
|
||||
call setline(1, ['', 'abcd', ''])
|
||||
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
|
||||
func CompleteFunc3(findstart, base)
|
||||
@@ -938,9 +1076,15 @@ func Test_completefunc_error()
|
||||
set completefunc=CompleteFunc3
|
||||
new
|
||||
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!
|
||||
|
||||
set completefunc&
|
||||
set completefunc& complete&
|
||||
delfunc CompleteFunc
|
||||
delfunc CompleteFunc2
|
||||
delfunc CompleteFunc3
|
||||
@@ -959,7 +1103,15 @@ func Test_completefunc_invalid_data()
|
||||
set completefunc=CompleteFunc
|
||||
exe "normal i\<C-X>\<C-U>"
|
||||
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!
|
||||
endfunc
|
||||
|
||||
@@ -1636,18 +1788,363 @@ func Test_complete_item_refresh_always()
|
||||
return #{words: res, refresh: 'always'}
|
||||
endif
|
||||
endfunc
|
||||
new
|
||||
set completeopt=menu,longest
|
||||
set completefunc=Tcomplete
|
||||
new
|
||||
exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>"
|
||||
call assert_equal('up', getline(1))
|
||||
call assert_equal(6, g:CallCount)
|
||||
set completeopt&
|
||||
set completefunc&
|
||||
%d
|
||||
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!
|
||||
set completeopt&
|
||||
set complete&
|
||||
set completefunc&
|
||||
delfunc Tcomplete
|
||||
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
|
||||
func Test_complete_unreadable_thesaurus_file()
|
||||
CheckUnix
|
||||
@@ -1687,6 +2184,143 @@ func Test_no_mapping_for_ctrl_x_key()
|
||||
bwipe!
|
||||
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 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 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 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
|
||||
func Test_completefunc_callback()
|
||||
func CompleteFunc1(callnr, findstart, base)
|
||||
@@ -2564,10 +3198,19 @@ endfunc
|
||||
func Test_complete_smartindent()
|
||||
new
|
||||
setlocal smartindent completefunc=FooBarComplete
|
||||
|
||||
exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>"
|
||||
let result = getline(1,'$')
|
||||
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!
|
||||
delfunction! FooBarComplete
|
||||
endfunc
|
||||
|
@@ -277,6 +277,14 @@ func Test_complete()
|
||||
call feedkeys("i\<C-N>\<Esc>", 'xt')
|
||||
bwipe!
|
||||
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&
|
||||
endfun
|
||||
|
||||
|
Reference in New Issue
Block a user