Merge pull request #35962 from zeertzjq/vim-9.1.1797

vim-patch:9.1.{1797,1800,1810}
This commit is contained in:
zeertzjq
2025-09-30 09:54:02 +08:00
committed by GitHub
11 changed files with 410 additions and 129 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, to gather more alternatives for your candidate list,
see 'completefuzzycollect'. see 'completefuzzycollect'.
longest Only insert the longest common text of the matches. If longest
the menu is displayed you can use CTRL-L to add more When 'autocomplete' is not active, only the longest
characters. Whether case is ignored depends on the kind common prefix of the matches is inserted. If the popup
of completion. For buffer text the 'ignorecase' option is menu is displayed, you can use CTRL-L to add more
used. 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 Use a popup menu to show the possible completions. The
menu is only shown when there is more than one match and 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". with "menu" or "menuone". Overrides "preview".
preinsert preinsert
When 'autocomplete' is not active, inserts the part of the Inserts the text of the first completion candidate
first candidate word beyond the current completion leader, beyond the current leader, highlighted with |hl-PreInsert|.
highlighted with |hl-PreInsert|. The cursor doesn't move. The cursor does not move.
Requires "fuzzy" unset and "menuone" in 'completeopt'. Requires "fuzzy" to be unset, and either "menuone" in
'completeopt' or 'autocomplete' enabled. When
When 'autocomplete' is active, inserts the longest common 'autocomplete' is enabled, this does not work if
prefix of matches (from all shown items or from the 'ignorecase' is set without 'infercase'.
current buffer items). This occurs only when no menu item See also |preinserted()|.
is selected. Press CTRL-Y to accept.
preview Show extra information about the currently selected preview Show extra information about the currently selected
completion in the preview window. Only works in completion in the preview window. Only works in
combination with "menu" or "menuone". combination with "menu" or "menuone".
Only "fuzzy", "popup", "preinsert" and "preview" have an effect when Only "fuzzy", "longest", "popup", "preinsert" and "preview" have an
'autocomplete' is enabled. effect when 'autocomplete' is enabled.
This option does not apply to |cmdline-completion|. See 'wildoptions' This option does not apply to |cmdline-completion|. See 'wildoptions'
for that. for that.

View File

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

View File

