]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1742: complete: preinsert does not work well with preinsert v9.1.1742
authorGirish Palya <girishji@gmail.com>
Mon, 8 Sep 2025 19:23:29 +0000 (15:23 -0400)
committerChristian Brabandt <cb@256bit.org>
Mon, 8 Sep 2025 19:23:29 +0000 (15:23 -0400)
Problem:  complete: preinsert does not work well with preinsert
Solution: Make "preinsert" completeopt value work with autocompletion
          (Girish Palya)

This change extends Insert mode autocompletion so that 'preinsert' also
works when 'autocomplete' is enabled.

Try: `:set ac cot=preinsert`

See `:help 'cot'` for more details.

closes: #18213

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
14 files changed:
runtime/doc/insert.txt
runtime/doc/options.txt
runtime/doc/syntax.txt
runtime/doc/tags
runtime/syntax/vim.vim
src/edit.c
src/highlight.c
src/insexpand.c
src/optiondefs.h
src/proto/insexpand.pro
src/testdir/test_cmdline.vim
src/testdir/test_ins_complete.vim
src/version.c
src/vim.h

index f127d3dbc1c63693aef2b1ccce174484fb6cd4ce..a07a218e8b5d7b329df2b441a7ee6d4bbba39fae 100644 (file)
@@ -1,4 +1,4 @@
-*insert.txt*    For Vim version 9.1.  Last change: 2025 Aug 25
+*insert.txt*    For Vim version 9.1.  Last change: 2025 Sep 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -1150,7 +1150,26 @@ any of them at any time by typing |CTRL-X|, which temporarily suspends
 autocompletion.  To use |i_CTRL-N| or |i_CTRL-X_CTRL-N| specifically, press
 |CTRL-E| first to dismiss the popup menu (see |complete_CTRL-E|).
 
-See also 'autocomplete', 'autocompletetimeout' and 'autocompletedelay'.
+                                               *ins-autocompletion-example*
+Example setup~
+A typical configuration for automatic completion with a popup menu: >
+       set autocomplete
+       set complete=.^5,w^5,b^5,u^5
+       set completeopt=popup
+
+       inoremap <silent><expr> <Tab>   pumvisible() ? "\<C-n>" : "\<Tab>"
+       inoremap <silent><expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
+<
+This enables automatic completion with suggestions from the current buffer,
+other windows, and listed buffers, displayed in a popup menu.  Each source is
+limited to 5 candidates.  <Tab> and <S-Tab> move through the items when the
+menu is visible.  Optionally, add "preinsert" to 'completeopt' to insert the
+longest common prefix automatically.  You can also add other completion
+sources to 'complete' as needed.
+
+See also 'autocomplete', 'autocompletedelay' and 'autocompletetimeout'.
+
+For command-line autocompletion, see |cmdline-autocompletion|.
 
 
 FUNCTIONS FOR FINDING COMPLETIONS                      *complete-functions*
index 20fe85b84db94698d54ccf3c7e3c4b3337fa9369..51c3c64e5967c2a382bb14cb7ede4a57e4d0a768 100644 (file)
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2025 Sep 02
+*options.txt*  For Vim version 9.1.  Last change: 2025 Sep 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2124,10 +2124,11 @@ A jump table for the options with a short description can be found at |Q_op|.
                                                *'complete'* *'cpt'* *E535*
 'complete' 'cpt'       string  (default: ".,w,b,u,t,i")
                        local to buffer
-       This option specifies how keyword completion |ins-completion| works
-       when CTRL-P or CTRL-N are used.  It is also used for whole-line
-       completion |i_CTRL-X_CTRL-L|.  It indicates the type of completion
-       and the places to scan.  It is a comma-separated list of flags:
+       This option controls how completion |ins-completion| behaves when
+       using CTRL-P, CTRL-N, or |ins-autocompletion|.  It is also used for
+       whole-line completion |i_CTRL-X_CTRL-L|.  It indicates the type of
+       completion and the places to scan.  It is a comma-separated list of
+       flags:
        .       scan the current buffer ('wrapscan' is ignored)
        w       scan buffers from other windows
        b       scan other loaded buffers that are in the buffer list
@@ -2179,9 +2180,11 @@ A jump table for the options with a short description can be found at |Q_op|.
           5. tags
           6. included files
 
