diff --git a/runtime/colors/vim.lua b/runtime/colors/vim.lua index 5b9309ab38..dd3e83c653 100644 --- a/runtime/colors/vim.lua +++ b/runtime/colors/vim.lua @@ -60,6 +60,7 @@ hi('PmenuMatch', { link = 'Pmenu' }) hi('PmenuMatchSel', { link = 'PmenuSel' }) hi('PmenuExtra', { link = 'Pmenu' }) hi('PmenuExtraSel', { link = 'PmenuSel' }) +hi('ComplMatchIns', { link = 'Normal' }) hi('Substitute', { link = 'Search' }) hi('Whitespace', { link = 'NonText' }) hi('MsgSeparator', { link = 'StatusLine' }) diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 41fcdc3e1b..284be09121 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -118,8 +118,8 @@ Note that when using ":" any motion becomes charwise exclusive. *inclusive-motion-selection-exclusive* When 'selection' is "exclusive", |Visual| mode is active and an inclusive motion has been used, the cursor position will be adjusted by another -character to the right, so that visual selction includes the expected text and -can be acted upon. +character to the right, so that the Visual selection includes the expected +text and can be acted upon. *forced-motion* FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ad0835e80f..73704a143c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -216,6 +216,7 @@ EDITOR • On Windows, filename arguments on the command-line prefixed with "~\" or "~/" are now expanded to the user's profile directory, not a relative path to a literal "~" directory. +• |hl-ComplMatchIns| shows matched text of the currently inserted completion. • |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup. EVENTS diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index df4d0f7260..f5183e3247 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5243,6 +5243,8 @@ PmenuMatch Popup menu: Matched text in normal item. Combined with *hl-PmenuMatchSel* PmenuMatchSel Popup menu: Matched text in selected item. Combined with |hl-PmenuMatch| and |hl-PmenuSel|. + *hl-ComplMatchIns* +ComplMatchIns Matched text of the currently inserted completion. *hl-Question* Question |hit-enter| prompt and yes/no questions. *hl-QuickFixLine* diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index b8f21d7454..a1d03486ff 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -31,6 +31,7 @@ #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" +#include "nvim/insexpand.h" #include "nvim/mark_defs.h" #include "nvim/marktree_defs.h" #include "nvim/match.h" @@ -934,6 +935,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s colnr_T vcol_prev = -1; // "wlv.vcol" of previous character ScreenGrid *grid = &wp->w_grid; // grid specific to the window + const bool in_curline = wp == curwin && lnum == curwin->w_cursor.lnum; const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; const bool has_foldtext = has_fold && *wp->w_p_fdt != NUL; @@ -1117,7 +1119,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } // Check if the char under the cursor should be inverted (highlighted). - if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin + if (!highlight_match && in_curline && cursor_is_block_during_visual(*p_sel == 'e')) { noinvcur = true; } @@ -1629,8 +1631,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } // When still displaying '$' of change command, stop at cursor. - if (dollar_vcol >= 0 && wp == curwin - && lnum == wp->w_cursor.lnum && wlv.vcol >= wp->w_virtcol) { + if (dollar_vcol >= 0 && in_curline && wlv.vcol >= wp->w_virtcol) { draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); // don't clear anything after wlv.col wlv_put_linebuf(wp, &wlv, wlv.col, false, bg_attr, 0); @@ -1786,6 +1787,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); } + // Apply ComplMatchIns highlight if needed. + if (wlv.filler_todo <= 0 + && (State & MODE_INSERT) && in_curline && ins_compl_active()) { + int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line)); + if (ins_match_attr > 0) { + char_attr_pri = hl_combine_attr(char_attr_pri, ins_match_attr); + wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); + } + } + if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) { const int v = (int)(ptr - line); linenr_T lnume = lnum + foldinfo.fi_lines - 1; @@ -2446,8 +2457,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // With 'virtualedit' we may never reach cursor position, but we still // need to correct the cursor column, so do that at end of line. if (!did_wcol && wlv.filler_todo <= 0 - && wp == curwin && lnum == wp->w_cursor.lnum - && conceal_cursor_line(wp) + && in_curline && conceal_cursor_line(wp) && (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) { wp->w_wcol = wlv.col - wlv.boguscols; if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) { @@ -2640,7 +2650,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // Update w_cline_height and w_cline_folded if the cursor line was // updated (saves a call to plines_win() later). - if (wp == curwin && lnum == curwin->w_cursor.lnum) { + if (in_curline) { curwin->w_cline_row = startrow; curwin->w_cline_height = wlv.row - startrow; curwin->w_cline_folded = has_fold; diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index f1f5f47630..636124a698 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -173,6 +173,7 @@ static const char *highlight_init_both[] = { "default link PmenuKind Pmenu", "default link PmenuKindSel PmenuSel", "default link PmenuSbar Pmenu", + "default link ComplMatchIns Normal", "default link Substitute Search", "default link StatusLineTerm StatusLine", "default link StatusLineTermNC StatusLineNC", diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 2e3a2e39ac..5036ec5e45 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -256,6 +256,7 @@ static pos_T compl_startpos; static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed +static colnr_T compl_ins_end_col = 0; static char *compl_orig_text = NULL; ///< text as it was before ///< completion started /// Undo information to restore extmarks for original text. @@ -282,6 +283,11 @@ static size_t spell_bad_len = 0; // length of located bad word static int compl_selected_item = -1; +// "compl_match_array" points the currently displayed list of entries in the +// popup menu. It is NULL when there is no popup menu. +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; + /// CTRL-X pressed in Insert mode. void ins_ctrl_x(void) { @@ -943,6 +949,30 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len) return strncmp(match->cp_str, str, len) == 0; } +/// when len is -1 mean use whole length of p otherwise part of p +static void ins_compl_insert_bytes(char *p, int len) + FUNC_ATTR_NONNULL_ALL +{ + if (len == -1) { + len = (int)strlen(p); + } + assert(len >= 0); + ins_bytes_len(p, (size_t)len); + compl_ins_end_col = curwin->w_cursor.col - 1; +} + +/// Checks if the column is within the currently inserted completion text +/// column range. If it is, it returns a special highlight attribute. +/// -1 mean normal item. +int ins_compl_col_range_attr(int col) +{ + if (col >= compl_col && col < compl_ins_end_col) { + return syn_name2attr("ComplMatchIns"); + } + + return -1; +} + /// Reduce the longest common string for match "match". static void ins_compl_longest_match(compl_T *match) { @@ -952,7 +982,7 @@ static void ins_compl_longest_match(compl_T *match) bool had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(false); - ins_bytes(compl_leader + get_compl_len()); + ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it @@ -986,7 +1016,7 @@ static void ins_compl_longest_match(compl_T *match) *p = NUL; bool had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(false); - ins_bytes(compl_leader + get_compl_len()); + ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it @@ -1058,11 +1088,6 @@ unsigned get_cot_flags(void) return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags; } -/// "compl_match_array" points the currently displayed list of entries in the -/// popup menu. It is NULL when there is no popup menu. -static pumitem_T *compl_match_array = NULL; -static int compl_match_arraysize; - /// Remove any popup menu. static void ins_compl_del_pum(void) { @@ -1678,6 +1703,7 @@ void ins_compl_clear(void) compl_cont_status = 0; compl_started = false; compl_matches = 0; + compl_ins_end_col = 0; XFREE_CLEAR(compl_pattern); compl_patternlen = 0; XFREE_CLEAR(compl_leader); @@ -1802,7 +1828,7 @@ static void ins_compl_new_leader(void) { ins_compl_del_pum(); ins_compl_delete(true); - ins_bytes(compl_leader + get_compl_len()); + ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); compl_used_match = false; if (compl_started) { @@ -2137,7 +2163,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) const int compl_len = get_compl_len(); const int len = (int)strlen(p); if (len > compl_len) { - ins_bytes_len(p + compl_len, (size_t)(len - compl_len)); + ins_compl_insert_bytes(p + compl_len, len - compl_len); } } restore_orig_extmarks(); @@ -3639,7 +3665,7 @@ void ins_compl_insert(bool in_compl_func) // Make sure we don't go over the end of the string, this can happen with // illegal bytes. if (compl_len < (int)strlen(compl_shown_match->cp_str)) { - ins_bytes(compl_shown_match->cp_str + compl_len); + ins_compl_insert_bytes(compl_shown_match->cp_str + compl_len, -1); } compl_used_match = !match_at_original_text(compl_shown_match); @@ -3888,14 +3914,15 @@ 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) { - ins_bytes(compl_orig_text + get_compl_len()); + ins_compl_insert_bytes(compl_orig_text + 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); } else { - ins_bytes(compl_leader + get_compl_len()); + assert(compl_leader != NULL); + ins_compl_insert_bytes(compl_leader + get_compl_len(), -1); } if (!strcmp(compl_curr_match->cp_str, compl_orig_text)) { restore_orig_extmarks(); diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index beb3ae385c..a64fc99f26 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5561,6 +5561,71 @@ describe('builtin popupmenu', function() ]]) feed('') end) + + -- oldtest: Test_pum_matchins_highlight() + it('with ComplMatchIns highlight', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi ComplMatchIns guifg=red + ]]) + + feed('Sαβγ ') + screen:expect([[ + αβγ {8:foo}^ | + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('') + + feed('Sαβγ ') + screen:expect([[ + αβγ {8:bar}^ | + {1:~ }{n: foo }{1: }| + {1:~ }{s: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 2 of 3} | + ]]) + feed('') + + feed('Sαβγ ') + screen:expect([[ + αβγ {8:你好}^ | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('') + + -- restore after accept + feed('Sαβγ ') + screen:expect([[ + αβγ foo^ | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('') + + -- restore after cancel completion + feed('Sαβγ ') + screen:expect([[ + αβγ foo ^ | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('') + end) end end diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index a24e133ce6..28104bdff5 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1713,4 +1713,49 @@ func Test_pum_keep_select() call StopVimInTerminal(buf) endfunc +func Test_pum_matchins_highlight() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi ComplMatchIns ctermfg=red + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \\") + call VerifyScreenDump(buf, 'Test_pum_matchins_01', {}) + call term_sendkeys(buf, "\\") + + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \\\") + call VerifyScreenDump(buf, 'Test_pum_matchins_02', {}) + call term_sendkeys(buf, "\\") + + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \\\\") + call VerifyScreenDump(buf, 'Test_pum_matchins_03', {}) + call term_sendkeys(buf, "\\") + + " restore after accept + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \\\") + call VerifyScreenDump(buf, 'Test_pum_matchins_04', {}) + call term_sendkeys(buf, "\\") + + " restore after cancel completion + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \\\") + call VerifyScreenDump(buf, 'Test_pum_matchins_05', {}) + call term_sendkeys(buf, "\\") + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab