diff options
-rw-r--r-- | runtime/doc/dev_vimpatch.txt | 1 | ||||
-rw-r--r-- | runtime/doc/news.txt | 1 | ||||
-rw-r--r-- | runtime/doc/options.txt | 6 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 6 | ||||
-rw-r--r-- | src/nvim/ascii_defs.h | 9 | ||||
-rw-r--r-- | src/nvim/edit.c | 5 | ||||
-rw-r--r-- | src/nvim/insexpand.c | 58 | ||||
-rw-r--r-- | src/nvim/options.lua | 7 | ||||
-rw-r--r-- | test/old/testdir/gen_opt_test.vim | 4 | ||||
-rw-r--r-- | test/old/testdir/test_ins_complete.vim | 117 |
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 |