vim-patch:9.1.1797: completion: autocompletion can be improved

Problem:  completion: autocompletion can be improved
Solution: Add support for "longest" and "preinsert" in 'autocomplete';
          add preinserted() (Girish Palya)

* Add support for "longest" in 'completeopt' when 'autocomplete'
  is enabled. (Note: the cursor position does not change automatically
  when 'autocomplete' is enabled.)
* Add support for "preinsert" when 'autocomplete' is enabled. Ensure
  "preinsert" works the same with and without 'autocomplete'
* introduce the preinserted() Vim script function, useful for defining
  custom key mappings.

fixes: vim/vim#18314
closes: vim/vim#18387

c05335082a

Co-authored-by: Girish Palya <girishji@gmail.com>
This commit is contained in:
zeertzjq
2025-09-27 09:09:03 +08:00
parent 4896178863
commit 7b9c063d11
11 changed files with 381 additions and 130 deletions

View File

@@ -1638,11 +1638,22 @@ A jump table for the options with a short description can be found at |Q_op|.
to gather more alternatives for your candidate list,
see 'completefuzzycollect'.
longest Only insert the longest common text of the matches. If
the menu is displayed you can use CTRL-L to add more
characters. Whether case is ignored depends on the kind
of completion. For buffer text the 'ignorecase' option is
used.
longest
When 'autocomplete' is not active, only the longest
common prefix of the matches is inserted. If the popup
menu is displayed, you can use CTRL-L to add more
characters. Whether case is ignored depends on the type
of completion. For buffer text the 'ignorecase' option
applies.
When 'autocomplete' is active and no completion item is
selected, the longest common prefix of the matches is
inserted after the cursor. The prefix is taken either
from all displayed items or only from items in the current
buffer. The inserted text is highlighted with
|hl-PreInsert|, and the cursor position does not change
(similar to `"preinsert"`). Press CTRL-Y to accept.
See also |preinserted()|.
menu Use a popup menu to show the possible completions. The
menu is only shown when there is more than one match and
@@ -1675,22 +1686,21 @@ A jump table for the options with a short description can be found at |Q_op|.
with "menu" or "menuone". Overrides "preview".
preinsert
When 'autocomplete' is not active, inserts the part of the
first candidate word beyond the current completion leader,
highlighted with |hl-PreInsert|. The cursor doesn't move.
Requires "fuzzy" unset and "menuone" in 'completeopt'.
When 'autocomplete' is active, inserts the longest common
prefix of matches (from all shown items or from the
current buffer items). This occurs only when no menu item
is selected. Press CTRL-Y to accept.
Inserts the text of the first completion candidate
beyond the current leader, highlighted with |hl-PreInsert|.
The cursor does not move.
Requires "fuzzy" to be unset, and either "menuone" in
'completeopt' or 'autocomplete' enabled. When
'autocomplete' is enabled, this does not work if
'ignorecase' is set without 'infercase'.
See also |preinserted()|.
preview Show extra information about the currently selected
completion in the preview window. Only works in
combination with "menu" or "menuone".
Only "fuzzy", "popup", "preinsert" and "preview" have an effect when
'autocomplete' is enabled.
Only "fuzzy", "longest", "popup", "preinsert" and "preview" have an
effect when 'autocomplete' is enabled.
This option does not apply to |cmdline-completion|. See 'wildoptions'
for that.

View File

@@ -940,6 +940,7 @@ Insert mode completion: *completion-functions*
complete_info() get current completion information
complete_match() get insert completion start match col and
trigger text
preinserted() check if text is inserted after cursor
pumvisible() check if the popup menu is displayed
pum_getpos() position and size of popup menu if visible

View File

@@ -7232,6 +7232,15 @@ pow({x}, {y}) *pow()*
Return: ~
(`number`)
preinserted() *preinserted()*
Returns non-zero if text has been inserted after the cursor
because "preinsert" is present in 'completeopt', or if
"longest" is present in 'completeopt' while 'autocomplete'
is enabled. Otherwise returns zero.
Return: ~
(`number`)
prevnonblank({lnum}) *prevnonblank()*
Return the line number of the first line at or above {lnum}
that is not blank. Example: >vim

View File

