vim-patch:9.1.1056: Vim doesn't highlight to be inserted text when completing (#32251)

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: vim/vim#3433
closes: vim/vim#16403

edd4ac3e89

Co-authored-by: glepnir <glephunter@gmail.com>
This commit is contained in:
zeertzjq 2025-01-30 14:39:13 +08:00 committed by GitHub
parent 35c5e23107
commit efa664c7ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 205 additions and 9 deletions

View File

@ -188,6 +188,7 @@ information.
vim_strcat strncat xstrlcat
VIM_ISWHITE ascii_iswhite
IS_WHITE_OR_NUL ascii_iswhite_or_nul
IS_WHITE_NL_OR_NUL ascii_iswhite_nl_or_nul
vim_isalpha mb_isalpha
vim_isNormalIDc ascii_isident
vim_islower vim_isupper mb_islower mb_isupper

View File

@ -309,6 +309,7 @@ LUA
OPTIONS
• 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|.
• 'completeopt' flag "preinsert" highlights text to be inserted.
• 'messagesopt' configures |:messages| and |hit-enter| prompt.
• 'tabclose' controls which tab page to focus when closing a tab page.

View File

@ -1575,6 +1575,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.
*'completeslash'* *'csl'*
'completeslash' 'csl' string (default "")
local to buffer

View File

@ -1102,6 +1102,12 @@ vim.go.cia = vim.go.completeitemalign
--- 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.
---
--- @type string
vim.o.completeopt = "menu,preview"
vim.o.cot = vim.o.completeopt

View File

@ -103,6 +103,15 @@ static inline bool ascii_iswhite_or_nul(int c)
return ascii_iswhite(c) || c == NUL;
}
/// Checks if `c` is a space or tab or newline character or NUL.
///
/// @see {ascii_isdigit}
static inline bool ascii_iswhite_nl_or_nul(int c)
FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return ascii_iswhite(c) || c == '\n' || c == NUL;
}
/// Check whether character is a decimal digit.
///
/// Library isdigit() function is officially locale-dependent and, for

View File

@ -609,7 +609,10 @@ static int insert_execute(VimState *state, int key)
&& (s->c == CAR || s->c == K_KENTER || s->c == NL)))
&& stop_arrow() == OK) {
ins_compl_delete(false);
ins_compl_insert(false);
ins_compl_insert(false, false);
} else if (ascii_iswhite_nl_or_nul(s->c) && ins_compl_preinsert_effect()) {
// Delete preinserted text when typing special chars
ins_compl_delete(false);
}
}
}

View File

@ -1781,12 +1781,33 @@ int ins_compl_len(void)
return compl_length;
}
/// Return true when preinsert is set otherwise FALSE.
static bool ins_compl_has_preinsert(void)
{
return (get_cot_flags() & (kOptCotFlagFuzzy|kOptCotFlagPreinsert)) == kOptCotFlagPreinsert;
}
/// Returns true if the pre-insert effect is valid and the cursor is within
/// the `compl_ins_end_col` range.
bool 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
/// to be got from the user.
int ins_compl_bs(void)
{
if (ins_compl_preinsert_effect()) {
ins_compl_delete(false);
}
char *line = get_cursor_line_ptr();
char *p = line + curwin->w_cursor.col;
MB_PTR_BACK(line, p);
@ -1872,8 +1893,13 @@ static void ins_compl_new_leader(void)
ins_compl_show_pum();
// 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.size > 0) {
ins_compl_insert(false, true);
}
// Don't let Enter select when use user function and refresh_always is set
if (compl_match_array == NULL || ins_compl_refresh_always()) {
if (ins_compl_refresh_always()) {
compl_enter_selects = false;
}
}
@ -1896,6 +1922,10 @@ void ins_compl_addleader(int c)
{
int cc;
if (ins_compl_preinsert_effect()) {
ins_compl_delete(false);
}
if (stop_arrow() == FAIL) {
return;
}
@ -3696,6 +3726,12 @@ void ins_compl_delete(bool new_leader)
// In insert mode: Delete the typed part.
// In replace mode: Put the old characters back, if any.
int col = compl_col + (compl_status_adding() ? compl_length : orig_col);
bool has_preinsert = ins_compl_preinsert_effect();
if (has_preinsert) {
col = compl_col + (int)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) {
return;
@ -3713,15 +3749,24 @@ void ins_compl_delete(bool new_leader)
/// Insert the new text being completed.
/// "in_compl_func" is true when called from complete_check().
void ins_compl_insert(bool in_compl_func)
/// "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(bool in_compl_func, bool move_cursor)
{
int compl_len = get_compl_len();
bool preinsert = ins_compl_has_preinsert();
char *str = compl_shown_match->cp_str.data;
size_t 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.size) {
ins_compl_insert_bytes(compl_shown_match->cp_str.data + compl_len, -1);
ins_compl_insert_bytes(str + compl_len, -1);
if (preinsert && move_cursor) {
curwin->w_cursor.col -= (colnr_T)(strlen(str) - leader_len);
}
}
compl_used_match = !match_at_original_text(compl_shown_match);
compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert);
dict_T *dict = ins_compl_dict_alloc(compl_shown_match);
set_vim_var_dict(VV_COMPLETED_ITEM, dict);
@ -3923,6 +3968,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
unsigned cur_cot_flags = get_cot_flags();
bool compl_no_insert = (cur_cot_flags & kOptCotFlagNoinsert) != 0;
bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
bool 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.
@ -3967,13 +4013,13 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
}
// 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.data + get_compl_len(), -1);
compl_used_match = false;
restore_orig_extmarks();
} 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 {
assert(compl_leader.data != NULL);
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1);

View File

@ -1494,6 +1494,7 @@ local options = {
'noselect',
'fuzzy',
'nosort',
'preinsert',
},
flags = true,
deny_duplicates = true,
@ -1542,6 +1543,12 @@ local options = {
nosort Disable sorting of completion candidates based on fuzzy
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.
]=],
full_name = 'completeopt',
list = 'onecomma',

View File

@ -87,7 +87,7 @@ let test_values = {
\ ['xxx', 'help,nofile']],
\ 'clipboard': [['', 'unnamed'], ['xxx', '\ze*', 'exclude:\\%(']],
\ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup',
\ 'noinsert', 'noselect', 'fuzzy', 'menu,longest'],
\ 'noinsert', 'noselect', 'fuzzy', 'preinsert', 'menu,longest'],
\ ['xxx', 'menu,,,longest,']],
\ 'encoding': [['utf8'], []],
\ 'foldcolumn': [[0, 1, 4, 'auto', 'auto:1', 'auto:9'], [-1, 13, 999]],
@ -186,7 +186,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

@ -2964,4 +2964,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