patch 9.1.1797: completion: autocompletion can be improved

Commit: 
https://github.com/vim/vim/commit/c05335082adb21d99d96374779856444a3a0688f
Author: Girish Palya <[email protected]>
Date:   Fri Sep 26 17:29:38 2025 +0000

    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: #18314
    closes: #18387
    
    Signed-off-by: Girish Palya <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 90aefbf33..0a73928e9 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -453,6 +453,7 @@ popup_setoptions({id}, {options})
 popup_settext({id}, {text})    none    set the text of popup window {id}
 popup_show({id})               Number  unhide popup window {id}
 pow({x}, {y})                  Float   {x} to the power of {y}
+preinserted()                  Number  whether text is inserted after cursor
 prevnonblank({lnum})           Number  line nr of non-blank line <= {lnum}
 printf({fmt}, {expr1}...)      String  format text
 prompt_getprompt({buf})                String  get prompt text
@@ -8033,6 +8034,15 @@ pow({x}, {y})                                            
*pow()*
                Return type: |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 type: |Number|
+
+
 prevnonblank({lnum})                                   *prevnonblank()*
                Return the line number of the first line at or above {lnum}
                that is not blank.  Example: >
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 012027518..ebd7e53bc 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2025 Sep 22
+*options.txt*  For Vim version 9.1.  Last change: 2025 Sep 26
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2256,11 +2256,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
@@ -2302,22 +2313,21 @@ A jump table for the options with a short description 
can be found at |Q_op|.
                    {only works when compiled with the |+textprop| feature}
 
           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", "popuphidden", "preinsert" and "preview" have