-       As you can see, CTRL-N and CTRL-P can be used to do any 'iskeyword'-
-       based expansion (e.g., dictionary |i_CTRL-X_CTRL-K|, included patterns
-       |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions).
+       CTRL-N, CTRL-P, and |ins-autocompletion| can be used for any
+       'iskeyword'-based completion (dictionary |i_CTRL-X_CTRL-K|, included
+       patterns |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]|, and normal
+       expansions).  With the "F" and "o" flags in 'complete', non-keywords
+       can also be completed.
 
        An optional match limit can be specified for a completion source by
        appending a caret ("^") followed by a {count} to the source flag.
@@ -2298,17 +2301,22 @@ 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
-                   Preinsert the portion of the first candidate word that is
-                   not part of the current completion leader and using the
-                   |hl-ComplMatchIns| highlight group.  In order for it to
-                   work, "fuzzy" must not be set and "menuone" must be set.
+                   When autocompletion is not enabled, inserts the part of the
+                   first candidate word beyond the current completion leader,
+                   highlighted with |hl-ComplMatchIns|.  The cursor does not
+                   move.  Requires 'fuzzy' unset and 'menuone' in 'completeopt'.
+
+                   When 'autocomplete' is enabled, inserts the longest common
+                   prefix of matches (from all shown items or buffer-specific
+                   matches), highlighted with |hl-PreInsert|.  This occurs only
+                   when no menu item is selected.  Press CTRL-Y to accept.
 
           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" and "preview" have an effect when
-       'autocomplete' is enabled.
+       Only "fuzzy", "popup", "popuphidden", "preinsert" and "preview" have
+       an effect when 'autocomplete' is enabled.
 
        This option does not apply to |cmdline-completion|. See 'wildoptions'
        for that.
@@ -4654,7 +4662,7 @@ A jump table for the options with a short description can be found at |Q_op|.
                                     #:TabLineSel,_:TabLineFill,!:CursorColumn,
                                     .:CursorLine,o:ColorColumn,q:QuickFixLine,
                                     z:StatusLineTerm,Z:StatusLineTermNC,
-                                    g:MsgArea,h:ComplMatchIns")
+                                    g:MsgArea,h:ComplMatchIns,I:PreInsert")
                        global
        This option can be used to set highlighting mode for various
        occasions.  It is a comma-separated list of character pairs.  The
@@ -4717,6 +4725,7 @@ A jump table for the options with a short description can be found at |Q_op|.
        |hl-PmenuThumb|  X  popup menu scrollbar thumb
        |hl-PmenuMatch|  k  popup menu matched text
        |hl-PmenuMatchSel| <  popup menu matched text in selected line
+       |hl-PreInsert|   I  text inserted when "preinsert" and 'autocomplete'
 
        The display modes are:
                r       reverse         (termcap entry "mr" and "me")
index 2aecb65a6b666760b67ad2281b3666da6de3f363..949bf1759fa7cc480ac06d8b841d35af694f51f9 100644 (file)
@@ -1,4 +1,4 @@
-*syntax.txt*   For Vim version 9.1.  Last change: 2025 Aug 18
+*syntax.txt*   For Vim version 9.1.  Last change: 2025 Sep 08
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -5998,6 +5998,8 @@ PmenuMatchSel     Popup menu: Matched text in selected item. Applied in
                combination with |hl-PmenuSel|.
                                                        *hl-ComplMatchIns*
 ComplMatchIns  Matched text of the currently inserted completion.
+                                                       *hl-PreInsert*
+PreInsert      Text inserted during autocompletion when "preinsert".
                                                        *hl-PopupSelected*
 PopupSelected  Popup window created with |popup_menu()|.  Linked to
                |hl-PmenuSel| by default.