@@ -7232,6 +7232,15 @@ pow({x}, {y}) *pow()*
Return: ~ Return: ~
(`number`) (`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()* prevnonblank({lnum}) *prevnonblank()*
Return the line number of the first line at or above {lnum} Return the line number of the first line at or above {lnum}
that is not blank. Example: >vim 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, --- to gather more alternatives for your candidate list,
--- see 'completefuzzycollect'. --- see 'completefuzzycollect'.
--- ---
--- longest Only insert the longest common text of the matches. If --- longest
--- the menu is displayed you can use CTRL-L to add more --- When 'autocomplete' is not active, only the longest
--- characters. Whether case is ignored depends on the kind --- common prefix of the matches is inserted. If the popup
--- of completion. For buffer text the 'ignorecase' option is --- menu is displayed, you can use CTRL-L to add more
--- used. --- 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 Use a popup menu to show the possible completions. The
--- menu is only shown when there is more than one match and --- 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". --- with "menu" or "menuone". Overrides "preview".
--- ---
--- preinsert --- preinsert
--- When 'autocomplete' is not active, inserts the part of the --- Inserts the text of the first completion candidate
--- first candidate word beyond the current completion leader, --- beyond the current leader, highlighted with `hl-PreInsert`.
--- highlighted with `hl-PreInsert`. The cursor doesn't move. --- The cursor does not move.
--- Requires "fuzzy" unset and "menuone" in 'completeopt'. --- Requires "fuzzy" to be unset, and either "menuone" in
--- --- 'completeopt' or 'autocomplete' enabled. When
--- When 'autocomplete' is active, inserts the longest common --- 'autocomplete' is enabled, this does not work if
--- prefix of matches (from all shown items or from the --- 'ignorecase' is set without 'infercase'.
--- current buffer items). This occurs only when no menu item --- See also `preinserted()`.
--- is selected. Press CTRL-Y to accept.
--- ---
--- preview Show extra information about the currently selected --- preview Show extra information about the currently selected
--- completion in the preview window. Only works in --- completion in the preview window. Only works in
--- combination with "menu" or "menuone". --- combination with "menu" or "menuone".
--- ---
--- Only "fuzzy", "popup", "preinsert" and "preview" have an effect when --- Only "fuzzy", "longest", "popup", "preinsert" and "preview" have an
--- 'autocomplete' is enabled. --- effect when 'autocomplete' is enabled.
--- ---
--- This option does not apply to `cmdline-completion`. See 'wildoptions' --- This option does not apply to `cmdline-completion`. See 'wildoptions'
--- for that. --- for that.

View File

@@ -6554,6 +6554,14 @@ function vim.fn.perleval(expr) end
--- @return number --- @return number
function vim.fn.pow(x, y) end 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} --- Return the line number of the first line at or above {lnum}
--- that is not blank. Example: >vim --- that is not blank. Example: >vim
--- let ind = indent(prevnonblank(v:lnum - 1)) --- 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))) && (s->c == CAR || s->c == K_KENTER || s->c == NL)))
&& stop_arrow() == OK) { && stop_arrow() == OK) {
ins_compl_delete(false); ins_compl_delete(false);
if (ins_compl_has_preinsert() && ins_compl_autocomplete_enabled()) { ins_compl_insert(false, !ins_compl_has_preinsert());
(void)ins_compl_insert(false, true); if (ins_compl_preinsert_longest()) {
} else { ins_compl_init_get_longest();
(void)ins_compl_insert(false, false); return 1;
} }
} else if (ascii_iswhite_nl_or_nul(s->c) && ins_compl_preinsert_effect()) { } else if (ascii_iswhite_nl_or_nul(s->c) && ins_compl_preinsert_effect()) {
// Delete preinserted text when typing special chars // Delete preinserted text when typing special chars

View File

@@ -8024,6 +8024,18 @@ M.funcs = {
returns = 'number', returns = 'number',
signature = 'pow({x}, {y})', 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 = { prevnonblank = {
args = 1, args = 1,
base = 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 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_time_slice_expired = false; ///< time budget exceeded for current source
static bool compl_from_nonkeyword = false; ///< completion started from non-keyword 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. // Halve the current completion timeout, simulating exponential decay.
#define COMPL_MIN_TIMEOUT_MS 5 #define COMPL_MIN_TIMEOUT_MS 5
@@ -889,6 +889,15 @@ static bool is_nearest_active(void)
&& !(flags & kOptCotFlagFuzzy); && !(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 /// Add a match to the list of matches
/// ///
/// @param[in] str text of the match to add /// @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; compl_curr_match = match;
// Find the longest common string if still doing that. // 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); ins_compl_longest_match(match);
} }
@@ -1107,17 +1117,12 @@ static size_t ins_compl_leader_len(void)
/// -1 means normal item. /// -1 means normal item.
int ins_compl_col_range_attr(linenr_T lnum, int col) 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; int attr;
if ((get_cot_flags() & kOptCotFlagFuzzy) if ((get_cot_flags() & kOptCotFlagFuzzy)
|| (!has_preinsert || (!compl_hi_on_autocompl_longest && ins_compl_preinsert_longest())
&& (attr = syn_name2attr("ComplMatchIns")) == 0) || (attr = syn_name2attr(has_preinsert ? "PreInsert" : "ComplMatchIns")) == 0) {
|| (!compl_autocomplete && has_preinsert
&& (attr = syn_name2attr("PreInsert")) == 0)
|| (compl_autocomplete
&& (!compl_autocomplete_preinsert
|| (attr = syn_name2attr("PreInsert")) == 0))) {
return -1; return -1;
} }
@@ -1511,7 +1516,8 @@ static int ins_compl_build_pum(void)
} }
unsigned cur_cot_flags = get_cot_flags(); 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; bool fuzzy_filter = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
compl_T *match_head = NULL, *match_tail = NULL; compl_T *match_head = NULL, *match_tail = NULL;
@@ -2132,6 +2138,9 @@ int ins_compl_len(void)
bool ins_compl_has_preinsert(void) bool ins_compl_has_preinsert(void)
{ {
unsigned cur_cot_flags = get_cot_flags(); unsigned cur_cot_flags = get_cot_flags();
if (compl_autocomplete && p_ic && !p_inf) {
return false;
}
return (!compl_autocomplete return (!compl_autocomplete
? (cur_cot_flags & (kOptCotFlagPreinsert|kOptCotFlagFuzzy|kOptCotFlagMenuone)) ? (cur_cot_flags & (kOptCotFlagPreinsert|kOptCotFlagFuzzy|kOptCotFlagMenuone))
== (kOptCotFlagPreinsert|kOptCotFlagMenuone) == (kOptCotFlagPreinsert|kOptCotFlagMenuone)
@@ -2143,19 +2152,13 @@ bool ins_compl_has_preinsert(void)
/// the `compl_ins_end_col` range. /// the `compl_ins_end_col` range.
bool ins_compl_preinsert_effect(void) bool ins_compl_preinsert_effect(void)
{ {
if (!ins_compl_has_preinsert()) { if (!ins_compl_has_preinsert() && !ins_compl_preinsert_longest()) {
return false; return false;
} }
return curwin->w_cursor.col < compl_ins_end_col; 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 /// Delete one character before the cursor and show the subset of the matches
/// that match the word that is now before the cursor. /// 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 /// 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)); (size_t)(p_off - (ptrdiff_t)compl_col));
// Clear selection if a menu item is currently selected in autocompletion // 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; 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. // Matches were cleared, need to search for them now.
// Set "compl_restarting" to avoid that the first match is inserted. // Set "compl_restarting" to avoid that the first match is inserted.
compl_restarting = true; 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) { if (ins_complete(Ctrl_N, true) == FAIL) {
compl_cont_status = 0; 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. // Show the popup menu with a different set of matches.
ins_compl_show_pum(); ins_compl_show_pum();
compl_autocomplete_preinsert = false;
// Don't let Enter select the original text when there is no popup menu. // Don't let Enter select the original text when there is no popup menu.
if (compl_match_array == NULL) { if (compl_match_array == NULL) {
compl_enter_selects = false; compl_enter_selects = false;
} else if (ins_compl_has_preinsert() && compl_leader.size > 0) { } else if (ins_compl_has_preinsert() && compl_leader.size > 0) {
if (compl_started && compl_autocomplete && !ins_compl_preinsert_effect()) { ins_compl_insert(true, false);
if (ins_compl_insert(true, true) == OK) { } else if (compl_started && ins_compl_preinsert_longest()
compl_autocomplete_preinsert = true; && compl_leader.size > 0 && !ins_compl_preinsert_effect()) {
} ins_compl_insert(true, true);
} else {
(void)ins_compl_insert(true, false);
}
} }
// Don't let Enter select when use user function and refresh_always is set // Don't let Enter select when use user function and refresh_always is set
if (ins_compl_refresh_always()) { 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, ptr = ins_compl_get_next_word_or_line(st->ins_buf,
st->cur_match_pos, &len, &cont_s_ipos); 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)) { && strcmp(ptr, compl_pattern.data) == 0)) {
continue; continue;
} }
@@ -4895,7 +4898,7 @@ static int ins_compl_get_exp(pos_T *ini)
} }
may_trigger_modechanged(); may_trigger_modechanged();
if (is_nearest_active()) { if (is_nearest_active() && !ins_compl_has_preinsert()) {
sort_compl_match_list(cp_compare_nearest); 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); xfree(match_count);
if (len > (int)ins_compl_leader_len()) { 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; *prefix_len = (size_t)len;
return first; return first;
} }
@@ -5117,9 +5135,9 @@ static char *find_common_prefix(size_t *prefix_len, bool curbuf_only)
/// Insert the new text being completed. /// Insert the new text being completed.
/// "move_cursor" is used when 'completeopt' includes "preinsert" and when true /// "move_cursor" is used when 'completeopt' includes "preinsert" and when true
/// cursor needs to move back from the inserted text to the compl_leader. /// cursor needs to move back from the inserted text to the compl_leader.
/// When "preinsert_prefix" is true the longest common prefix is inserted /// When "insert_prefix" is true the longest common prefix is inserted instead
/// instead of shown match. /// of shown match.
int ins_compl_insert(bool move_cursor, bool preinsert_prefix) void ins_compl_insert(bool move_cursor, bool insert_prefix)
{ {
int compl_len = get_compl_len(); int compl_len = get_compl_len();
bool preinsert = ins_compl_has_preinsert(); 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(); size_t leader_len = ins_compl_leader_len();
char *has_multiple = strchr(cp_str, '\n'); char *has_multiple = strchr(cp_str, '\n');
if (preinsert_prefix) { if (insert_prefix) {
cp_str = find_common_prefix(&cp_str_len, false); cp_str = find_common_prefix(&cp_str_len, false);
if (cp_str == NULL) { if (cp_str == NULL) {
cp_str = find_common_prefix(&cp_str_len, true); cp_str = find_common_prefix(&cp_str_len, true);
if (cp_str == NULL) { 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) { } 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); ins_compl_expand_multiple(cp_str + compl_len);
} else { } else {
ins_compl_insert_bytes(cp_str + compl_len, ins_compl_insert_bytes(cp_str + compl_len,
preinsert_prefix ? (int)cp_str_len - compl_len : -1); insert_prefix ? (int)cp_str_len - compl_len : -1);
if (preinsert && move_cursor) { if ((preinsert || insert_prefix) && move_cursor) {
curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len);
} }
} }
} }
compl_used_match = !(match_at_original_text(compl_shown_match) 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); dict_T *dict = ins_compl_dict_alloc(compl_shown_match);
set_vim_var_dict(VV_COMPLETED_ITEM, dict); 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 /// 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; bool found_end = false;
compl_T *found_compl = NULL; compl_T *found_compl = NULL;
unsigned cur_cot_flags = get_cot_flags(); 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; bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
while (--todo >= 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; const bool started = compl_started;
buf_T *const orig_curbuf = curbuf; buf_T *const orig_curbuf = curbuf;
unsigned cur_cot_flags = get_cot_flags(); 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_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
bool compl_preinsert = ins_compl_has_preinsert(); 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; return -1;
} }
compl_autocomplete_preinsert = false;
// Insert the text of the new completion, or the compl_leader. // Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) { if (!started && ins_compl_preinsert_longest()) {
bool insert_orig = !compl_preinsert; ins_compl_insert(true, true);
if (compl_preinsert && compl_autocomplete) { } else if (compl_no_insert && !started && !compl_preinsert) {
if (ins_compl_insert(true, true) == OK) { ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1);
compl_autocomplete_preinsert = true;
} else {
insert_orig = true;
}
}
if (insert_orig) {
ins_compl_insert_bytes(compl_orig_text.data + get_compl_len(), -1);
}
compl_used_match = false; compl_used_match = false;
restore_orig_extmarks(); restore_orig_extmarks();
} else if (insert_match) { } else if (insert_match) {
if (!compl_get_longest || compl_used_match) { if (!compl_get_longest || compl_used_match) {
bool none_selected = match_at_original_text(compl_shown_match); bool preinsert_longest = ins_compl_preinsert_longest()
if (compl_preinsert && compl_autocomplete && none_selected) { && match_at_original_text(compl_shown_match); // none selected
if (ins_compl_insert(none_selected, true) == OK) { ins_compl_insert(compl_preinsert || preinsert_longest, preinsert_longest);
compl_autocomplete_preinsert = none_selected;
} else {
(void)ins_compl_insert(false, false);
}
} else {
(void)ins_compl_insert(!compl_autocomplete, false);
}
} else { } else {
assert(compl_leader.data != NULL); assert(compl_leader.data != NULL);
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); 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) if (compl_pending && !got_int && !(cot_flags & kOptCotFlagNoinsert)
&& !compl_autocomplete) { && (!compl_autocomplete || ins_compl_has_preinsert())) {
// Insert the first match immediately and advance compl_shown_match, // Insert the first match immediately and advance compl_shown_match,
// before finding other matches. // before finding other matches.
int todo = compl_pending > 0 ? compl_pending : -compl_pending; 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) void ins_compl_enable_autocomplete(void)
{ {
compl_autocomplete = true; compl_autocomplete = true;
compl_get_longest = false;
} }
/// Remove (if needed) and show the popup menu /// Remove (if needed) and show the popup menu
@@ -6599,3 +6604,11 @@ static void cpt_compl_refresh(void)
// Make the list cyclic // Make the list cyclic
compl_matches = ins_compl_make_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, to gather more alternatives for your candidate list,
see 'completefuzzycollect'. see 'completefuzzycollect'.
longest Only insert the longest common text of the matches. If longest
the menu is displayed you can use CTRL-L to add more When 'autocomplete' is not active, only the longest
characters. Whether case is ignored depends on the kind common prefix of the matches is inserted. If the popup
of completion. For buffer text the 'ignorecase' option is menu is displayed, you can use CTRL-L to add more
used. 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 Use a popup menu to show the possible completions. The
menu is only shown when there is more than one match and menu is only shown when there is more than one match and
@@ -1700,22 +1711,21 @@ local options = {
with "menu" or "menuone". Overrides "preview". with "menu" or "menuone". Overrides "preview".
preinsert preinsert
When 'autocomplete' is not active, inserts the part of the Inserts the text of the first completion candidate
first candidate word beyond the current completion leader, beyond the current leader, highlighted with |hl-PreInsert|.
highlighted with |hl-PreInsert|. The cursor doesn't move. The cursor does not move.
Requires "fuzzy" unset and "menuone" in 'completeopt'. Requires "fuzzy" to be unset, and either "menuone" in
'completeopt' or 'autocomplete' enabled. When
When 'autocomplete' is active, inserts the longest common 'autocomplete' is enabled, this does not work if
prefix of matches (from all shown items or from the 'ignorecase' is set without 'infercase'.
current buffer items). This occurs only when no menu item See also |preinserted()|.
is selected. Press CTRL-Y to accept.
preview Show extra information about the currently selected preview Show extra information about the currently selected
completion in the preview window. Only works in completion in the preview window. Only works in
combination with "menu" or "menuone". combination with "menu" or "menuone".
Only "fuzzy", "popup", "preinsert" and "preview" have an effect when Only "fuzzy", "longest", "popup", "preinsert" and "preview" have an
'autocomplete' is enabled. effect when 'autocomplete' is enabled.
This option does not apply to |cmdline-completion|. See 'wildoptions' This option does not apply to |cmdline-completion|. See 'wildoptions'
for that. for that.

View File

@@ -1496,13 +1496,13 @@ describe('completion', function()
-- During delay wait, user can open menu using CTRL_N completion -- During delay wait, user can open menu using CTRL_N completion
feed('<Esc>') feed('<Esc>')
command('set completeopt=menuone,preinsert') command('set completeopt=menuone')
feed('Sf<C-N>') feed('Sf<C-N>')
screen:expect([[ screen:expect([[
foo | foo |
foobar | foobar |
foobarbaz | foobarbaz |
f{102:^oo} | foo^ |
{12:foo }{1: }| {12:foo }{1: }|
{4:foobar }{1: }| {4:foobar }{1: }|
{4:foobarbaz }{1: }| {4:foobarbaz }{1: }|

View File

@@ -124,10 +124,9 @@ endfunc
func Test_omni_dash() func Test_omni_dash()
func Omni(findstart, base) func Omni(findstart, base)
if a:findstart if a:findstart
return 5 return 5
else else
echom a:base return ['-help', '-v']
return ['-help', '-v']
endif endif
endfunc endfunc
set omnifunc=Omni set omnifunc=Omni
@@ -4231,6 +4230,145 @@ func Test_completeopt_preinsert()
delfunc Omni_test delfunc Omni_test
endfunc 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. " Check that mark positions are correct after triggering multiline completion.
func Test_complete_multiline_marks() func Test_complete_multiline_marks()
func Omni_test(findstart, base) func Omni_test(findstart, base)
@@ -5696,7 +5834,7 @@ func Test_autocompletedelay()
call VerifyScreenDump(buf, 'Test_autocompletedelay_6', {}) call VerifyScreenDump(buf, 'Test_autocompletedelay_6', {})
" During delay wait, user can open menu using CTRL_N completion " 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 term_sendkeys(buf, "Sf\<C-N>")
call VerifyScreenDump(buf, 'Test_autocompletedelay_7', {}) call VerifyScreenDump(buf, 'Test_autocompletedelay_7', {})
@@ -5720,16 +5858,18 @@ func Test_autocompletedelay()
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
endfunc endfunc
func Test_autocomplete_completeopt_preinsert() " Preinsert longest prefix when autocomplete
func Test_autocomplete_longest()
func GetLine() func GetLine()
let g:line = getline('.') let g:line = getline('.')
let g:col = col('.') let g:col = col('.')
let g:preinserted = preinserted()
endfunc endfunc
call Ntest_override("char_avail", 1) call Ntest_override("char_avail", 1)
new new
inoremap <buffer><F5> <C-R>=GetLine()<CR> inoremap <buffer><F5> <C-R>=GetLine()<CR>
set completeopt=preinsert autocomplete set completeopt=longest autocomplete
call setline(1, ["foobar", "foozbar"]) call setline(1, ["foobar", "foozbar"])
call feedkeys("Go\<ESC>", 'tx') call feedkeys("Go\<ESC>", 'tx')
@@ -5761,10 +5901,15 @@ func Test_autocomplete_completeopt_preinsert()
call DoTest("f\<C-P>", 'foobar', 7) call DoTest("f\<C-P>", 'foobar', 7)
call DoTest("fo\<BS>\<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", 'foozar', 2)
call DoTest("zar\<C-O>0f\<C-Y>", 'foozar', 4) 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>", '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>", 'foozbarzar', 8)
call DoTest("zar\<C-O>0f\<C-N>\<C-N>", 'foobarzar', 7) 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) 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) call DoTest("f", 'f', 2)
set cot-=fuzzy 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 " leader should match prefix of inserted word
%delete %delete
set smartcase ignorecase set smartcase ignorecase
@@ -5788,6 +5961,16 @@ func Test_autocomplete_completeopt_preinsert()
call assert_equal('FOO', g:line) call assert_equal('FOO', g:line)
set smartcase& ignorecase& 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 " Verify that redo (dot) works
%delete %delete
call setline(1, ["foobar", "foozbar", "foobaz", "changed", "change"]) call setline(1, ["foobar", "foozbar", "foobaz", "changed", "change"])
@@ -5801,6 +5984,16 @@ func Test_autocomplete_completeopt_preinsert()
call feedkeys("Go\<ESC>", 'tx') call feedkeys("Go\<ESC>", 'tx')
call DoTest("f\<C-N>\<C-N>\<BS>\<BS>\<BS>\<BS>", 'foo', 3) 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 _ %delete _
let &l:undolevels = &l:undolevels let &l:undolevels = &l:undolevels
normal! ifoo normal! ifoo
@@ -5824,7 +6017,7 @@ func Test_autocomplete_completeopt_preinsert()
call assert_equal('', v:errmsg) call assert_equal('', v:errmsg)
endfunc endfunc
" Check that switching buffer with "preinsert" doesn't corrupt undo. " Check that switching buffer with "longest" doesn't corrupt undo.
new new
setlocal bufhidden=wipe setlocal bufhidden=wipe
inoremap <buffer> <F2> <Cmd>enew!<CR> inoremap <buffer> <F2> <Cmd>enew!<CR>
@@ -5832,7 +6025,7 @@ func Test_autocomplete_completeopt_preinsert()
bwipe! bwipe!
call CheckUndo() call CheckUndo()
" Check that closing window with "preinsert" doesn't corrupt undo. " Check that closing window with "longest" doesn't corrupt undo.
new new
setlocal bufhidden=wipe setlocal bufhidden=wipe
inoremap <buffer> <F2> <Cmd>close!<CR> inoremap <buffer> <F2> <Cmd>close!<CR>
@@ -5842,6 +6035,21 @@ func Test_autocomplete_completeopt_preinsert()
%delete _ %delete _
delfunc CheckUndo 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! bw!
set cot& autocomplete& set cot& autocomplete&
delfunc GetLine delfunc GetLine