@@ -1183,11 +1183,22 @@ vim.go.cia = vim.go.completeitemalign
--- to gather more alternatives for your candidate list,
--- see 'completefuzzycollect'.
---
--- longest Only insert the longest common text of the matches. If
--- the menu is displayed you can use CTRL-L to add more
--- characters. Whether case is ignored depends on the kind
--- of completion. For buffer text the 'ignorecase' option is
--- used.
--- longest
--- When 'autocomplete' is not active, only the longest
--- common prefix of the matches is inserted. If the popup
--- menu is displayed, you can use CTRL-L to add more
--- characters. Whether case is ignored depends on the type
--- of completion. For buffer text the 'ignorecase' option
--- applies.
---
--- When 'autocomplete' is active and no completion item is
--- selected, the longest common prefix of the matches is
--- inserted after the cursor. The prefix is taken either
--- from all displayed items or only from items in the current
--- buffer. The inserted text is highlighted with
--- `hl-PreInsert`, and the cursor position does not change
--- (similar to `"preinsert"`). Press CTRL-Y to accept.
--- See also `preinserted()`.
---
--- menu Use a popup menu to show the possible completions. The
--- menu is only shown when there is more than one match and
@@ -1220,22 +1231,21 @@ vim.go.cia = vim.go.completeitemalign
--- with "menu" or "menuone". Overrides "preview".
---
--- preinsert
--- When 'autocomplete' is not active, inserts the part of the
--- first candidate word beyond the current completion leader,
--- highlighted with `hl-PreInsert`. The cursor doesn't move.
--- Requires "fuzzy" unset and "menuone" in 'completeopt'.
---
--- When 'autocomplete' is active, inserts the longest common
--- prefix of matches (from all shown items or from the
--- current buffer items). This occurs only when no menu item
--- is selected. Press CTRL-Y to accept.
--- Inserts the text of the first completion candidate
--- beyond the current leader, highlighted with `hl-PreInsert`.
--- The cursor does not move.
--- Requires "fuzzy" to be unset, and either "menuone" in
--- 'completeopt' or 'autocomplete' enabled. When
--- 'autocomplete' is enabled, this does not work if
--- 'ignorecase' is set without 'infercase'.
--- See also `preinserted()`.
---
--- preview Show extra information about the currently selected
--- completion in the preview window. Only works in
--- combination with "menu" or "menuone".
---
--- Only "fuzzy", "popup", "preinsert" and "preview" have an effect when
--- 'autocomplete' is enabled.
--- Only "fuzzy", "longest", "popup", "preinsert" and "preview" have an
--- effect when 'autocomplete' is enabled.
---
--- This option does not apply to `cmdline-completion`. See 'wildoptions'
--- for that.

View File

@@ -6554,6 +6554,14 @@ function vim.fn.perleval(expr) end
--- @return number
function vim.fn.pow(x, y) end
--- Returns non-zero if text has been inserted after the cursor
--- because "preinsert" is present in 'completeopt', or if
--- "longest" is present in 'completeopt' while 'autocomplete'
--- is enabled. Otherwise returns zero.
---
--- @return number
function vim.fn.preinserted() end
--- Return the line number of the first line at or above {lnum}
--- that is not blank. Example: >vim
--- let ind = indent(prevnonblank(v:lnum - 1))

View File

