aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2025-01-30 14:39:13 +0800
committerGitHub <noreply@github.com>2025-01-30 14:39:13 +0800
commitefa664c7ed21b63f2cf0a8caa53161fe7e32b2bb (patch)
tree5823bfb8aadf263a079818a96480dfd35f5d0685
parent35c5e231078365033524b0aa2166118a1b2ef600 (diff)
downloadrneovim-efa664c7ed21b63f2cf0a8caa53161fe7e32b2bb.tar.gz
rneovim-efa664c7ed21b63f2cf0a8caa53161fe7e32b2bb.tar.bz2
rneovim-efa664c7ed21b63f2cf0a8caa53161fe7e32b2bb.zip
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 https://github.com/vim/vim/commit/edd4ac3e895ce16034c7e098f1d68e0155d97886 Co-authored-by: glepnir <glephunter@gmail.com>
-rw-r--r--runtime/doc/dev_vimpatch.txt1
-rw-r--r--runtime/doc/news.txt1
-rw-r--r--runtime/doc/options.txt6
-rw-r--r--runtime/lua/vim/_meta/options.lua6
-rw-r--r--src/nvim/ascii_defs.h9
-rw-r--r--src/nvim/edit.c5
-rw-r--r--src/nvim/insexpand.c58
-rw-r--r--src/nvim/options.lua7
-rw-r--r--test/old/testdir/gen_opt_test.vim4
-rw-r--r--test/old/testdir/test_ins_complete.vim117
10 files changed, 205 insertions, 9 deletions
diff --git a/runtime/doc/dev_vimpatch.txt b/runtime/doc/dev_vimpatch.txt
index 5119613b55..76be24878a 100644
--- a/runtime/doc/dev_vimpatch.txt
+++ b/runtime/doc/dev_vimpatch.txt
@@ -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
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 65417971ab..2208428f75 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -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.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 1f19db5eb9..b4ae3cc8fd 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index f0f0d1a768..452959970d 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -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
diff --git a/src/nvim/ascii_defs.h b/src/nvim/ascii_defs.h
index 155a18fb95..86187b553c 100644
--- a/src/nvim/ascii_defs.h
+++ b/src/nvim/ascii_defs.h
@@ -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
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index e55cda1c21..9e17c93f3f 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -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);
}
}
}
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 8eb1a49e7d..73b84175d7 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -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);
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index c6f5221c8b..fdd5799b46 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -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',
diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim
index bcb0f3d4c4..f9fcc4ae1b 100644
--- a/test/old/testdir/gen_opt_test.vim
+++ b/test/old/testdir/gen_opt_test.vim
@@ -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',
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
index 1c63e8f4cc..969d5012f4 100644
--- a/test/old/testdir/test_ins_complete.vim
+++ b/test/old/testdir/test_ins_complete.vim
@@ -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