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>
This commit is contained in:
glepnir
2025-01-29 18:53:51 +01:00
committed by Christian Brabandt
parent ec961b05dc
commit edd4ac3e89
10 changed files with 194 additions and 16 deletions

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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 : */

View File

@ -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',

View File

@ -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

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1056,
/**/
1055,
/**/