]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1797: completion: autocompletion can be improved v9.1.1797
authorGirish Palya <girishji@gmail.com>
Fri, 26 Sep 2025 17:29:38 +0000 (17:29 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 26 Sep 2025 17:29:38 +0000 (17:29 +0000)
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 <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
13 files changed:
runtime/doc/builtin.txt
runtime/doc/options.txt
runtime/doc/tags
runtime/doc/usr_41.txt
runtime/doc/version9.txt
runtime/syntax/vim.vim
src/edit.c
src/evalfunc.c
src/insexpand.c
src/proto/insexpand.pro
src/testdir/dumps/Test_autocompletedelay_7.dump
src/testdir/test_ins_complete.vim
src/version.c

index 90aefbf336df8438a304751f2026305389ccc535..0a73928e97f3f766b8f4b28bbaf084b93e9fe9a6 100644 (file)
@@ -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: >
index 012027518358ebf0125a37f227112e725f2f8880..ebd7e53bca15905459fcfe7d5e49481e953b5aad 100644 (file)
@@ -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.
index 9dc1f3dc5e4d803d51faea7cf209d30b6acede91..c59f848d563559b458a99482efe3016a2352dec1 100644 (file)
@@ -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*
index 813211e7c889f5fb9517f17231ff8dcb1a3fb926..6ee10f433f2e33f61fc48b25086c8d6e07acb97e 100644 (file)
@@ -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
 
index 1227641a266280fb82379f916830e26680ec3532..5447b28c6a176f2fa53d99f7d689eb7ffd537af2 100644 (file)
@@ -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
index 1d244b66c9e6f1b223af877c13c22077fb156ef9..c618afe53396638846d073d489fe9fb7a5e04728 100644 (file)
@@ -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
index 400d047b8c211f3e3ac6e4189e6400e22a89c37f..d564e2c9e6bb9b2b6e525633f230d3aef3ea321d 100644 (file)
@@ -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())
index c775240c3ba82eeec3ceb66864211798831a1d3d..b5c5bb908781ad5e3b2ad533df5beb17acd6b239 100644 (file)
@@ -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,
index db6efdcf9270494b4e693c19015228adeaaf7b58..bf28bb2c13c9df9b7407a31d9e7f80fd51579891 100644 (file)
@@ -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, '\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.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;
+}
index fcf64378c09d0d0f8b3e15a8cdfe04c15d0e104a..c10d2046b5479f3c7967a5b5b8711130de756514 100644 (file)
@@ -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 : */
index 6c43fa46e922ed23a036e828837c021c4f4975d4..08f4059c68ff7d03fc7519c4031d6e193c5eb8ab 100644 (file)
@@ -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
index 6f6232f963668ad7f45f13c3549ac9d5bb31403e..ba449f2dbc157054225941a3649fb2b1a67c1e57 100644 (file)
@@ -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>
index 4d820a9cb4e6bc40ac01011e5afb0eca99158d8f..f5d1e62b9da3ff680de0709a7de377cb61634038 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1797,
 /**/
     1796,
 /**/