-       an effect when 'autocomplete' is enabled.
+       Only "fuzzy", "longest", "popup", "popuphidden", "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/tags b/runtime/doc/tags
index 9dc1f3dc5..c59f848d5 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -9774,6 +9774,7 @@ postscript-print-util     print.txt       
/*postscript-print-util*
 postscript-printing    print.txt       /*postscript-printing*
 pow()  builtin.txt     /*pow()*
 ppwiz.vim      syntax.txt      /*ppwiz.vim*
+preinserted()  builtin.txt     /*preinserted()*
 press-enter    message.txt     /*press-enter*
 press-return   message.txt     /*press-return*
 prevcount-variable     eval.txt        /*prevcount-variable*
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 813211e7c..6ee10f433 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1,4 +1,4 @@
-*usr_41.txt*   For Vim version 9.1.  Last change: 2025 Aug 23
+*usr_41.txt*   For Vim version 9.1.  Last change: 2025 Sep 26
 
                     VIM USER MANUAL - by Bram Moolenaar
 
@@ -1135,6 +1135,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/version9.txt b/runtime/doc/version9.txt
index 1227641a2..5447b28c6 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Sep 22
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Sep 26
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41803,6 +41803,8 @@ Functions: ~
 |matchstrlist()|       all the matches of a pattern in a List of strings
 |ngettext()|           lookup single/plural message translation
 |popup_setbuf()|       switch to a different buffer in a popup
+|preinserted()|                whether preinserted text has been inserted 
during
+                       completion (see 'completeopt')
 |str2blob()|           convert a List of strings into a blob
 |test_null_tuple()|    return a null tuple
 |tuple2list()|         turn a Tuple of items into a List
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 1d244b66c..c618afe53 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -156,7 +156,7 @@ syn keyword vimFuncName contained abs acos add and append 
appendbufline argc arg
 syn keyword vimFuncName contained char2nr charclass charcol charidx chdir 
cindent clearmatches cmdcomplete_info col complete complete_add complete_check 
complete_info complete_match confirm copy cos cosh count cscope_connection 
cursor debugbreak deepcopy delete deletebufline did_filetype diff diff_filler 
diff_hlID digraph_get digraph_getlist digraph_set digraph_setlist echoraw empty 
environ err_teapot escape eval eventhandler executable execute exepath exists 
exists_compiled exp expand expandcmd extend extendnew feedkeys filecopy 
filereadable filewritable filter finddir findfile flatten flattennew float2nr 
floor fmod fnameescape fnamemodify foldclosed foldclosedend foldlevel foldtext 
foldtextresult foreach foreground fullcommand funcref function garbagecollect
 syn keyword vimFuncName contained get getbufinfo getbufline getbufoneline 
getbufvar getcellpixels getcellwidths getchangelist getchar getcharmod 
getcharpos getcharsearch getcharstr getcmdcomplpat getcmdcompltype getcmdline 
getcmdpos getcmdprompt getcmdscreenpos getcmdtype getcmdwintype getcompletion 
getcompletiontype getcurpos getcursorcharpos getcwd getenv getfontname getfperm 
getfsize getftime getftype getimstatus getjumplist getline getloclist 
getmarklist getmatches getmousepos getmouseshape getpid getpos getqflist getreg 
getreginfo getregion getregionpos getregtype getscriptinfo getstacktrace 
gettabinfo gettabvar gettabwinvar gettagstack gettext getwininfo getwinpos 
getwinposx getwinposy getwinvar glob glob2regpat globpath has has_key 
haslocaldir hasmapto
 syn keyword vimFuncName contained histadd histdel histget histnr hlID hlexists 
hlget hlset hostname iconv id indent index indexof input inputdialog inputlist 
inputrestore inputsave inputsecret insert instanceof interrupt invert 
isabsolutepath isdirectory isinf islocked isnan items job_getchannel job_info 
job_setoptions job_start job_status job_stop join js_decode js_encode 
json_decode json_encode keys keytrans len libcall libcallnr line line2byte 
lispindent list2blob list2str list2tuple listener_add listener_flush 
listener_remove localtime log log10 luaeval map maparg mapcheck maplist mapnew 
mapset match matchadd matchaddpos matcharg matchbufline matchdelete matchend 
matchfuzzy matchfuzzypos matchlist matchstr matchstrlist matchstrpos max 
menu_info min mkdir mode
-syn keyword vimFuncName contained mzeval nextnonblank ngettext nr2char or 
pathshorten perleval popup_atcursor popup_beval popup_clear popup_close 
popup_create popup_dialog popup_filter_menu popup_filter_yesno popup_findecho 
popup_findinfo popup_findpreview popup_getoptions popup_getpos popup_hide 
popup_list popup_locate popup_menu popup_move popup_notification popup_setbuf 
popup_setoptions popup_settext popup_show pow prevnonblank printf 
prompt_getprompt prompt_setcallback prompt_setinterrupt prompt_setprompt 
prop_add prop_add_list prop_clear prop_find prop_list prop_remove prop_type_add 
prop_type_change prop_type_delete prop_type_get prop_type_list pum_getpos 
pumvisible py3eval pyeval pyxeval rand range readblob readdir readdirex 
readfile reduce reg_executing
+syn keyword vimFuncName contained mzeval nextnonblank ngettext nr2char or 
pathshorten perleval popup_atcursor popup_beval popup_clear popup_close 
popup_create popup_dialog popup_filter_menu popup_filter_yesno popup_findecho 
popup_findinfo popup_findpreview popup_getoptions popup_getpos popup_hide 
popup_list popup_locate popup_menu popup_move popup_notification popup_setbuf 
popup_setoptions popup_settext popup_show pow preinserted prevnonblank printf 
prompt_getprompt prompt_setcallback prompt_setinterrupt prompt_setprompt 
prop_add prop_add_list prop_clear prop_find prop_list prop_remove prop_type_add 
prop_type_change prop_type_delete prop_type_get prop_type_list pum_getpos 
pumvisible py3eval pyeval pyxeval rand range readblob readdir readdirex 
readfile reduce reg_executing
 syn keyword vimFuncName contained reg_recording reltime reltimefloat 
reltimestr remote_expr remote_foreground remote_peek remote_read remote_send 
remote_startserver remove rename repeat resolve reverse round rubyeval 
screenattr screenchar screenchars screencol screenpos screenrow screenstring 
search searchcount searchdecl searchpair searchpairpos searchpos server2client 
serverlist setbufline setbufvar setcellwidths setcharpos setcharsearch 
setcmdline setcmdpos setcursorcharpos setenv setfperm setline setloclist 
setmatches setpos setqflist setreg settabvar settabwinvar settagstack setwinvar 
sha256 shellescape shiftwidth sign_define sign_getdefined sign_getplaced 
sign_jump sign_place sign_placelist sign_undefine sign_unplace sign_unplacelist 
simplify sin sinh slice
 syn keyword vimFuncName contained sort sound_clear sound_playevent 
sound_playfile sound_stop soundfold spellbadword spellsuggest split sqrt srand 
state str2blob str2float str2list str2nr strcharlen strcharpart strchars 
strdisplaywidth strftime strgetchar stridx string strlen strpart strptime 
strridx strtrans strutf16len strwidth submatch substitute swapfilelist swapinfo 
swapname synID synIDattr synIDtrans synconcealed synstack system systemlist 
tabpagebuflist tabpagenr tabpagewinnr tagfiles taglist tan tanh tempname 
term_dumpdiff term_dumpload term_dumpwrite term_getaltscreen term_getansicolors 
term_getattr term_getcursor term_getjob term_getline term_getscrolled 
term_getsize term_getstatus term_gettitle term_gettty term_list term_scrape 
term_sendkeys term_setansicolors
 syn keyword vimFuncName contained term_setapi term_setkill term_setrestore 
term_setsize term_start term_wait terminalprops test_alloc_fail test_autochdir 
test_feedinput test_garbagecollect_now test_garbagecollect_soon test_getvalue 
test_gui_event test_ignore_error test_mswin_event test_null_blob 
test_null_channel test_null_dict test_null_function test_null_job 
test_null_list test_null_partial test_null_string test_null_tuple 
test_option_not_set test_override test_refcount test_setmouse test_settime 
test_srand_seed test_unknown test_void timer_info timer_pause timer_start 
timer_stop timer_stopall tolower toupper tr trim trunc tuple2list type typename 
undofile undotree uniq uri_decode uri_encode utf16idx values virtcol 
virtcol2col visualmode wildmenumode wildtrigger
diff --git a/src/edit.c b/src/edit.c
index 400d047b8..d564e2c9e 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -693,11 +693,12 @@ edit(
                        && stop_arrow() == OK)
                {
                    ins_compl_delete();
-                   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();
+                       continue;
+                   }
                }
                // Delete preinserted text when typing special chars
                else if (IS_WHITE_NL_OR_NUL(c) && ins_compl_preinsert_effect())
diff --git a/src/evalfunc.c b/src/evalfunc.c
index c775240c3..b5c5bb908 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2655,6 +2655,8 @@ static const funcentry_T global_functions[] =
                        ret_number,         PROP_FUNC(f_popup_show)},
     {"pow",            2, 2, FEARG_1,      arg2_float_or_nr,
                        ret_float,          f_pow},
+    {"preinserted",    0, 0, 0,            NULL,
+                       ret_number_bool,    f_preinserted},
     {"prevnonblank",   1, 1, FEARG_1,      arg1_lnum,
                        ret_number,         f_prevnonblank},
     {"printf",         1, 19, FEARG_2,     arg119_printf,
diff --git a/src/insexpand.c b/src/insexpand.c
index db6efdcf9..bf28bb2c1 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -210,7 +210,7 @@ static int    compl_autocomplete = FALSE;       // whether 
autocompletion is active
 static int       compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS;
 static int       compl_time_slice_expired = FALSE; // time budget exceeded for 
current source
 static int       compl_from_nonkeyword = FALSE;    // completion started from 
non-keyword
-static int       compl_autocomplete_preinsert = FALSE;    // apply preinsert 
highlight
+static int       compl_hi_on_autocompl_longest = FALSE;    // apply 
"PreInsert" highlight
 
 // Halve the current completion timeout, simulating exponential decay.
 #define COMPL_MIN_TIMEOUT_MS   5
@@ -853,6 +853,18 @@ is_nearest_active(void)
        && !(flags & COT_FUZZY);
 }
 
+/*
+ * Returns TRUE if autocomplete is active and the pre-insert effect targets the
+ * longest prefix.
+ */
+    int
+ins_compl_preinsert_longest(void)
+{
+    return compl_autocomplete
+       && (get_cot_flags() & (COT_LONGEST | COT_PREINSERT | COT_FUZZY))
+               == COT_LONGEST;
+}
+
 /*
  * Add a match to the list of matches. The arguments are:
  *     str       - text of the match to add
@@ -1025,7 +1037,8 @@ ins_compl_add(
     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);
 
     return OK;
@@ -1084,17 +1097,14 @@ ins_compl_leader_len(void)
 ins_compl_col_range_attr(linenr_T lnum, int col)
 {
     int            start_col;
+    int            has_preinsert = ins_compl_has_preinsert()
+                       || ins_compl_preinsert_longest();
     int            attr;
-    int            has_preinsert = ins_compl_has_preinsert();
 
     if ((get_cot_flags() & COT_FUZZY)
-           || (!has_preinsert
-               && (attr = syn_name2attr((char_u *)"ComplMatchIns")) == 0)
-           || (!compl_autocomplete && has_preinsert
-               && (attr = syn_name2attr((char_u *)"PreInsert")) == 0)
-           || (compl_autocomplete
-               && (!compl_autocomplete_preinsert
-                   || (attr = syn_name2attr((char_u *)"PreInsert")) == 0)))
+           || (!compl_hi_on_autocompl_longest && ins_compl_preinsert_longest())
+           || (attr = syn_name2attr(has_preinsert
+                   ? (char_u *)"PreInsert" : (char_u *)"ComplMatchIns")) == 0)
        return -1;
 
     start_col = compl_col + (int)ins_compl_leader_len();
@@ -1613,7 +1623,7 @@ ins_compl_build_pum(void)
     int                cur = -1;
     unsigned int cur_cot_flags = get_cot_flags();
     int                compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0
-                               || compl_autocomplete;
+                   || (compl_autocomplete && !ins_compl_has_preinsert());
     int                fuzzy_filter = (cur_cot_flags & COT_FUZZY) != 0;
     compl_T    *match_head = NULL;
     compl_T    *match_tail = NULL;
@@ -2352,6 +2362,8 @@ ins_compl_len(void)
 ins_compl_has_preinsert(void)
 {
     int cur_cot_flags = get_cot_flags();
+    if (compl_autocomplete && p_ic && !p_inf)
+       return FALSE;
     return !compl_autocomplete
        ? (cur_cot_flags & (COT_PREINSERT | COT_FUZZY | COT_MENUONE))
                == (COT_PREINSERT | COT_MENUONE)
@@ -2365,21 +2377,12 @@ ins_compl_has_preinsert(void)
     int
 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.
- */
-    int
-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.
@@ -2425,7 +2428,7 @@ ins_compl_bs(void)
     }
 
     // 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;
 
     ins_compl_new_leader();
