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 <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/edit.c b/src/edit.c
index f4c5edf..72a82f8 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -690,8 +690,11 @@
&& 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 bf8f215..3b9494b 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -1947,6 +1947,28 @@
}
/*
+ * 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.
* Returns the character to be used, NUL if the work is done and another char
@@ -1958,6 +1980,9 @@
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 @@
// 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 @@
{
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 @@
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 @@
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 @@
/*
* 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 @@
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 @@
}
// 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 @@
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 11fefc3..df5bf4d 100644
--- a/src/option.h
+++ b/src/option.h
@@ -531,6 +531,7 @@
#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 8672665..4dda7a0 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -120,7 +120,7 @@
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 24c325f..fe3fead 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -58,9 +58,10 @@
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 7a4ffa8..30de286 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -155,7 +155,7 @@
\ ['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 466e358..e4bc98a 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -3025,4 +3025,121 @@
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 450bcdf..836f0a8 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1056,
+/**/
1055,
/**/
1054,