index 7f73f05580e6c8337037cfddabbcffeaf857cfc0..489cb3989304e53fa9f0342a38177b072d46e469 100644 (file)
@@ -8366,6 +8366,7 @@ hl-PmenuSel       syntax.txt      /*hl-PmenuSel*
 hl-PmenuThumb  syntax.txt      /*hl-PmenuThumb*
 hl-PopupNotification   syntax.txt      /*hl-PopupNotification*
 hl-PopupSelected       syntax.txt      /*hl-PopupSelected*
+hl-PreInsert   syntax.txt      /*hl-PreInsert*
 hl-Question    syntax.txt      /*hl-Question*
 hl-QuickFixLine        syntax.txt      /*hl-QuickFixLine*
 hl-Scrollbar   syntax.txt      /*hl-Scrollbar*
@@ -8627,6 +8628,7 @@ inputrestore()    builtin.txt     /*inputrestore()*
 inputsave()    builtin.txt     /*inputsave()*
 inputsecret()  builtin.txt     /*inputsecret()*
 ins-autocompletion     insert.txt      /*ins-autocompletion*
+ins-autocompletion-example     insert.txt      /*ins-autocompletion-example*
 ins-completion insert.txt      /*ins-completion*
 ins-completion-menu    insert.txt      /*ins-completion-menu*
 ins-expandtab  insert.txt      /*ins-expandtab*
index 3b1afa8c1dd55ec0343283bc3ccbd66d459b944a..b8709d4fefc8370cf37b6bac99d8643db44846b9 100644 (file)
@@ -2,7 +2,7 @@
 " Language:       Vim script
 " Maintainer:     Hirohito Higashi <h.east.727 ATMARK gmail.com>
 "         Doug Kearns <dougkearns@gmail.com>
-" Last Change:    2025 Aug 29
+" Last Change:    2025 Sep 08
 " Former Maintainer: Charles E. Campbell
 
 " DO NOT CHANGE DIRECTLY.
@@ -145,8 +145,8 @@ syn keyword vimGroup contained Added Bold BoldItalic Boolean Changed Character C
 
 " Default highlighting groups {{{2
 " GEN_SYN_VIM: vimHLGroup, START_STR='syn keyword vimHLGroup contained', END_STR=''
-syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit VisualNOS DiffText DiffTextAdd PmenuSbar TabLineSel TabLineFill TabPanel TabPanelSel TabPanelFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel PopupSelected MessageWindow PopupNotification Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC ToolbarLine ToolbarButton Menu Tooltip Scrollbar CursorIM
-syn keyword vimHLGroup contained ComplMatchIns LineNrAbove LineNrBelow MsgArea Terminal User1 User2 User3 User4 User5 User6 User7 User8 User9
+syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit VisualNOS DiffText DiffTextAdd PmenuSbar TabLineSel TabLineFill TabPanel TabPanelSel TabPanelFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel PopupSelected MessageWindow PopupNotification PreInsert Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC ToolbarLine ToolbarButton Menu Tooltip Scrollbar
+syn keyword vimHLGroup contained CursorIM ComplMatchIns LineNrAbove LineNrBelow MsgArea Terminal User1 User2 User3 User4 User5 User6 User7 User8 User9
 syn match vimHLGroup contained "\<Conceal\>"
 syn case match
 
index c61a9250e9aa6313535d3d510775716ff4722b2f..41e1c00703da2dfea56b060210b5409d242c5380 100644 (file)
@@ -693,7 +693,11 @@ edit(
                        && stop_arrow() == OK)
                {
                    ins_compl_delete();
-                   ins_compl_insert(FALSE);
+                   if (ins_compl_has_preinsert()
+                           && ins_compl_has_autocomplete())
+                       (void)ins_compl_insert(FALSE, TRUE);
+                   else
+                       (void)ins_compl_insert(FALSE, FALSE);
                }
                // Delete preinserted text when typing special chars
                else if (IS_WHITE_NL_OR_NUL(c) && ins_compl_preinsert_effect())
index ec3d11568c28cb70c0ae9582d79567d91a715ea9..cee780450e614549c5ef74458fb7e1ba1bcf924b 100644 (file)
@@ -269,6 +269,7 @@ static char *(highlight_init_both[]) = {
     "default link PopupSelected PmenuSel",
     "default link MessageWindow WarningMsg",
     "default link PopupNotification WarningMsg",
+    "default link PreInsert Added",
     CENT("Normal cterm=NONE", "Normal gui=NONE"),
     NULL
 };
index 54d42aaf85989aac61c9aa819c97bf5c8654d8a7..71b09313ed08723e2d1b791fb61765a5162d85d7 100644 (file)
@@ -154,8 +154,9 @@ static string_T       compl_leader = {NULL, 0};
 static int       compl_get_longest = FALSE;    // put longest common string
                                                // in compl_leader
 
-// Selected one of the matches.  When FALSE the match was edited or using the
-// longest common string.
+// This flag indicates that one of the items in the match list is currently
+// selected.  FALSE when no match is selected or the match was edited or using
+// the longest common string.
 static int       compl_used_match;
 
 // didn't finish finding completions.
@@ -210,6 +211,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
 
 // Halve the current completion timeout, simulating exponential decay.
 #define COMPL_MIN_TIMEOUT_MS   5
@@ -252,6 +254,7 @@ typedef struct cpt_source_T
 #ifdef ELAPSED_FUNC
     elapsed_T  compl_start_tv;     // Timestamp when match collection starts
 #endif
+    char_u  cs_flag;       // flag indicating the type of source
 } cpt_source_T;
 
 static cpt_source_T *cpt_sources_array; // Pointer to the array of completion sources
@@ -1067,7 +1070,11 @@ ins_compl_col_range_attr(linenr_T lnum, int col)
     int            attr;
 
     if ((get_cot_flags() & COT_FUZZY)
-           || (attr = syn_name2attr((char_u *)"ComplMatchIns")) == 0)
+           || (!compl_autocomplete
+               && (attr = syn_name2attr((char_u *)"ComplMatchIns")) == 0)
+           || (compl_autocomplete
+               && (!compl_autocomplete_preinsert
+                   || (attr = syn_name2attr((char_u *)"PreInsert")) == 0)))
        return -1;
 
     start_col = compl_col + (int)ins_compl_leader_len();
@@ -1624,13 +1631,13 @@ ins_compl_build_pum(void)
     {
        compl->cp_in_match_array = FALSE;
 
+       leader = get_leader_for_startcol(compl, TRUE);
+
        // Apply 'smartcase' behavior during normal mode
-       if (ctrl_x_mode_normal() && !p_inf && compl_leader.string
-               && !ignorecase(compl_leader.string) && !fuzzy_filter)
+       if (ctrl_x_mode_normal() && !p_inf && leader->string
+               && !ignorecase(leader->string) && !fuzzy_filter)
            compl->cp_flags &= ~CP_ICASE;
 
-       leader = get_leader_for_startcol(compl, TRUE);
-
        if (!match_at_original_text(compl)
                && (leader->string == NULL
                    || ins_compl_equal(compl, leader->string,
@@ -2339,12 +2346,14 @@ ins_compl_len(void)
  * Return TRUE when the 'completeopt' "preinsert" flag is in effect,
  * otherwise return FALSE.
  */
