Skip to content

Commit edd4ac3

Browse files
glepnirchrisbra
authored andcommitted
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 <[email protected]> Signed-off-by: Christian Brabandt <[email protected]>
1 parent ec961b0 commit edd4ac3

File tree

10 files changed

+194
-16
lines changed

10 files changed

+194
-16
lines changed

runtime/doc/options.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*options.txt* For Vim version 9.1. Last change: 2025 Jan 26
1+
*options.txt* For Vim version 9.1. Last change: 2025 Jan 29
22

33

44
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|.
21682168
scores when "fuzzy" is enabled. Candidates will appear
21692169
in their original order.
21702170

2171+
preinsert
2172+
Preinsert the portion of the first candidate word that is
2173+
not part of the current completion leader and using the
2174+
|hl-ComplMatchIns| highlight group. Does not work when
2175+
"fuzzy" is also included.
2176+
21712177
*'completepopup'* *'cpp'*
21722178
'completepopup' 'cpp' string (default empty)
21732179
global

runtime/doc/version9.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41625,7 +41625,9 @@ Changed~
4162541625
the "matches" key
4162641626
- |v:stacktrace| The stack trace of the exception most recently caught and
4162741627
not finished
41628-
- New option value "nosort" for 'completeopt'
41628+
- New option value for 'completeopt':
41629+
"nosort" - do not sort completion results
41630+
"preinsert" - highlight to be inserted values
4162941631
- add |dist#vim9#Launch()| and |dist#vim9#Open()| to the |vim-script-library|
4163041632
and decouple it from |netrw|
4163141633
- 'termguicolors' is automatically enabled if the terminal supports the RGB

src/edit.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,11 @@ edit(
690690
&& stop_arrow() == OK)
691691
{
692692
ins_compl_delete();
693-
ins_compl_insert(FALSE);
693+
ins_compl_insert(FALSE, FALSE);
694694
}
695+
// Delete preinserted text when typing special chars
696+
else if (IS_WHITE_NL_OR_NUL(c) && ins_compl_preinsert_effect())
697+
ins_compl_delete();
695698
}
696699
}
697700

src/insexpand.c

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,6 +1946,28 @@ ins_compl_len(void)
19461946
return compl_length;
19471947
}
19481948