@@ -606,10 +606,10 @@ static int insert_execute(VimState *state, int key)
&& (s->c == CAR || s->c == K_KENTER || s->c == NL)))
&& stop_arrow() == OK) {
ins_compl_delete(false);
if (ins_compl_has_preinsert() && ins_compl_autocomplete_enabled()) {
(void)ins_compl_insert(false, true);
} else {
(void)ins_compl_insert(false, false);
ins_compl_insert(false, !ins_compl_has_preinsert());
if (ins_compl_preinsert_longest()) {
ins_compl_init_get_longest();
return 1;
}
} else if (ascii_iswhite_nl_or_nul(s->c) && ins_compl_preinsert_effect()) {
// Delete preinserted text when typing special chars

View File

@@ -8024,6 +8024,18 @@ M.funcs = {
returns = 'number',
signature = 'pow({x}, {y})',
},
preinserted = {
desc = [=[
Returns non-zero if text has been inserted after the cursor
because "preinsert" is present in 'completeopt', or if
"longest" is present in 'completeopt' while 'autocomplete'
is enabled. Otherwise returns zero.
]=],
name = 'preinserted',
params = {},
returns = 'number',
signature = 'preinserted()',
},
prevnonblank = {
args = 1,
base = 1,

View File

@@ -296,7 +296,7 @@ static bool compl_autocomplete = false; ///< whether autocompletion is ac
static uint64_t compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS;
static bool compl_time_slice_expired = false; ///< time budget exceeded for current source
static bool compl_from_nonkeyword = false; ///< completion started from non-keyword
static bool compl_autocomplete_preinsert = false; ///< apply preinsert highlight
static bool compl_hi_on_autocompl_longest = false; ///< apply "PreInsert" highlight
// Halve the current completion timeout, simulating exponential decay.
#define COMPL_MIN_TIMEOUT_MS 5
@@ -889,6 +889,15 @@ static bool is_nearest_active(void)
&& !(flags & kOptCotFlagFuzzy);
}
/// Returns true if autocomplete is active and the pre-insert effect targets the
/// longest prefix.
bool ins_compl_preinsert_longest(void)
{
return compl_autocomplete
&& (get_cot_flags() & (kOptCotFlagLongest | kOptCotFlagPreinsert | kOptCotFlagFuzzy))
== kOptCotFlagLongest;
}
/// Add a match to the list of matches
///
/// @param[in] str text of the match to add
@@ -1053,7 +1062,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
compl_curr_match = match;
// Find the longest common string if still doing that.
if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0 && !cfc_has_mode()) {
if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0 && !cfc_has_mode()
&& !ins_compl_preinsert_longest()) {
ins_compl_longest_match(match);
}
@@ -1107,17 +1117,12 @@ static size_t ins_compl_leader_len(void)
/// -1 means normal item.
int ins_compl_col_range_attr(linenr_T lnum, int col)
{
const bool has_preinsert = ins_compl_has_preinsert();
const bool has_preinsert = ins_compl_has_preinsert() || ins_compl_preinsert_longest();
int attr;
if ((get_cot_flags() & kOptCotFlagFuzzy)
|| (!has_preinsert
&& (attr = syn_name2attr("ComplMatchIns")) == 0)
|| (!compl_autocomplete && has_preinsert
&& (attr = syn_name2attr("PreInsert")) == 0)
|| (compl_autocomplete
&& (!compl_autocomplete_preinsert
|| (attr = syn_name2attr("PreInsert")) == 0))) {
|| (!compl_hi_on_autocompl_longest && ins_compl_preinsert_longest())
|| (attr = syn_name2attr(has_preinsert ? "PreInsert" : "ComplMatchIns")) == 0) {
return -1;
}
@@ -1511,7 +1516,8 @@ static int ins_compl_build_pum(void)
}
unsigned cur_cot_flags = get_cot_flags();
bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0 || compl_autocomplete;
bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0
|| (compl_autocomplete && !ins_compl_has_preinsert());
bool fuzzy_filter = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
compl_T *match_head = NULL, *match_tail = NULL;
@@ -2132,6 +2138,9 @@ int ins_compl_len(void)
bool ins_compl_has_preinsert(void)
{
unsigned cur_cot_flags = get_cot_flags();
if (compl_autocomplete && p_ic && !p_inf) {
return false;
}
return (!compl_autocomplete
? (cur_cot_flags & (kOptCotFlagPreinsert|kOptCotFlagFuzzy|kOptCotFlagMenuone))
== (kOptCotFlagPreinsert|kOptCotFlagMenuone)
@@ -2143,19 +2152,13 @@ bool ins_compl_has_preinsert(void)
/// the `compl_ins_end_col` range.
bool ins_compl_preinsert_effect(void)
{
if (!ins_compl_has_preinsert()) {
if (!ins_compl_has_preinsert() && !ins_compl_preinsert_longest()) {
return false;
}
return curwin->w_cursor.col < compl_ins_end_col;
}
/// Returns true if autocompletion is active.
bool ins_compl_autocomplete_enabled(void)
{
return compl_autocomplete;
}
/// Delete one character before the cursor and show the subset of the matches
/// that match the word that is now before the cursor.
/// Returns the character to be used, NUL if the work is done and another char
@@ -2198,7 +2201,7 @@ int ins_compl_bs(void)
(size_t)(p_off - (ptrdiff_t)compl_col));
// Clear selection if a menu item is currently selected in autocompletion
if (compl_autocomplete && compl_first_match) {
if (compl_autocomplete && compl_first_match && !ins_compl_has_preinsert()) {
compl_shown_match = compl_first_match;
}
@@ -2291,18 +2294,14 @@ static void ins_compl_new_leader(void)
// Show the popup menu with a different set of matches.
ins_compl_show_pum();
compl_autocomplete_preinsert = false;
// Don't let Enter select the original text when there is no popup menu.
if (compl_match_array == NULL) {
compl_enter_selects = false;
} else if (ins_compl_has_preinsert() && compl_leader.size > 0) {
if (compl_started && compl_autocomplete && !ins_compl_preinsert_effect()) {
if (ins_compl_insert(true, true) == OK) {
compl_autocomplete_preinsert = true;
}
} else {
(void)ins_compl_insert(true, false);
}
ins_compl_insert(true, false);
} else if (compl_started && ins_compl_preinsert_longest()
&& compl_leader.size > 0 && !ins_compl_preinsert_effect()) {
ins_compl_insert(true, true);
}
// Don't let Enter select when use user function and refresh_always is set
if (ins_compl_refresh_always()) {
@@ -4388,7 +4387,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
ptr = ins_compl_get_next_word_or_line(st->ins_buf,
st->cur_match_pos, &len, &cont_s_ipos);
}
if (ptr == NULL || (!compl_autocomplete && ins_compl_has_preinsert()
if (ptr == NULL || (ins_compl_has_preinsert()
&& strcmp(ptr, compl_pattern.data) == 0)) {
continue;
}
@@ -4895,7 +4894,7 @@ static int ins_compl_get_exp(pos_T *ini)
}
may_trigger_modechanged();
if (is_nearest_active()) {
if (is_nearest_active() && !ins_compl_has_preinsert()) {
sort_compl_match_list(cp_compare_nearest);
}
@@ -5108,6 +5107,21 @@ static char *find_common_prefix(size_t *prefix_len, bool curbuf_only)
xfree(match_count);
if (len > (int)ins_compl_leader_len()) {
assert(first != NULL);
// Avoid inserting text that duplicates the text already present
// after the cursor.
if (len == (int)strlen(first)) {
char *line = get_cursor_line_ptr();
char *p = line + curwin->w_cursor.col;
if (p && !ascii_iswhite_or_nul(*p)) {
char *end = find_word_end(p);
int text_len = (int)(end - p);
if (text_len > 0 && text_len < (len - (int)ins_compl_leader_len())
&& strncmp(first + len - text_len, p, (size_t)text_len) == 0) {
len -= text_len;
}
}
}
*prefix_len = (size_t)len;
return first;
}
@@ -5117,9 +5131,9 @@ static char *find_common_prefix(size_t *prefix_len, bool curbuf_only)
/// Insert the new text being completed.
/// "move_cursor" is used when 'completeopt' includes "preinsert" and when true
/// cursor needs to move back from the inserted text to the compl_leader.
/// When "preinsert_prefix" is true the longest common prefix is inserted
/// instead of shown match.
int ins_compl_insert(bool move_cursor, bool preinsert_prefix)
/// When "insert_prefix" is true the longest common prefix is inserted instead
/// of shown match.
void ins_compl_insert(bool move_cursor, bool insert_prefix)
{
int compl_len = get_compl_len();
bool preinsert = ins_compl_has_preinsert();
@@ -5128,12 +5142,13 @@ int ins_compl_insert(bool move_cursor, bool preinsert_prefix)
size_t leader_len = ins_compl_leader_len();
char *has_multiple = strchr(cp_str, '\n');
if (preinsert_prefix) {
if (insert_prefix) {
cp_str = find_common_prefix(&cp_str_len, false);
if (cp_str == NULL) {
cp_str = find_common_prefix(&cp_str_len, true);
if (cp_str == NULL) {
return FAIL;
cp_str = compl_shown_match->cp_str.data;
cp_str_len = compl_shown_match->cp_str.size;
}
}
} else if (cpt_sources_array != NULL) {
@@ -5160,18 +5175,18 @@ int ins_compl_insert(bool move_cursor, bool preinsert_prefix)
ins_compl_expand_multiple(cp_str + compl_len);
} else {
ins_compl_insert_bytes(cp_str + compl_len,
preinsert_prefix ? (int)cp_str_len - compl_len : -1);
if (preinsert && move_cursor) {
insert_prefix ? (int)cp_str_len - compl_len : -1);
if ((preinsert || insert_prefix) && move_cursor) {
curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len);
}
}
}
compl_used_match = !(match_at_original_text(compl_shown_match)
|| (preinsert && !compl_autocomplete));
|| (preinsert && !insert_prefix));
dict_T *dict = ins_compl_dict_alloc(compl_shown_match);
set_vim_var_dict(VV_COMPLETED_ITEM, dict);
return OK;
compl_hi_on_autocompl_longest = insert_prefix && move_cursor;
}
/// show the file name for the completion match (if any). Truncate the file
@@ -5240,7 +5255,8 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a
bool found_end = false;
compl_T *found_compl = NULL;
unsigned cur_cot_flags = get_cot_flags();
bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0 || compl_autocomplete;
bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0
|| (compl_autocomplete && !ins_compl_has_preinsert());
bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
while (--todo >= 0) {
@@ -5351,7 +5367,8 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
const bool started = compl_started;
buf_T *const orig_curbuf = curbuf;
unsigned cur_cot_flags = get_cot_flags();
bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0 || compl_autocomplete;
bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0
|| (compl_autocomplete && !ins_compl_has_preinsert());
bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
bool compl_preinsert = ins_compl_has_preinsert();
@@ -5397,35 +5414,18 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
return -1;
}
compl_autocomplete_preinsert = false;
// Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) {
bool insert_orig = !compl_preinsert;
if (compl_preinsert && compl_autocomplete) {
if (ins_compl_insert(true, true) == OK) {
compl_autocomplete_preinsert = true;
} else {
insert_orig = true;
}
}
if (insert_orig) {
if (!started && ins_compl_preinsert_longest()) {
ins_compl_insert(true, true);
} else if (compl_no_insert && !started && !compl_preinsert) {
ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1);
}
compl_used_match = false;
restore_orig_extmarks();
} else if (insert_match) {
if (!compl_get_longest || compl_used_match) {
bool none_selected = match_at_original_text(compl_shown_match);
if (compl_preinsert && compl_autocomplete && none_selected) {
if (ins_compl_insert(none_selected, true) == OK) {
compl_autocomplete_preinsert = none_selected;
} else {
(void)ins_compl_insert(false, false);
}
} else {
(void)ins_compl_insert(!compl_autocomplete, false);
}
bool preinsert_longest = ins_compl_preinsert_longest()
&& match_at_original_text(compl_shown_match); // none selected
ins_compl_insert(compl_preinsert || preinsert_longest, preinsert_longest);
} else {
assert(compl_leader.data != NULL);
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1);
@@ -5534,8 +5534,8 @@ void ins_compl_check_keys(int frequency, bool in_compl_func)
}
}
if (compl_pending != 0 && !got_int && !(cot_flags & kOptCotFlagNoinsert)
&& !compl_autocomplete) {
if (compl_pending && !got_int && !(cot_flags & kOptCotFlagNoinsert)
&& (!compl_autocomplete || ins_compl_has_preinsert())) {
// Insert the first match immediately and advance compl_shown_match,
// before finding other matches.
int todo = compl_pending > 0 ? compl_pending : -compl_pending;
@@ -6599,3 +6599,11 @@ static void cpt_compl_refresh(void)
// Make the list cyclic
compl_matches = ins_compl_make_cyclic();
}
/// "preinserted()" function
void f_preinserted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
if (ins_compl_preinsert_effect()) {
rettv->vval.v_number = 1;
}
}

View File

@@ -1663,11 +1663,22 @@ local options = {
to gather more alternatives for your candidate list,
see 'completefuzzycollect'.
longest Only insert the longest common text of the matches. If
the menu is displayed you can use CTRL-L to add more
characters. Whether case is ignored depends on the kind
of completion. For buffer text the 'ignorecase' option is
used.
longest
When 'autocomplete' is not active, only the longest
common prefix of the matches is inserted. If the popup
menu is displayed, you can use CTRL-L to add more
characters. Whether case is ignored depends on the type
of completion. For buffer text the 'ignorecase' option
applies.
When 'autocomplete' is active and no completion item is
selected, the longest common prefix of the matches is
inserted after the cursor. The prefix is taken either
from all displayed items or only from items in the current
buffer. The inserted text is highlighted with
|hl-PreInsert|, and the cursor position does not change
(similar to `"preinsert"`). Press CTRL-Y to accept.
See also |preinserted()|.
menu Use a popup menu to show the possible completions. The
menu is only shown when there is more than one match and
@@ -1700,22 +1711,21 @@ local options = {
with "menu" or "menuone". Overrides "preview".
preinsert
When 'autocomplete' is not active, inserts the part of the
first candidate word beyond the current completion leader,
highlighted with |hl-PreInsert|. The cursor doesn't move.
Requires "fuzzy" unset and "menuone" in 'completeopt'.
When 'autocomplete' is active, inserts the longest common
prefix of matches (from all shown items or from the
current buffer items). This occurs only when no menu item
is selected. Press CTRL-Y to accept.
Inserts the text of the first completion candidate
beyond the current leader, highlighted with |hl-PreInsert|.
The cursor does not move.
Requires "fuzzy" to be unset, and either "menuone" in
'completeopt' or 'autocomplete' enabled. When
'autocomplete' is enabled, this does not work if
'ignorecase' is set without 'infercase'.
See also |preinserted()|.
preview Show extra information about the currently selected
completion in the preview window. Only works in
combination with "menu" or "menuone".
Only "fuzzy", "popup", "preinsert" and "preview" have an effect when
'autocomplete' is enabled.
Only "fuzzy", "longest", "popup", "preinsert" and "preview" have an
effect when 'autocomplete' is enabled.
This option does not apply to |cmdline-completion|. See 'wildoptions'
for that.

View File

@@ -1496,18 +1496,18 @@ describe('completion', function()
-- During delay wait, user can open menu using CTRL_N completion
feed('<Esc>')
command('set completeopt=menuone,preinsert')
command('set completeopt=menuone,longest')
feed('Sf<C-N>')
screen:expect([[
foo |
foobar |
foobarbaz |
f{102:^oo} |
{12:foo }{1: }|
foo^ |
{4:foo }{1: }|
{4:foobar }{1: }|
{4:foobarbaz }{1: }|
{1:~ }|*2
{5:-- Keyword completion (^N^P) }{6:match 1 of 3} |
{5:-- Keyword completion (^N^P) }{19:Back at original} |
]])
-- After the menu is open, ^N/^P and Up/Down should not delay

View File

@@ -126,7 +126,6 @@ func Test_omni_dash()
if a:findstart
return 5
else
echom a:base
return ['-help', '-v']
endif
endfunc
@@ -4231,6 +4230,145 @@ func Test_completeopt_preinsert()
delfunc Omni_test
endfunc
func Test_autocomplete_completeopt_preinsert()
func Omni_test(findstart, base)
if a:findstart
return col(".") - 1
endif
return [#{word: "fobar"}, #{word: "foobar"}]
endfunc
set omnifunc=Omni_test complete+=o
set completeopt=preinsert autocomplete
" set completeopt=preinsert,menuone autocomplete
func GetLine()
let g:line = getline('.')
let g:col = col('.')
endfunc
call Ntest_override("char_avail", 1)
new
inoremap <buffer><F5> <C-R>=GetLine()<CR>
call feedkeys("Sfo\<F5>\<ESC>", 'tx')
call assert_equal("fobar", g:line)
call assert_equal(3, g:col)
call feedkeys("Sfoo\<F5>\<ESC>", 'tx')
call assert_equal("foobar", g:line)
call feedkeys("Sfoo\<BS>\<BS>\<BS>", 'tx')
call assert_equal("", getline('.'))
" delete a character
call feedkeys("Sfoo\<BS>b\<F5>\<ESC>", 'tx')
call assert_equal("fobar", g:line)
call assert_equal(4, g:col)
set complete&
%d
call setline(1, ['fobar', 'foobar'])
call feedkeys("Gofoo\<BS>\<BS>\<F5>\<ESC>", 'tx')
call assert_equal("fobar", g:line)
call assert_equal(2, g:col)
call feedkeys("Shello wo\<Left>\<Left>\<Left>f\<F5>\<ESC>", 'tx')
call assert_equal("hello fobar wo", g:line)
call assert_equal(9, g:col)
call feedkeys("Shello wo\<Left>\<Left>\<Left>f\<BS>\<F5>\<ESC>", 'tx')
call assert_equal("hello wo", g:line)
call assert_equal(8, g:col)
call feedkeys("Shello wo\<Left>\<Left>\<Left>foo\<F5>\<ESC>", 'tx')
call assert_equal("hello foobar wo", g:line)
call assert_equal(11, g:col)
call feedkeys("Shello wo\<Left>\<Left>\<Left>foo\<BS>b\<F5>\<ESC>", 'tx')
call assert_equal("hello fobar wo", g:line)
call assert_equal(11, g:col)
" confirm
call feedkeys("Sf\<C-Y>", 'tx')
call assert_equal("fobar", getline('.'))
call assert_equal(5, col('.'))
" cancel
call feedkeys("Sfo\<C-E>", 'tx')
call assert_equal("fo", getline('.'))
call assert_equal(2, col('.'))
" delete preinsert part
call feedkeys("Sfo ", 'tx')
call assert_equal("fo ", getline('.'))
call assert_equal(3, col('.'))
" can not work with fuzzy
set cot+=fuzzy
call feedkeys("Sf", 'tx')
call assert_equal("f", getline('.'))
set cot-=fuzzy
" does not work with 'ignorecase' unless 'infercase' is also enabled
%d
call setline(1, ['FIX', 'fobar', 'foobar'])
set ignorecase
call feedkeys("Gof\<F5>\<ESC>", 'tx')
call assert_equal("f", g:line) " should not produce 'FIX'
set infercase
call feedkeys("Gof\<F5>\<ESC>", 'tx')
call assert_equal("fix", g:line)
set ignorecase& infercase&
%delete _
let &l:undolevels = &l:undolevels
normal! ifoo
let &l:undolevels = &l:undolevels
normal! obar
let &l:undolevels = &l:undolevels
normal! obaz
let &l:undolevels = &l:undolevels
func CheckUndo()
let g:errmsg = ''
call assert_equal(['foo', 'bar', 'baz'], getline(1, '$'))
undo
call assert_equal(['foo', 'bar'], getline(1, '$'))
undo
call assert_equal(['foo'], getline(1, '$'))
undo
call assert_equal([''], getline(1, '$'))
later 3
call assert_equal(['foo', 'bar', 'baz'], getline(1, '$'))
call assert_equal('', v:errmsg)
endfunc
" Check that switching buffer with "preinsert" doesn't corrupt undo.
new
setlocal bufhidden=wipe
inoremap <buffer> <F2> <Cmd>enew!<CR>
call feedkeys("if\<F2>\<Esc>", 'tx')
bwipe!
call CheckUndo()
" Check that closing window with "preinsert" doesn't corrupt undo.
new
setlocal bufhidden=wipe
inoremap <buffer> <F2> <Cmd>close!<CR>
call feedkeys("if\<F2>\<Esc>", 'tx')
call CheckUndo()
%delete _
delfunc CheckUndo
bw!
set cot&
set omnifunc&
set autocomplete&
call Ntest_override("char_avail", 0)
delfunc Omni_test
delfunc GetLine
endfunc
" Check that mark positions are correct after triggering multiline completion.
func Test_complete_multiline_marks()
func Omni_test(findstart, base)
@@ -5696,7 +5834,7 @@ func Test_autocompletedelay()
call VerifyScreenDump(buf, 'Test_autocompletedelay_6', {})
" During delay wait, user can open menu using CTRL_N completion
call term_sendkeys(buf, "\<Esc>:set completeopt=menuone,preinsert\<CR>")
call term_sendkeys(buf, "\<Esc>:set completeopt=menuone,longest\<CR>")
call term_sendkeys(buf, "Sf\<C-N>")
call VerifyScreenDump(buf, 'Test_autocompletedelay_7', {})
@@ -5720,16 +5858,18 @@ func Test_autocompletedelay()
call StopVimInTerminal(buf)
endfunc
func Test_autocomplete_completeopt_preinsert()
" Preinsert longest prefix when autocomplete
func Test_autocomplete_longest()
func GetLine()
let g:line = getline('.')
let g:col = col('.')
let g:preinserted = preinserted()
endfunc
call Ntest_override("char_avail", 1)
new
inoremap <buffer><F5> <C-R>=GetLine()<CR>
set completeopt=preinsert autocomplete
set completeopt=longest autocomplete
call setline(1, ["foobar", "foozbar"])
call feedkeys("Go\<ESC>", 'tx')
@@ -5761,10 +5901,15 @@ func Test_autocomplete_completeopt_preinsert()
call DoTest("f\<C-P>", 'foobar', 7)
call DoTest("fo\<BS>\<C-P>", 'foobar', 7)
" <C-Y> to accept preinserted text
inoremap <buffer><F6> <C-R>=pumvisible()<CR>
call DoTest("zar\<C-O>0f", 'foozar', 2)
call DoTest("zar\<C-O>0f\<C-Y>", 'foozar', 4)
call DoTest("zar\<C-O>0f\<C-Y>\<F6>", 'foo1zar', 5)
call DoTest("zar\<C-O>0f\<C-Y>\<BS>", 'foozar', 3)
call DoTest("zar\<C-O>0f\<C-Y>\<BS>\<F6>", 'fo1zar', 4)
" Select items in menu
call DoTest("zar\<C-O>0f\<C-N>", 'foozbarzar', 8)
call DoTest("zar\<C-O>0f\<C-N>\<C-N>", 'foobarzar', 7)
call DoTest("zar\<C-O>0f\<C-N>\<C-N>\<C-N>", 'foozar', 2)
@@ -5778,6 +5923,34 @@ func Test_autocomplete_completeopt_preinsert()
call DoTest("f", 'f', 2)
set cot-=fuzzy
" preinserted()
call DoTest("f", 'foo', 2)
call assert_equal(1, g:preinserted)
call assert_equal(0, preinserted())
call DoTest("f\<BS>", '', 1)
call assert_equal(0, g:preinserted)
call DoTest("f ", 'f ', 3)
call assert_equal(0, g:preinserted)
call DoTest("foob", 'foobar', 5)
call assert_equal(1, g:preinserted)
call DoTest("foob\<BS>\<BS>x", 'fox', 4)
call assert_equal(0, g:preinserted)
" Complete non-keyword
func Omni(findstart, base)
if a:findstart
return 5
else
return ['xyz:']
endif
endfunc
set omnifunc=Omni
set cpt+=o
call DoTest("xyz:", "xyz:xyz:", 5)
call DoTest("xyz:\<BS>\<BS>", "xyz:", 3)
set omnifunc& cpt&
delfunc Omni
" leader should match prefix of inserted word
%delete
set smartcase ignorecase
@@ -5788,6 +5961,16 @@ func Test_autocomplete_completeopt_preinsert()
call assert_equal('FOO', g:line)
set smartcase& ignorecase&
" avoid repeating text that is already present after the cursor
%delete
call setline(1, ["foobarbaz", ""])
call feedkeys($"G", 'tx')
call DoTest("baz\<C-O>0f", "foobarbaz", 2)
call feedkeys($"Sxfoozar\<CR>\<Esc>", 'tx')
call DoTest("baz\<C-O>0f", "foobarbaz", 2)
call feedkeys($"Sfoozar\<CR>\<Esc>", 'tx')
call DoTest("baz\<C-O>0f", "foobaz", 2)
" Verify that redo (dot) works
%delete
call setline(1, ["foobar", "foozbar", "foobaz", "changed", "change"])
@@ -5824,7 +6007,7 @@ func Test_autocomplete_completeopt_preinsert()
call assert_equal('', v:errmsg)
endfunc
" Check that switching buffer with "preinsert" doesn't corrupt undo.
" Check that switching buffer with "longest" doesn't corrupt undo.
new
setlocal bufhidden=wipe
inoremap <buffer> <F2> <Cmd>enew!<CR>
@@ -5832,7 +6015,7 @@ func Test_autocomplete_completeopt_preinsert()
bwipe!
call CheckUndo()
" Check that closing window with "preinsert" doesn't corrupt undo.
" Check that closing window with "longest" doesn't corrupt undo.
new
setlocal bufhidden=wipe
inoremap <buffer> <F2> <Cmd>close!<CR>