-    static int
+    int
 ins_compl_has_preinsert(void)
 {
     int cur_cot_flags = get_cot_flags();
-    return (cur_cot_flags & (COT_PREINSERT | COT_FUZZY | COT_MENUONE))
-       == (COT_PREINSERT | COT_MENUONE) && !compl_autocomplete;
+    return !compl_autocomplete
+       ? (cur_cot_flags & (COT_PREINSERT | COT_FUZZY | COT_MENUONE))
+               == (COT_PREINSERT | COT_MENUONE)
+       : (cur_cot_flags & (COT_PREINSERT | COT_FUZZY)) == COT_PREINSERT;
 }
 
 /*
@@ -2360,6 +2369,15 @@ ins_compl_preinsert_effect(void)
     return curwin->w_cursor.col < compl_ins_end_col;
 }
 
+/*
+ * Returns TRUE if autocompletion is active.
+ */
+    int
+ins_compl_has_autocomplete(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.
@@ -2505,11 +2523,23 @@ 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)
-       ins_compl_insert(TRUE);
+    {
+       if (compl_started && compl_autocomplete
+               && !ins_compl_preinsert_effect())
+       {
+           if (ins_compl_insert(TRUE, TRUE) != OK)
+               (void)ins_compl_insert(FALSE, FALSE);
+           else
+               compl_autocomplete_preinsert = TRUE;
+       }
+       else
+           (void)ins_compl_insert(TRUE, FALSE);
+    }
 }
 
 /*
@@ -2956,6 +2986,7 @@ ins_compl_stop(int c, int prev_mode, int retval)
     compl_autocomplete = FALSE;
     compl_from_nonkeyword = FALSE;
     compl_best_matches = 0;
+    compl_ins_end_col = 0;
 
     if (c == Ctrl_C && cmdwin_type != 0)
        // Avoid the popup menu remains displayed when leaving the
@@ -5057,9 +5088,10 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
            continue;
 
        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 || (ins_compl_has_preinsert() && STRCMP(ptr, compl_pattern.string) == 0))
+           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()
+                   && STRCMP(ptr, compl_pattern.string) == 0))
            continue;
 
        if (is_nearest_active() && in_curbuf)
@@ -5677,8 +5709,8 @@ ins_compl_delete(void)
     int col = compl_col + (compl_status_adding() ? compl_length : 0);
     string_T   remaining = {NULL, 0};
     int            orig_col;
-    int        has_preinsert = ins_compl_preinsert_effect();
-    if (has_preinsert)
+
+    if (ins_compl_preinsert_effect())
     {
        col += (int)ins_compl_leader_len();
        curwin->w_cursor.col = compl_ins_end_col;
@@ -5770,13 +5802,111 @@ ins_compl_expand_multiple(char_u *str)
     compl_ins_end_col = curwin->w_cursor.col;
 }
 
+/*
+ * Find the longest common prefix among the current completion matches.
+ * Returns a pointer to the first match string, with *prefix_len set to
+ * the length of the common prefix.
+ * If "curbuf_only" is TRUE, restrict matches to the current buffer
+ * ('.' source in 'complete').
+ */
+    static char_u *
+find_common_prefix(size_t *prefix_len, int curbuf_only)
+{
+    compl_T     *compl;
+    int                *match_count;
+    char_u     *first = NULL;
+    int                len = -1;
+    int                is_cpt_completion = (cpt_sources_array != NULL);
+
+    if (!is_cpt_completion)
+       return NULL;
+
+    match_count = ALLOC_CLEAR_MULT(int, cpt_sources_count);
+    if (match_count == NULL)
+       return NULL;
+
+    (void)get_leader_for_startcol(NULL, TRUE); // Clear the cache
+
+    compl = compl_first_match;
+    do
+    {
+       string_T *leader = get_leader_for_startcol(compl, TRUE);
+
+       // Apply 'smartcase' behavior during normal mode
+       if (ctrl_x_mode_normal() && !p_inf && leader->string
+               && !ignorecase(leader->string))
+           compl->cp_flags &= ~CP_ICASE;
+
+       if (!match_at_original_text(compl)
+               && (leader->string == NULL
+                   || ins_compl_equal(compl, leader->string,
+                       (int)leader->length)))
+       {
+           // Limit number of items from each source if max_items is set.
+           int match_limit_exceeded = FALSE;
+           int cur_source = compl->cp_cpt_source_idx;
+
+           if (cur_source != -1)
+           {
+               match_count[cur_source]++;
+               int max_matches = cpt_sources_array[cur_source].cs_max_matches;
+               if (max_matches > 0 && match_count[cur_source] > max_matches)
+                   match_limit_exceeded = TRUE;
+           }
+
+           if (!match_limit_exceeded && (!curbuf_only
+                       || cpt_sources_array[cur_source].cs_flag == '.'))
+           {
+               if (first == NULL)
+               {
+                   first = compl->cp_str.string;
+                   len = (int)STRLEN(first);
+               }
+               else
+               {
+                   int j = 0;  // count in bytes
+                   char_u *s1 = first;
+                   char_u *s2 = compl->cp_str.string;
+
+                   while (j < len && *s1 != NUL && *s2 != NUL)
+                   {
+                       if (MB_BYTE2LEN(*s1) != MB_BYTE2LEN(*s2)
+                               || memcmp(s1, s2, MB_BYTE2LEN(*s1)) != 0)
+                           break;
+
+                       j += MB_BYTE2LEN(*s1);
+                       MB_PTR_ADV(s1);
+                       MB_PTR_ADV(s2);
+                   }
+                   len = j;
+
+                   if (len == 0)
+                       break;
+               }
+           }
+       }
+       compl = compl->cp_next;
+    } while (compl != NULL && !is_first_match(compl));
+
+    vim_free(match_count);
+
+    if (len > get_compl_len())
+    {
+       *prefix_len = (size_t)len;
+       return first;
+    }
+    return NULL;
+}
+
 /*
  * 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.
  */