@@ -2538,21 +2541,14 @@ ins_compl_new_leader(void)
     if (!compl_interrupted)
        show_pum(save_w_wrow, save_w_leftcol);
 
-    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.length > 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.length > 0 && !ins_compl_preinsert_effect())
+       ins_compl_insert(TRUE, TRUE);
 }
 
 /*
@@ -2602,14 +2598,14 @@ ins_compl_addleader(int c)
        ins_compl_restart();
 
     // When 'always' is set, don't reset compl_leader. While completing,
-    // cursor doesn't point original position, changing compl_leader would
-    // break redo.
+    // cursor doesn't point to the original position, changing compl_leader
+    // would break redo.
     if (!compl_opt_refresh_always)
     {
        VIM_CLEAR_STRING(compl_leader);
        compl_leader.length = (size_t)(curwin->w_cursor.col - compl_col);
        compl_leader.string = vim_strnsave(ml_get_curline() + compl_col,
-                                            compl_leader.length);
+               compl_leader.length);
        if (compl_leader.string == NULL)
        {
            compl_leader.length = 0;
@@ -5104,7 +5100,7 @@ get_next_default_completion(ins_compl_next_state_T *st, 
pos_T *start_pos)
        if (!in_fuzzy_collect)
            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.string) == 0))
            continue;
 
@@ -5666,7 +5662,7 @@ 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);
 
     return match_count;
@@ -5907,6 +5903,22 @@ find_common_prefix(size_t *prefix_len, int curbuf_only)
 
     if (len > (int)ins_compl_leader_len())
     {
+       // Avoid inserting text that duplicates the text already present
+       // after the cursor.
+       if (len == (int)STRLEN(first))
+       {
+           char_u *line = ml_get_curline();
+           char_u *p = line + curwin->w_cursor.col;
+           if (p && !IS_WHITE_OR_NUL(*p))
+           {
+               char_u *end = find_word_end(p);
+               int text_len = end - p;
+               if (text_len > 0
+                       && text_len < (len - (int)ins_compl_leader_len())
+                       && STRNCMP(first + len - text_len, p, text_len) == 0)
+                   len -= text_len;
+           }
+       }
        *prefix_len = (size_t)len;
        return first;
     }
@@ -5917,11 +5929,11 @@ find_common_prefix(size_t *prefix_len, int 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.
+ * When "insert_prefix" is TRUE the longest common prefix is inserted instead
+ * of shown match.
  */