1949+
/*
1950+
* Return TRUE when preinsert is set otherwise FALSE.
1951+
*/
1952+
static int
1953+
ins_compl_has_preinsert(void)
1954+
{
1955+
return (get_cot_flags() & (COT_PREINSERT | COT_FUZZY)) == COT_PREINSERT;
1956+
}
1957+
1958+
/*
1959+
* Returns TRUE if the pre-insert effect is valid and the cursor is within
1960+
* the `compl_ins_end_col` range.
1961+
*/
1962+
int
1963+
ins_compl_preinsert_effect(void)
1964+
{
1965+
if (!ins_compl_has_preinsert())
1966+
return FALSE;
1967+
1968+
return curwin->w_cursor.col < compl_ins_end_col;
1969+
}
1970+
19491971
/*
19501972
* Delete one character before the cursor and show the subset of the matches
19511973
* that match the word that is now before the cursor.
@@ -1958,6 +1980,9 @@ ins_compl_bs(void)
19581980
char_u *line;
19591981
char_u *p;
19601982

1983+
if (ins_compl_preinsert_effect())
1984+
ins_compl_delete();
1985+
19611986
line = ml_get_curline();
19621987
p = line + curwin->w_cursor.col;
19631988
MB_PTR_BACK(line, p);
@@ -2054,6 +2079,8 @@ ins_compl_new_leader(void)
20542079
// Don't let Enter select the original text when there is no popup menu.
20552080
if (compl_match_array == NULL)
20562081
compl_enter_selects = FALSE;
2082+
else if (ins_compl_has_preinsert() && compl_leader.length > 0)
2083+
ins_compl_insert(FALSE, TRUE);
20572084
}
20582085

20592086
/*
@@ -2079,6 +2106,9 @@ ins_compl_addleader(int c)
20792106
{
20802107
int cc;
20812108

2109+
if (ins_compl_preinsert_effect())
2110+
ins_compl_delete();
2111+
20822112
if (stop_arrow() == FAIL)
20832113
return;
20842114
if (has_mbyte && (cc = (*mb_char2len)(c)) > 1)
@@ -3092,7 +3122,8 @@ set_completion(colnr_T startcol, list_T *list)
30923122
compl_col = startcol;
30933123
compl_length = (int)curwin->w_cursor.col - (int)startcol;
30943124
// compl_pattern doesn't need to be set
3095-
compl_orig_text.string = vim_strnsave(ml_get_curline() + compl_col, (size_t)compl_length);
3125+
compl_orig_text.string = vim_strnsave(ml_get_curline() + compl_col,
3126+
(size_t)compl_length);
30963127
if (p_ic)
30973128
flags |= CP_ICASE;
30983129
if (compl_orig_text.string == NULL)
@@ -4305,11 +4336,16 @@ ins_compl_update_shown_match(void)
43054336
void
43064337
ins_compl_delete(void)
43074338
{
4308-
int col;
4309-
43104339
// In insert mode: Delete the typed part.
43114340
// In replace mode: Put the old characters back, if any.
4312-
col = compl_col + (compl_status_adding() ? compl_length : 0);
4341+
int col = compl_col + (compl_status_adding() ? compl_length : 0);
4342+
int has_preinsert = ins_compl_preinsert_effect();
4343+
if (has_preinsert)
4344+
{
4345+
col = compl_col + ins_compl_leader_len() - compl_length;
4346+
curwin->w_cursor.col = compl_ins_end_col;
4347+
}
4348+
43134349
if ((int)curwin->w_cursor.col > col)
43144350
{
43154351
if (stop_arrow() == FAIL)
@@ -4330,17 +4366,26 @@ ins_compl_delete(void)
43304366
/*
43314367
* Insert the new text being completed.
43324368
* "in_compl_func" is TRUE when called from complete_check().
4369+
* "move_cursor" is used when 'completeopt' includes "preinsert" and when TRUE
4370+
* cursor needs to move back from the inserted text to the compl_leader.
43334371
*/
43344372
void
4335-
ins_compl_insert(int in_compl_func)
4373+
ins_compl_insert(int in_compl_func, int move_cursor)
43364374
{
4337-
int compl_len = get_compl_len();
4375+
int compl_len = get_compl_len();
4376+
int preinsert = ins_compl_has_preinsert();
4377+
char_u *str = compl_shown_match->cp_str.string;
4378+
int leader_len = ins_compl_leader_len();
43384379

43394380
// Make sure we don't go over the end of the string, this can happen with
43404381
// illegal bytes.
43414382
if (compl_len < (int)compl_shown_match->cp_str.length)
4342-
ins_compl_insert_bytes(compl_shown_match->cp_str.string + compl_len, -1);
4343-
if (match_at_original_text(compl_shown_match))
4383+
{
4384+
ins_compl_insert_bytes(str + compl_len, -1);
4385+
if (preinsert && move_cursor)
4386+
curwin->w_cursor.col -= ((size_t)STRLEN(str) - leader_len);
4387+
}
4388+
if (match_at_original_text(compl_shown_match) || preinsert)
43444389
compl_used_match = FALSE;
43454390
else
43464391
compl_used_match = TRUE;
@@ -4572,6 +4617,7 @@ ins_compl_next(
45724617
unsigned int cur_cot_flags = get_cot_flags();
45734618
int compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0;
45744619
int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
4620+
int compl_preinsert = ins_compl_has_preinsert();
45754621

45764622
// When user complete function return -1 for findstart which is next
45774623
// time of 'always', compl_shown_match become NULL.
@@ -4614,15 +4660,15 @@ ins_compl_next(
46144660
}
46154661

46164662
// Insert the text of the new completion, or the compl_leader.
4617-
if (compl_no_insert && !started)
4663+
if (compl_no_insert && !started && !compl_preinsert)
46184664
{
46194665
ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1);
46204666
compl_used_match = FALSE;
46214667
}
46224668
else if (insert_match)
46234669
{
46244670
if (!compl_get_longest || compl_used_match)
4625-
ins_compl_insert(in_compl_func);
4671+
ins_compl_insert(in_compl_func, TRUE);
46264672
else
46274673
ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1);
46284674
}