-    void
-ins_compl_insert(int move_cursor)
+    int
+ins_compl_insert(int move_cursor, int preinsert_prefix)
 {
     int                compl_len = get_compl_len();
     int                preinsert = ins_compl_has_preinsert();
@@ -5785,10 +5915,20 @@ ins_compl_insert(int move_cursor)
     size_t     leader_len = ins_compl_leader_len();
     char_u     *has_multiple = vim_strchr(cp_str, '\n');
 
+    if (preinsert_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;
+       }
+    }
     // Since completion sources may provide matches with varying start
     // positions, insert only the portion of the match that corresponds to the
     // intended replacement range.
-    if (cpt_sources_array != NULL)
+    else if (cpt_sources_array != NULL)
     {
        int     cpt_idx = compl_shown_match->cp_cpt_source_idx;
        if (cpt_idx >= 0 && compl_col >= 0)
@@ -5814,7 +5954,8 @@ ins_compl_insert(int move_cursor)
            ins_compl_expand_multiple(cp_str + compl_len);
        else
        {
-           ins_compl_insert_bytes(cp_str + compl_len, -1);
+           ins_compl_insert_bytes(cp_str + compl_len,
+                   preinsert_prefix ? (int)cp_str_len - compl_len : -1);
            if (preinsert && move_cursor)
                curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len);
        }