-    int
-ins_compl_insert(int move_cursor, int preinsert_prefix)
+    void
+ins_compl_insert(int move_cursor, int insert_prefix)
 {
     int                compl_len = get_compl_len();
     int                preinsert = ins_compl_has_preinsert();
@@ -5930,14 +5942,17 @@ ins_compl_insert(int move_cursor, int preinsert_prefix)
     size_t     leader_len = ins_compl_leader_len();
     char_u     *has_multiple = vim_strchr(cp_str, '
');
 
-    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.string;
+               cp_str_len = compl_shown_match->cp_str.length;
+           }
        }
     }
     // Since completion sources may provide matches with varying start
@@ -5970,13 +5985,13 @@ ins_compl_insert(int move_cursor, int preinsert_prefix)
        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);
        }
     }
     if (match_at_original_text(compl_shown_match)
-           || (preinsert && !compl_autocomplete))
+           || (preinsert && !insert_prefix))
        compl_used_match = FALSE;
     else
        compl_used_match = TRUE;
@@ -5987,7 +6002,7 @@ ins_compl_insert(int move_cursor, int preinsert_prefix)
        set_vim_var_dict(VV_COMPLETED_ITEM, dict);
     }
 #endif
-    return OK;
+    compl_hi_on_autocompl_longest = insert_prefix && move_cursor;
 }
 
 /*
@@ -6071,7 +6086,7 @@ find_next_completion_match(
     compl_T    *found_compl = NULL;
     unsigned int cur_cot_flags = get_cot_flags();
     int                compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0
-                               || compl_autocomplete;
+                   || (compl_autocomplete && !ins_compl_has_preinsert());
     int                compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
     string_T   *leader;
 
@@ -6201,7 +6216,7 @@ ins_compl_next(
     buf_T   *orig_curbuf = curbuf;
     unsigned int cur_cot_flags = get_cot_flags();
     int            compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0
-                               || compl_autocomplete;
+                   || (compl_autocomplete && !ins_compl_has_preinsert());
     int            compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
     int            compl_preinsert = ins_compl_has_preinsert();
 
@@ -6245,38 +6260,22 @@ ins_compl_next(
        return -1;
     }
 
-    compl_autocomplete_preinsert = FALSE;
-
     // Insert the text of the new completion, or the compl_leader.
-    if (compl_no_insert && !started)
+    if (!started && ins_compl_preinsert_longest())
+       ins_compl_insert(TRUE, TRUE);
+    else if (compl_no_insert && !started && !compl_preinsert)
     {
-       int 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.string + get_compl_len(), 
-1);
+       ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1);
        compl_used_match = FALSE;
     }
     else if (insert_match)
     {
        if (!compl_get_longest || compl_used_match)
        {
-           int 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);
+           int 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
            ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1);
@@ -6417,8 +6416,8 @@ ins_compl_check_keys(int frequency, int in_compl_func)
            check_elapsed_time();
     }
 
-    if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT)
-           && !compl_autocomplete)
+    if (compl_pending && !got_int && !(cot_flags & COT_NOINSERT)
+           && (!compl_autocomplete || ins_compl_has_preinsert()))
     {
        // Insert the first match immediately and advance compl_shown_match,
        // before finding other matches.
@@ -7750,3 +7749,13 @@ cpt_compl_refresh(void)
     compl_matches = ins_compl_make_cyclic();
 #endif
 }
+
+/*
+ * "preinserted()" function
+ */
+    void
+f_preinserted(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+{
+    if (ins_compl_preinsert_effect())
+       rettv->vval.v_number = 1;
+}
diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro
index fcf64378c..c10d2046b 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -48,7 +48,6 @@ colnr_T ins_compl_col(void);
 int ins_compl_len(void);
 int ins_compl_has_preinsert(void);
 int ins_compl_preinsert_effect(void);
-int ins_compl_autocomplete_enabled(void);
 int ins_compl_bs(void);
 void ins_compl_addleader(int c);
 void ins_compl_addfrommatch(void);
@@ -70,10 +69,12 @@ void f_complete_check(typval_T *argvars, typval_T *rettv);
 void f_complete_match(typval_T *argvars, typval_T *rettv);
 void f_complete_info(typval_T *argvars, typval_T *rettv);
 void ins_compl_delete(void);
-int ins_compl_insert(int move_cursor, int preinsert_prefix);
+void ins_compl_insert(int move_cursor, int insert_longest);
 void ins_compl_check_keys(int frequency, int in_compl_func);
 int ins_complete(int c, int enable_pum);
 void ins_compl_enable_autocomplete(void);
 void free_insexpand_stuff(void);
 int ins_compl_has_autocomplete(void);
+int ins_compl_preinsert_longest(void);
+void f_preinserted(typval_T *argvars UNUSED, typval_T *rettv UNUSED);
 /* vim: set ft=c : */
diff --git a/src/testdir/dumps/Test_autocompletedelay_7.dump 
b/src/testdir/dumps/Test_autocompletedelay_7.dump
index 6c43fa46e..08f4059c6 100644
--- a/src/testdir/dumps/Test_autocompletedelay_7.dump
+++ b/src/testdir/dumps/Test_autocompletedelay_7.dump
@@ -1,10 +1,10 @@
 |f+0&#ffffff0|o@1| @71
 |f|o@1|b|a|r| @68
 |f|o@1|b|a|r|b|a|z| @65
-|f>o+0#00e0003&@1| +0#0000000&@71
-|f+0#0000001#e0e0e08|o@1| @11| +0#4040ff13#ffffff0@59
+|f|o@1> @71
+|f+0#0000001#ffd7ff255|o@1| @11| +0#4040ff13#ffffff0@59
 |f+0#0000001#ffd7ff255|o@1|b|a|r| @8| +0#4040ff13#ffffff0@59
 |f+0#0000001#ffd7ff255|o@1|b|a|r|b|a|z| @5| +0#4040ff13#ffffff0@59
 |~| @73
 |~| @73
-|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| 
|m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@33
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| 
|B+0#e000002&|a|c|k| |a|t| |o|r|i|g|i|n|a|l| +0#0000000&@29
diff --git a/src/testdir/test_ins_complete.vim 
b/src/testdir/test_ins_complete.vim
index 6f6232f96..ba449f2db 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -119,10 +119,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
@@ -4120,6 +4119,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 test_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 test_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)
@@ -5583,7 +5721,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', {})
 
@@ -5607,16 +5745,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 test_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')
 
@@ -5648,10 +5788,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)
@@ -5665,6 +5810,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
@@ -5675,6 +5848,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"])
@@ -5711,7 +5894,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>
@@ -5719,7 +5902,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>
diff --git a/src/version.c b/src/version.c
index 4d820a9cb..f5d1e62b9 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1797,
 /**/
     1796,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1v2CVF-00DAzI-MT%40256bit.org.

Raspunde prin e-mail lui