From c830901e8cde49467d1c99c2d656a13e51979790 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 17 Dec 2024 12:40:30 +0800 Subject: [PATCH 1/3] vim-patch:9.1.0936: cannot highlight completed text Problem: cannot highlight completed text Solution: (optionally) highlight auto-completed text using the ComplMatchIns highlight group (glepnir) closes: vim/vim#16173 https://github.com/vim/vim/commit/6a38aff218f5b99a1aed7edaa357df24b9092734 Co-authored-by: glepnir --- runtime/colors/vim.lua | 1 + runtime/doc/news.txt | 1 + runtime/doc/syntax.txt | 2 ++ src/nvim/drawline.c | 13 ++++++++- src/nvim/highlight_group.c | 1 + src/nvim/insexpand.c | 51 +++++++++++++++++++++++++-------- test/old/testdir/test_popup.vim | 45 +++++++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 13 deletions(-) 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/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..e15296572b 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; @@ -954,6 +956,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s int vi_attr = 0; // attributes for Visual and incsearch highlighting int area_attr = 0; // attributes desired by highlighting int search_attr = 0; // attributes desired by 'hlsearch' + int ins_match_attr = 0; // attributes desired by PmenuMatch int vcol_save_attr = 0; // saved attr for 'cursorcolumn' int decor_attr = 0; // attributes desired by syntax and extmarks bool has_syntax = false; // this buffer has syntax highl. @@ -1117,7 +1120,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; } @@ -2704,6 +2707,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s vcol_prev = wlv.vcol; } + if (wlv.filler_todo <= 0 + && (State & MODE_INSERT) && in_curline && ins_compl_active()) { + ins_match_attr = ins_compl_col_range_attr(wlv.col); + if (ins_match_attr > 0) { + wlv.char_attr = hl_combine_attr(wlv.char_attr, ins_match_attr); + } + } + // Store character to be displayed. // Skip characters that are left of the screen for 'nowrap'. if (wlv.filler_todo > 0) { 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/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index a24e133ce6..4f5b769ace 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_higlight() + 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 From 2f7b385f2ef61626bc034bd6f3a25f5ec9f3a1f3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 18 Dec 2024 07:34:52 +0800 Subject: [PATCH 2/3] vim-patch:9.1.0941: ComplMatchIns doesn't work after multibyte chars Problem: ComplMatchIns doesn't work after multibyte chars (after v9.1.0936) Solution: Use (ptr - line) instead of wlv.col (zeertzjq). closes: vim/vim#16233 https://github.com/vim/vim/commit/f4ccada5c372b2c14cc32490860c6995cd00268c --- src/nvim/drawline.c | 27 ++++++----- test/functional/ui/popupmenu_spec.lua | 65 +++++++++++++++++++++++++++ test/old/testdir/test_popup.vim | 10 ++--- 3 files changed, 83 insertions(+), 19 deletions(-) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index e15296572b..a1d03486ff 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -956,7 +956,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s int vi_attr = 0; // attributes for Visual and incsearch highlighting int area_attr = 0; // attributes desired by highlighting int search_attr = 0; // attributes desired by 'hlsearch' - int ins_match_attr = 0; // attributes desired by PmenuMatch int vcol_save_attr = 0; // saved attr for 'cursorcolumn' int decor_attr = 0; // attributes desired by syntax and extmarks bool has_syntax = false; // this buffer has syntax highl. @@ -1632,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); @@ -1789,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; @@ -2449,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) { @@ -2643,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; @@ -2707,14 +2714,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s vcol_prev = wlv.vcol; } - if (wlv.filler_todo <= 0 - && (State & MODE_INSERT) && in_curline && ins_compl_active()) { - ins_match_attr = ins_compl_col_range_attr(wlv.col); - if (ins_match_attr > 0) { - wlv.char_attr = hl_combine_attr(wlv.char_attr, ins_match_attr); - } - } - // Store character to be displayed. // Skip characters that are left of the screen for 'nowrap'. if (wlv.filler_todo > 0) { diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index beb3ae385c..1d4709e856 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_higlight() + 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 4f5b769ace..de8674dad0 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1729,29 +1729,29 @@ func Test_pum_matchins_higlight() let buf = RunVimInTerminal('-S Xscript', {}) call TermWait(buf) - call term_sendkeys(buf, "S\\") + 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 term_sendkeys(buf, "Sαβγ \\\") call VerifyScreenDump(buf, 'Test_pum_matchins_02', {}) call term_sendkeys(buf, "\\") call TermWait(buf) - call term_sendkeys(buf, "S\\\\") + 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 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 term_sendkeys(buf, "Sαβγ \\\") call VerifyScreenDump(buf, 'Test_pum_matchins_05', {}) call term_sendkeys(buf, "\\") From b7da54aa9e72a0c7684546439f078ed89ba16d0a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 18 Dec 2024 07:49:13 +0800 Subject: [PATCH 3/3] vim-patch:9.1.0942: a few typos were found Problem: a few typos were found Solution: fix them (zeertzjq) closes: vim/vim#16232 https://github.com/vim/vim/commit/d32bf0a06762f9ad08334d67b4d7f235f87f9063 --- runtime/doc/motion.txt | 4 ++-- test/functional/ui/popupmenu_spec.lua | 2 +- test/old/testdir/test_popup.vim | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 1d4709e856..a64fc99f26 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5562,7 +5562,7 @@ describe('builtin popupmenu', function() feed('') end) - -- oldtest: Test_pum_matchins_higlight() + -- oldtest: Test_pum_matchins_highlight() it('with ComplMatchIns highlight', function() exec([[ func Omni_test(findstart, base) diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index de8674dad0..28104bdff5 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1713,7 +1713,7 @@ func Test_pum_keep_select() call StopVimInTerminal(buf) endfunc -func Test_pum_matchins_higlight() +func Test_pum_matchins_highlight() CheckScreendump let lines =<< trim END func Omni_test(findstart, base)