patch 9.1.1056: Vim doesn't highlight to be inserted text when completing

Commit: 
https://github.com/vim/vim/commit/edd4ac3e895ce16034c7e098f1d68e0155d97886
Author: glepnir <glephun...@gmail.com>
Date:   Wed Jan 29 18:53:51 2025 +0100

    patch 9.1.1056: Vim doesn't highlight to be inserted text when completing
    
    Problem:  Vim doesn't highlight to be inserted text when completing
    Solution: Add support for the "preinsert" 'completeopt' value
              (glepnir)
    
    Support automatically inserting the currently selected candidate word
    that does not belong to the latter part of the leader.
    
    fixes: #3433
    closes: #16403
    
    Signed-off-by: glepnir <glephun...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 880ab0d53..648b18cbe 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 Jan 26
+*options.txt*  For Vim version 9.1.  Last change: 2025 Jan 29
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2168,6 +2168,12 @@ A jump table for the options with a short description 
can be found at |Q_op|.
                    scores when "fuzzy" is enabled. Candidates will appear
                    in their original order.
 
+          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. Does not work when
+                   "fuzzy" is also included.
+
                                        *'completepopup'* *'cpp'*
 'completepopup' 'cpp'  string (default empty)
                        global
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index e2243f604..87c603960 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -41625,7 +41625,9 @@ Changed~
   the "matches" key
 - |v:stacktrace| The stack trace of the exception most recently caught and
   not finished
-- New option value "nosort" for 'completeopt'
+- New option value for 'completeopt':
+       "nosort"        - do not sort completion results
+       "preinsert"     - highlight to be inserted values
 - add |dist#vim9#Launch()| and |dist#vim9#Open()| to the |vim-script-library|
   and decouple it from |netrw|
 - 'termguicolors' is automatically enabled if the terminal supports the RGB
diff --git a/src/edit.c b/src/edit.c
index f4c5edfc4..72a82f8fc 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -690,8 +690,11 @@ edit(
                        && stop_arrow() == OK)
                {
                    ins_compl_delete();
-                   ins_compl_insert(FALSE);
+                   ins_compl_insert(FALSE, FALSE);
                }
+               // Delete preinserted text when typing special chars
+               else if (IS_WHITE_NL_OR_NUL(c) && ins_compl_preinsert_effect())
+                   ins_compl_delete();
            }
        }
 
diff --git a/src/insexpand.c b/src/insexpand.c
index bf8f215e9..3b9494bb2 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -1946,6 +1946,28 @@ ins_compl_len(void)
     return compl_length;
 }
 
+/*
+ * Return TRUE when preinsert is set otherwise FALSE.
+ */
+    static int
+ins_compl_has_preinsert(void)
+{
+    return (get_cot_flags() & (COT_PREINSERT | COT_FUZZY)) == COT_PREINSERT;
+}
+
+/*
+ * Returns TRUE if the pre-insert effect is valid and the cursor is within
+ * the `compl_ins_end_col` range.
+ */
+    int
+ins_compl_preinsert_effect(void)
+{
+    if (!ins_compl_has_preinsert())
+       return FALSE;
+
+    return curwin->w_cursor.col < compl_ins_end_col;
+}
+
 /*
  * Delete one character before the cursor and show the subset of the matches
  * that match the word that is now before the cursor.
@@ -1958,6 +1980,9 @@ ins_compl_bs(void)
     char_u     *line;
     char_u     *p;
 
+    if (ins_compl_preinsert_effect())
+       ins_compl_delete();
+
     line = ml_get_curline();
     p = line + curwin->w_cursor.col;
     MB_PTR_BACK(line, p);
@@ -2054,6 +2079,8 @@ ins_compl_new_leader(void)
     // 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(FALSE, TRUE);
 }
 
 /*
@@ -2079,6 +2106,9 @@ ins_compl_addleader(int c)
 {
     int                cc;
 
+    if (ins_compl_preinsert_effect())
+       ins_compl_delete();
+
     if (stop_arrow() == FAIL)
        return;
     if (has_mbyte && (cc = (*mb_char2len)(c)) > 1)
@@ -3092,7 +3122,8 @@ set_completion(colnr_T startcol, list_T *list)
     compl_col = startcol;
     compl_length = (int)curwin->w_cursor.col - (int)startcol;
     // compl_pattern doesn't need to be set
-    compl_orig_text.string = vim_strnsave(ml_get_curline() + compl_col, 
(size_t)compl_length);
+    compl_orig_text.string = vim_strnsave(ml_get_curline() + compl_col,
+                                                       (size_t)compl_length);
     if (p_ic)
        flags |= CP_ICASE;
     if (compl_orig_text.string == NULL)
@@ -4305,11 +4336,16 @@ ins_compl_update_shown_match(void)
     void
 ins_compl_delete(void)
 {
-    int            col;
-
     // In insert mode: Delete the typed part.
     // In replace mode: Put the old characters back, if any.
-    col = compl_col + (compl_status_adding() ? compl_length : 0);
+    int col = compl_col + (compl_status_adding() ? compl_length : 0);
+    int        has_preinsert = ins_compl_preinsert_effect();
+    if (has_preinsert)
+    {
+       col = compl_col + ins_compl_leader_len() - compl_length;
+       curwin->w_cursor.col = compl_ins_end_col;
+    }
+
     if ((int)curwin->w_cursor.col > col)
     {
        if (stop_arrow() == FAIL)
@@ -4330,17 +4366,26 @@ ins_compl_delete(void)
 /*
  * Insert the new text being completed.
  * "in_compl_func" is TRUE when called from complete_check().
+ * "move_cursor" is used when 'completeopt' includes "preinsert" and when TRUE
+ * cursor needs to move back from the inserted text to the compl_leader.
  */
     void
-ins_compl_insert(int in_compl_func)
+ins_compl_insert(int in_compl_func, int move_cursor)
 {
-    int compl_len = get_compl_len();
+    int            compl_len = get_compl_len();
+    int            preinsert = ins_compl_has_preinsert();
+    char_u  *str = compl_shown_match->cp_str.string;
+    int            leader_len = ins_compl_leader_len();
 
     // Make sure we don't go over the end of the string, this can happen with
     // illegal bytes.
     if (compl_len < (int)compl_shown_match->cp_str.length)
-       ins_compl_insert_bytes(compl_shown_match->cp_str.string + compl_len, 
-1);
-    if (match_at_original_text(compl_shown_match))
+    {
+       ins_compl_insert_bytes(str + compl_len, -1);
+       if (preinsert && move_cursor)
+           curwin->w_cursor.col -= ((size_t)STRLEN(str) - leader_len);
+    }
+    if (match_at_original_text(compl_shown_match) || preinsert)
        compl_used_match = FALSE;
     else
        compl_used_match = TRUE;
@@ -4572,6 +4617,7 @@ ins_compl_next(
     unsigned int cur_cot_flags = get_cot_flags();
     int            compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0;
     int            compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
+    int            compl_preinsert = ins_compl_has_preinsert();
 
     // When user complete function return -1 for findstart which is next
     // time of 'always', compl_shown_match become NULL.
@@ -4614,7 +4660,7 @@ ins_compl_next(
     }
 
     // Insert the text of the new completion, or the compl_leader.
-    if (compl_no_insert && !started)
+    if (compl_no_insert && !started && !compl_preinsert)
     {
        ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1);
        compl_used_match = FALSE;
@@ -4622,7 +4668,7 @@ ins_compl_next(
     else if (insert_match)
     {
        if (!compl_get_longest || compl_used_match)
-           ins_compl_insert(in_compl_func);
+           ins_compl_insert(in_compl_func, TRUE);
        else
            ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1);
     }
diff --git a/src/option.h b/src/option.h
index 11fefc320..df5bf4dc7 100644
--- a/src/option.h
+++ b/src/option.h
@@ -531,6 +531,7 @@ EXTERN unsigned     cot_flags;      // flags from 
'completeopt'
 #define COT_NOSELECT       0x080   // FALSE: select & insert, TRUE: noselect
 #define COT_FUZZY          0x100   // TRUE: fuzzy match enabled
 #define COT_NOSORT         0x200   // TRUE: fuzzy match without qsort score
+#define COT_PREINSERT      0x400   // TRUE: preinsert
 #ifdef BACKSLASH_IN_FILENAME
 EXTERN char_u  *p_csl;         // 'completeslash'
 #endif
diff --git a/src/optionstr.c b/src/optionstr.c
index 8672665bc..4dda7a064 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -120,7 +120,7 @@ static char *(p_fdm_values[]) = {"manual", "expr", 
"marker", "indent", "syntax",
                                NULL};
 static char *(p_fcl_values[]) = {"all", NULL};
 #endif
-static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", 
"popup", "popuphidden", "noinsert", "noselect", "fuzzy", "nosort", NULL};
+static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", 
"popup", "popuphidden", "noinsert", "noselect", "fuzzy", "nosort", "preinsert", 
NULL};
 #ifdef BACKSLASH_IN_FILENAME
 static char *(p_csl_values[]) = {"slash", "backslash", NULL};
 #endif
diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro
index 24c325fe6..fe3fead25 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -58,9 +58,10 @@ void f_complete_add(typval_T *argvars, typval_T *rettv);
 void f_complete_check(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 in_compl_func);
+void ins_compl_insert(int in_compl_func, int move_cursor);
 void ins_compl_check_keys(int frequency, int in_compl_func);
 int ins_complete(int c, int enable_pum);
 int ins_compl_col_range_attr(int col);
 void free_insexpand_stuff(void);
+int ins_compl_preinsert_effect(void);
 /* vim: set ft=c : */
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index 7a4ffa8d6..30de28607 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -155,7 +155,7 @@ let test_values = {
       \                ['xxx']],
       \ 'concealcursor': [['', 'n', 'v', 'i', 'c', 'nvic'], ['xxx']],
       \ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup',
-      \                'popuphidden', 'noinsert', 'noselect', 'fuzzy', 
'menu,longest'],
+      \                'popuphidden', 'noinsert', 'noselect', 'fuzzy', 
"preinsert", 'menu,longest'],
       \                ['xxx', 'menu,,,longest,']],
       \ 'completeitemalign': [['abbr,kind,menu', 'menu,abbr,kind'],
       \                ['', 'xxx', 'abbr', 'abbr,menu', 'abbr,menu,kind,abbr',
diff --git a/src/testdir/test_ins_complete.vim 
b/src/testdir/test_ins_complete.vim
index 466e358cf..e4bc98ac9 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -3025,4 +3025,121 @@ func Test_complete_info_completed()
   set cot&
 endfunc
 
+function Test_completeopt_preinsert()
+  func Omni_test(findstart, base)
+    if a:findstart
+      return col(".")
+    endif
+    return [#{word: "fobar"}, #{word: "foobar"}, #{word: "你的"}, #{word: 
"你好世界"}]
+  endfunc
+  set omnifunc=Omni_test
+  set completeopt=menu,menuone,preinsert
+
+  new
+  call feedkeys("S\<C-X>\<C-O>f", 'tx')
+  call assert_equal("fobar", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("S\<C-X>\<C-O>foo", 'tx')
+  call assert_equal("foobar", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("S\<C-X>\<C-O>foo\<BS>\<BS>\<BS>", 'tx')
+  call assert_equal("", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  " delete a character and input new leader
+  call feedkeys("S\<C-X>\<C-O>foo\<BS>b", 'tx')
+  call assert_equal("fobar", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  " delete preinsert when prepare completion
+  call feedkeys("S\<C-X>\<C-O>f\<Space>", 'tx')
+  call assert_equal("f ", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("S\<C-X>\<C-O>你", 'tx')
+  call assert_equal("你的", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("S\<C-X>\<C-O>你好", 'tx')
+  call assert_equal("你好世界", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("Shello   wo\<Left>\<Left>\<Left>\<C-X>\<C-O>f", 'tx')
+  call assert_equal("hello  fobar wo", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("Shello   wo\<Left>\<Left>\<Left>\<C-X>\<C-O>f\<BS>", 'tx')
+  call assert_equal("hello   wo", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("Shello   wo\<Left>\<Left>\<Left>\<C-X>\<C-O>foo", 'tx')
+  call assert_equal("hello  foobar wo", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  call feedkeys("Shello   wo\<Left>\<Left>\<Left>\<C-X>\<C-O>foo\<BS>b", 'tx')
+  call assert_equal("hello  fobar wo", getline('.'))
+  call feedkeys("\<C-E>\<ESC>", 'tx')
+
+  " confrim
+  call feedkeys("S\<C-X>\<C-O>f\<C-Y>", 'tx')
+  call assert_equal("fobar", getline('.'))
+  call assert_equal(5, col('.'))
+
+  " cancel
+  call feedkeys("S\<C-X>\<C-O>fo\<C-E>", 'tx')
+  call assert_equal("fo", getline('.'))
+  call assert_equal(2, col('.'))
+
+  call feedkeys("S hello hero\<CR>h\<C-X>\<C-N>", 'tx')
+  call assert_equal("hello", getline('.'))
+  call assert_equal(1, col('.'))
+
+  call feedkeys("Sh\<C-X>\<C-N>\<C-Y>", 'tx')
+  call assert_equal("hello", getline('.'))
+  call assert_equal(5, col('.'))
+
+  " delete preinsert part
+  call feedkeys("S\<C-X>\<C-O>fo ", 'tx')
+  call assert_equal("fo ", getline('.'))
+  call assert_equal(3, col('.'))
+
+  " whole line
+  call feedkeys("Shello hero\<CR>\<C-X>\<C-L>", 'tx')
+  call assert_equal("hello hero", getline('.'))
+  call assert_equal(1, col('.'))
+
+  call feedkeys("Shello hero\<CR>he\<C-X>\<C-L>", 'tx')
+  call assert_equal("hello hero", getline('.'))
+  call assert_equal(2, col('.'))
+
+  " can not work with fuzzy
+  set cot+=fuzzy
+  call feedkeys("S\<C-X>\<C-O>", 'tx')
+  call assert_equal("fobar", getline('.'))
+  call assert_equal(5, col('.'))
+
+  " test for fuzzy and noinsert
+  set cot+=noinsert
+  call feedkeys("S\<C-X>\<C-O>fb", 'tx')
+  call assert_equal("fb", getline('.'))
+  call assert_equal(2, col('.'))
+
+  call feedkeys("S\<C-X>\<C-O>你", 'tx')
+  call assert_equal("你", getline('.'))
+  call assert_equal(1, col('.'))
+
+  call feedkeys("S\<C-X>\<C-O>fb\<C-Y>", 'tx')
+  call assert_equal("fobar", getline('.'))
+  call assert_equal(5, col('.'))
+
+  bw!
+  bw!
+  set cot&
+  set omnifunc&
+  delfunc Omni_test
+  autocmd! CompleteChanged
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab nofoldenable
diff --git a/src/version.c b/src/version.c
index 450bcdfaa..836f0a844 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1056,
 /**/
     1055,
 /**/

-- 
-- 
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 vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1tdCM7-00A0dd-PY%40256bit.org.

Raspunde prin e-mail lui