commit 77545c0f3c485f366a08b91c10ff48e42ca2fc49
parent 4896178863dc912baaf68ddcddab28eecc8f00d7
Author: zeertzjq <zeertzjq@outlook.com>
Date: Tue, 30 Sep 2025 09:54:02 +0800
Merge pull request #35962 from zeertzjq/vim-9.1.1797
vim-patch:9.1.{1797,1800,1810}
Diffstat:
11 files changed, 410 insertions(+), 129 deletions(-)
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -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.
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
@@ -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
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -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 because
+ "longest" is present in 'completeopt' while 'autocomplete'
+ is active. 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
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -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.
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -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 because
+--- "longest" is present in 'completeopt' while 'autocomplete'
+--- is active. 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))
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
@@ -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
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -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 because
+ "longest" is present in 'completeopt' while 'autocomplete'
+ is active. Otherwise returns zero.
+ ]=],
+ name = 'preinserted',
+ params = {},
+ returns = 'number',
+ signature = 'preinserted()',
+ },
prevnonblank = {
args = 1,
base = 1,
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
@@ -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;
}
@@ -2263,7 +2266,11 @@ static void ins_compl_new_leader(void)
// Matches were cleared, need to search for them now.
// Set "compl_restarting" to avoid that the first match is inserted.
compl_restarting = true;
- compl_autocomplete = ins_compl_has_autocomplete();
+ if (ins_compl_has_autocomplete()) {
+ ins_compl_enable_autocomplete();
+ } else {
+ compl_autocomplete = false;
+ }
if (ins_complete(Ctrl_N, true) == FAIL) {
compl_cont_status = 0;
}
@@ -2291,18 +2298,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 +4391,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 +4898,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 +5111,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 +5135,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 +5146,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 +5179,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 +5259,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 +5371,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 +5418,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) {
- ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1);
- }
+ 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 +5538,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;
@@ -6292,6 +6296,7 @@ int ins_complete(int c, bool enable_pum)
void ins_compl_enable_autocomplete(void)
{
compl_autocomplete = true;
+ compl_get_longest = false;
}
/// Remove (if needed) and show the popup menu
@@ -6599,3 +6604,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;
+ }
+}
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -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.
diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
@@ -1496,13 +1496,13 @@ 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')
feed('Sf<C-N>')
screen:expect([[
foo |
foobar |
foobarbaz |
- f{102:^oo} |
+ foo^ |
{12:foo }{1: }|
{4:foobar }{1: }|
{4:foobarbaz }{1: }|
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
@@ -124,10 +124,9 @@ endfunc
func Test_omni_dash()
func Omni(findstart, base)
if a:findstart
- return 5
+ return 5
else
- echom a:base
- return ['-help', '-v']
+ return ['-help', '-v']
endif
endfunc
set omnifunc=Omni
@@ -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\<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"])
@@ -5801,6 +5984,16 @@ func Test_autocomplete_completeopt_preinsert()
call feedkeys("Go\<ESC>", 'tx')
call DoTest("f\<C-N>\<C-N>\<BS>\<BS>\<BS>\<BS>", 'foo', 3)
+ " Issue #18410: When both "preinsert" and "longest" are set, "preinsert"
+ " takes precedence
+ %delete
+ set autocomplete completeopt+=longest,preinsert
+ call setline(1, ['foobar', 'foofoo', 'foobaz', ''])
+ call feedkeys("G", 'tx')
+ call DoTest("f", 'foobar', 2)
+ call assert_equal(1, g:preinserted)
+
+ " Undo
%delete _
let &l:undolevels = &l:undolevels
normal! ifoo
@@ -5824,7 +6017,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 +6025,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>
@@ -5842,6 +6035,21 @@ func Test_autocomplete_completeopt_preinsert()
%delete _
delfunc CheckUndo
+ " Check that behavior of "longest" in manual completion is unchanged.
+ for ac in [v:false, v:true]
+ let &ac = ac
+ set completeopt=menuone,longest
+ call feedkeys("Ssign u\<C-X>\<C-V>", 'tx')
+ call assert_equal('sign un', getline('.'))
+ call feedkeys("Ssign u\<C-X>\<C-V>\<C-V>", 'tx')
+ call assert_equal('sign undefine', getline('.'))
+ call feedkeys("Ssign u\<C-X>\<C-V>\<C-V>\<C-V>", 'tx')
+ call assert_equal('sign unplace', getline('.'))
+ call feedkeys("Ssign u\<C-X>\<C-V>\<C-V>\<C-V>\<C-V>", 'tx')
+ call assert_equal('sign u', getline('.'))
+ %delete
+ endfor
+
bw!
set cot& autocomplete&
delfunc GetLine