@@ -5830,6 +5971,7 @@ ins_compl_insert(int move_cursor)
        set_vim_var_dict(VV_COMPLETED_ITEM, dict);
     }
 #endif
+    return OK;
 }
 
 /*
@@ -6084,16 +6226,39 @@ 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 && !compl_preinsert)
+    if (compl_no_insert && !started)
     {
-       ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1);
+       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);
        compl_used_match = FALSE;
     }
     else if (insert_match)
     {
        if (!compl_get_longest || compl_used_match)
-           ins_compl_insert(TRUE);
+       {
+           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);
+       }
        else
            ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1);
     }
@@ -7360,6 +7525,7 @@ setup_cpt_sources(void)
            p++;
        if (*p) // If not end of string, count this segment
        {
+           cpt_sources_array[idx].cs_flag = *p;
            slen = copy_option_part(&p, buf, LSIZE, ","); // Advance p
            if (slen > 0)
            {
index 2de320b9c72abe91e2ca7c2664df33f3cd94e5f8..ea49e80b2665559f002cb0b279bf40a2c506db2d 100644 (file)
@@ -309,7 +309,7 @@ struct vimoption
 # define ISP_LATIN1 (char_u *)"@,161-255"
 #endif
 
-# define HIGHLIGHT_INIT "8:SpecialKey,~:EndOfBuffer,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,E:DiffTextAdd,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm,Z:StatusLineTermNC,g:MsgArea,h:ComplMatchIns,%:TabPanel,^:TabPanelSel,&:TabPanelFill"
+# define HIGHLIGHT_INIT "8:SpecialKey,~:EndOfBuffer,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,E:DiffTextAdd,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm,Z:StatusLineTermNC,g:MsgArea,h:ComplMatchIns,%:TabPanel,^:TabPanelSel,&:TabPanelFill,I:PreInsert"
 
 // Default python version for pyx* commands
 #if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
index 6d3f2c6376f8c4681fefae91ec424489f1aba459..e6925c5ab21b2fb8cd96ea33eba79c53c3df4671 100644 (file)
@@ -69,9 +69,11 @@ 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);
-void ins_compl_insert(int move_cursor);
+int ins_compl_insert(int move_cursor, int insert_longest_prefix);
 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_has_preinsert(void);
 /* vim: set ft=c : */
index fcf8fea4c6574f22d56ede6a06a624b999364c55..8e842d128fc195179806f314aff1a20f972af159 100644 (file)
@@ -489,6 +489,7 @@ func Test_highlight_completion()
   call assert_equal("\"hi link Aardig NONE", getreg(':'))
 
   " A cleared group does not show up in completions.
+  hi clear Added
   hi Anders ctermfg=green
   call assert_equal(['Aardig', 'Anders'], getcompletion('A', 'highlight'))
   hi clear Aardig
index f35bb18e04fe058c58e78f18a667951ef679f67e..41b223da1cdf11f5468edec0f9c4428ec312e61a 100644 (file)
@@ -5359,7 +5359,7 @@ func Test_autocomplete_timer()
   call assert_equal(-1, b:selected)
 
   " Test 7: Following 'cot' option values have no effect
-  set completeopt=menu,menuone,noselect,noinsert,longest,preinsert
+  set completeopt=menu,menuone,noselect,noinsert,longest
   set complete=.,Ffunction('TestComplete'\\,\ [0\\,\ 0\\,\ 0])
   let g:CallCount = 0
   call feedkeys("Sab\<c-n>\<F2>\<F3>\<Esc>0", 'tx!')