src/option.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ EXTERN unsigned cot_flags; // flags from 'completeopt'
531531
#define COT_NOSELECT 0x080 // FALSE: select & insert, TRUE: noselect
532532
#define COT_FUZZY 0x100 // TRUE: fuzzy match enabled
533533
#define COT_NOSORT 0x200 // TRUE: fuzzy match without qsort score
534+
#define COT_PREINSERT 0x400 // TRUE: preinsert
534535
#ifdef BACKSLASH_IN_FILENAME
535536
EXTERN char_u *p_csl; // 'completeslash'
536537
#endif

src/optionstr.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ static char *(p_fdm_values[]) = {"manual", "expr", "marker", "indent", "syntax",
120120
NULL};
121121
static char *(p_fcl_values[]) = {"all", NULL};
122122
#endif
123-
static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", "popup", "popuphidden", "noinsert", "noselect", "fuzzy", "nosort", NULL};
123+
static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", "popup", "popuphidden", "noinsert", "noselect", "fuzzy", "nosort", "preinsert", NULL};
124124
#ifdef BACKSLASH_IN_FILENAME
125125
static char *(p_csl_values[]) = {"slash", "backslash", NULL};
126126
#endif

src/proto/insexpand.pro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ void f_complete_add(typval_T *argvars, typval_T *rettv);
5858
void f_complete_check(typval_T *argvars, typval_T *rettv);
5959
void f_complete_info(typval_T *argvars, typval_T *rettv);
6060
void ins_compl_delete(void);
61-
void ins_compl_insert(int in_compl_func);
61+
void ins_compl_insert(int in_compl_func, int move_cursor);
6262
void ins_compl_check_keys(int frequency, int in_compl_func);
6363
int ins_complete(int c, int enable_pum);
6464
int ins_compl_col_range_attr(int col);
6565
void free_insexpand_stuff(void);
66+
int ins_compl_preinsert_effect(void);
6667
/* vim: set ft=c : */

src/testdir/gen_opt_test.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ let test_values = {
155155
\ ['xxx']],
156156
\ 'concealcursor': [['', 'n', 'v', 'i', 'c', 'nvic'], ['xxx']],
157157
\ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup',
158-
\ 'popuphidden', 'noinsert', 'noselect', 'fuzzy', 'menu,longest'],
158+
\ 'popuphidden', 'noinsert', 'noselect', 'fuzzy', "preinsert", 'menu,longest'],
159159
\ ['xxx', 'menu,,,longest,']],
160160
\ 'completeitemalign': [['abbr,kind,menu', 'menu,abbr,kind'],
161161
\ ['', 'xxx', 'abbr', 'abbr,menu', 'abbr,menu,kind,abbr',

src/testdir/test_ins_complete.vim

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3025,4 +3025,121 @@ func Test_complete_info_completed()
30253025
set cot&
30263026
endfunc
30273027

3028+
function Test_completeopt_preinsert()
3029+
func Omni_test(findstart, base)
3030+
if a:findstart
3031+
return col(".")
3032+
endif
3033+
return [#{word: "fobar"}, #{word: "foobar"}, #{word: "你的"}, #{word: "你好世界"}]
3034+
endfunc
3035+
set omnifunc=Omni_test
3036+
set completeopt=menu,menuone,preinsert
3037+
3038+
new
3039+
call feedkeys("S\<C-X>\<C-O>f", 'tx')
3040+
call assert_equal("fobar", getline('.'))
3041+
call feedkeys("\<C-E>\<ESC>", 'tx')
3042+
3043+
call feedkeys("S\<C-X>\<C-O>foo", 'tx')
3044+
call assert_equal("foobar", getline('.'))
3045+
call feedkeys("\<C-E>\<ESC>", 'tx')
3046+
3047+
call feedkeys("S\<C-X>\<C-O>foo\<BS>\<BS>\<BS>", 'tx')
3048+
call assert_equal("", getline('.'))
3049+
call feedkeys("\<C-E>\<ESC>", 'tx')
3050+
3051+
" delete a character and input new leader
3052+
call feedkeys("S\<C-X>\<C-O>foo\<BS>b", 'tx')
3053+
call assert_equal("fobar", getline('.'))
3054+
call feedkeys("\<C-E>\<ESC>", 'tx')
3055+
3056+
" delete preinsert when prepare completion
3057+
call feedkeys("S\<C-X>\<C-O>f\<Space>", 'tx')
3058+
call assert_equal("f ", getline('.'))
3059+
call feedkeys("\<C-E>\<ESC>", 'tx')
3060+
3061+
call feedkeys("S\<C-X>\<C-O>", 'tx')
3062+
call assert_equal("你的", getline('.'))
3063+
call feedkeys("\<C-E>\<ESC>", 'tx')
3064+
3065+
call feedkeys("S\<C-X>\<C-O>你好", 'tx')
3066+
call assert_equal("你好世界", getline('.'))
3067+
call feedkeys("\<C-E>\<ESC>", 'tx')
3068+
3069+
call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>f", 'tx')
3070+
call assert_equal("hello fobar wo", getline('.'))
3071+
call feedkeys("\<C-E>\<ESC>", 'tx')
3072+
3073+
call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>f\<BS>", 'tx')
3074+
call assert_equal("hello wo", getline('.'))
3075+
call feedkeys("\<C-E>\<ESC>", 'tx')
3076+
3077+
call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>foo", 'tx')
3078+
call assert_equal("hello foobar wo", getline('.'))
3079+
call feedkeys("\<C-E>\<ESC>", 'tx')
3080+
3081+
call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>foo\<BS>b", 'tx')
3082+
call assert_equal("hello fobar wo", getline('.'))
3083+
call feedkeys("\<C-E>\<ESC>", 'tx')
3084+
3085+
" confrim
3086+
call feedkeys("S\<C-X>\<C-O>f\<C-Y>", 'tx')
3087+
call assert_equal("fobar", getline('.'))
3088+
call assert_equal(5, col('.'))
3089+
3090+
" cancel
3091+
call feedkeys("S\<C-X>\<C-O>fo\<C-E>", 'tx')
3092+
call assert_equal("fo", getline('.'))
3093+
call assert_equal(2, col('.'))
3094+
3095+
call feedkeys("S hello hero\<CR>h\<C-X>\<C-N>", 'tx')
3096+
call assert_equal("hello", getline('.'))
3097+
call assert_equal(1, col('.'))
3098+
3099+
call feedkeys("Sh\<C-X>\<C-N>\<C-Y>", 'tx')
3100+
call assert_equal("hello", getline('.'))
3101+
call assert_equal(5, col('.'))
3102+
3103+
" delete preinsert part
3104+
call feedkeys("S\<C-X>\<C-O>fo ", 'tx')
3105+
call assert_equal("fo ", getline('.'))
3106+
call assert_equal(3, col('.'))
3107+
3108+
" whole line
3109+
call feedkeys("Shello hero\<CR>\<C-X>\<C-L>", 'tx')
3110+
call assert_equal("hello hero", getline('.'))
3111+
call assert_equal(1, col('.'))
3112+
3113+
call feedkeys("Shello hero\<CR>he\<C-X>\<C-L>", 'tx')
3114+
call assert_equal("hello hero", getline('.'))
3115+
call assert_equal(2, col('.'))
3116+
3117+
" can not work with fuzzy
3118+
set cot+=fuzzy
3119+
call feedkeys("S\<C-X>\<C-O>", 'tx')
3120+
call assert_equal("fobar", getline('.'))
3121+
call assert_equal(5, col('.'))
3122+
3123+
" test for fuzzy and noinsert
3124+
set cot+=noinsert
3125+
call feedkeys("S\<C-X>\<C-O>fb", 'tx')
3126+
call assert_equal("fb", getline('.'))
3127+
call assert_equal(2, col('.'))
3128+
3129+
call feedkeys("S\<C-X>\<C-O>", 'tx')
3130+
call assert_equal("", getline('.'))
3131+
call assert_equal(1, col('.'))
3132+
3133+
call feedkeys("S\<C-X>\<C-O>fb\<C-Y>", 'tx')
3134+
call assert_equal("fobar", getline('.'))
3135+
call assert_equal(5, col('.'))
3136+
3137+
bw!
3138+
bw!
3139+
set cot&
3140+
set omnifunc&
3141+
delfunc Omni_test
3142+
autocmd! CompleteChanged
3143+
endfunc
3144+
30283145
" vim: shiftwidth=2 sts=2 expandtab nofoldenable

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,8 @@ static char *(features[]) =
704704

705705
static int included_patches[] =
706706
{ /* Add new patch number below this line */
707+
/**/
708+
1056,
707709
/**/
708710
1055,
709711
/**/

0 commit comments

Comments
 (0)