@@ -5578,4 +5578,109 @@ func Test_autocompletedelay()
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_autocomplete_completeopt_preinsert()
+  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>
+  set completeopt=preinsert autocomplete
+  call setline(1, ["foobar", "foozbar"])
+  call feedkeys("Go\<ESC>", 'tx')
+
+  func DoTest(typed, line, col)
+    call feedkeys($"S{a:typed}\<F5>\<ESC>", 'tx')
+    call assert_equal(a:line, g:line)
+    call assert_equal(a:col, g:col)
+  endfunc
+
+  call DoTest("f", 'foo', 2)
+  call DoTest("fo", 'foo', 3)
+  call DoTest("foo", 'foo', 4)
+  call DoTest("foob", 'foobar', 5)
+  call DoTest("foob\<BS>", 'foo', 4)
+  call DoTest("foob\<BS>\<BS>", 'foo', 3)
+  call DoTest("foo\<BS>", 'foo', 3)
+  call DoTest("foo\<BS>\<BS>", 'foo', 2)
+  call DoTest("foo\<BS>\<BS>\<BS>", '', 1)
+
+  call DoTest("foo \<BS>", 'foo', 4)
+  call DoTest("foo \<BS>\<BS>", 'foo', 3)
+
+  call DoTest("f\<C-N>", 'foozbar', 8)
+  call DoTest("f\<C-N>\<C-N>", 'foobar', 7)
+  call DoTest("f\<C-N>\<C-N>\<C-N>", 'foo', 2)
+  call DoTest("f\<C-N>\<C-N>\<C-N>\<C-N>", 'foozbar', 8)
+  call DoTest("f\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'foo', 2)
+  call DoTest("f\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>\<C-P>", 'foobar', 7)
+  call DoTest("f\<C-P>", 'foobar', 7)
+  call DoTest("fo\<BS>\<C-P>", 'foobar', 7)
+
+  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>\<BS>", 'foozar', 3)
+
+  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)
+  call DoTest("zar\<C-O>0f\<C-N>\<C-N>\<C-N>\<C-N>", 'foozbarzar', 8)
+  call DoTest("zar\<C-O>0f\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'foozar', 2)
+  call DoTest("zar\<C-O>0f\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>\<C-P>", 'foobarzar', 7)
+  call DoTest("zar\<C-O>0f\<C-P>", 'foobarzar', 7)
+
+  " Should not work with fuzzy
+  set cot+=fuzzy
+  call DoTest("f", 'f', 2)
+
+  %delete _
+  let &l:undolevels = &l:undolevels
+  normal! ifoo
+  let &l:undolevels = &l:undolevels
+  normal! ofoobar
+  let &l:undolevels = &l:undolevels
+  normal! ofoobaz
+  let &l:undolevels = &l:undolevels
+
+  func CheckUndo()
+    let g:errmsg = ''
+    call assert_equal(['foo', 'foobar', 'foobaz'], getline(1, '$'))
+    undo
+    call assert_equal(['foo', 'foobar'], getline(1, '$'))
+    undo
+    call assert_equal(['foo'], getline(1, '$'))
+    undo
+    call assert_equal([''], getline(1, '$'))
+    later 3
+    call assert_equal(['foo', 'foobar', 'foobaz'], 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& autocomplete&
+  delfunc GetLine
+  delfunc DoTest
+  call test_override("char_avail", 0)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab nofoldenable
index adf9f206113aa8202b4079abd810b8a2bc100c52..d6431471efd65f5069c63c3c4224976169b6f8a2 100644 (file)
@@ -724,6 +724,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1742,
 /**/
     1741,
 /**/
index 609c05730e9ca611564b15ac90e3a0154ba38ef3..dd4e782b328a16af797915df45cc4777d6e8214e 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -1587,6 +1587,7 @@ typedef enum
     , HLF_TPL      // tabpanel
     , HLF_TPLS     // tabpanel selected
     , HLF_TPLF     // tabpanel filler
+    , HLF_PRI      // "preinsert" in 'completeopt'
     , HLF_COUNT            // MUST be the last one
 } hlf_T;
 
@@ -1599,7 +1600,7 @@ typedef enum
                  '+', '=', 'k', '<','[', ']', '{', '}', 'x', 'X', \
                  '*', '#', '_', '!', '.', 'o', 'q', \
                  'z', 'Z', 'g', \
-                 '%', '^', '&' }
+                 '%', '^', '&', 'I'}
 
 /*
  * Values for behaviour in spell_move_to