From dc4037f612c6f3bbc76890adfa638d48ec238eee Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 13 Jun 2024 14:08:50 +0800 Subject: vim-patch:9.1.0476: Cannot see matched text in popup menu Problem: Cannot see matched text in popup menu Solution: Introduce 2 new highlighting groups: PmenuMatch and PmenuMatchSel (glepnir) closes: vim/vim#14694 https://github.com/vim/vim/commit/40c1c3317d92f8c2adadf744fab72e4458e2a9fa Co-authored-by: glepnir --- runtime/colors/vim.lua | 2 + runtime/doc/syntax.txt | 5 ++ src/nvim/highlight.h | 2 + src/nvim/highlight_defs.h | 2 + src/nvim/highlight_group.c | 2 + src/nvim/insexpand.c | 6 ++ src/nvim/option_vars.h | 14 ++-- src/nvim/popupmenu.c | 75 +++++++++++++++++++++- src/nvim/search.c | 82 +++++++++++++++++++++++- test/functional/ui/cursor_spec.lua | 4 +- test/functional/ui/popupmenu_spec.lua | 117 ++++++++++++++++++++++++++++++++++ test/old/testdir/test_popup.vim | 67 +++++++++++++++++++ 12 files changed, 365 insertions(+), 13 deletions(-) diff --git a/runtime/colors/vim.lua b/runtime/colors/vim.lua index 76a56566a6..5b9309ab38 100644 --- a/runtime/colors/vim.lua +++ b/runtime/colors/vim.lua @@ -56,6 +56,8 @@ hi('CursorLineFold', { link = 'FoldColumn' }) hi('CurSearch', { link = 'Search' }) hi('PmenuKind', { link = 'Pmenu' }) hi('PmenuKindSel', { link = 'PmenuSel' }) +hi('PmenuMatch', { link = 'Pmenu' }) +hi('PmenuMatchSel', { link = 'PmenuSel' }) hi('PmenuExtra', { link = 'Pmenu' }) hi('PmenuExtraSel', { link = 'PmenuSel' }) hi('Substitute', { link = 'Search' }) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 9fc415a158..ac4dc6286c 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5076,6 +5076,11 @@ PmenuExtraSel Popup menu: Selected item "extra text". PmenuSbar Popup menu: Scrollbar. *hl-PmenuThumb* PmenuThumb Popup menu: Thumb of the scrollbar. + *hl-PmenuMatch* +PmenuMatch Popup menu: Matched text in normal item + *hl-PmenuMatchSel* +PmenuMatchSel Popup menu: Matched text in selected item + *hl-Question* Question |hit-enter| prompt and yes/no questions. *hl-QuickFixLine* diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index 558727fc51..2ba6cf5371 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -54,6 +54,8 @@ EXTERN const char *hlf_names[] INIT( = { [HLF_SPL] = "SpellLocal", [HLF_PNI] = "Pmenu", [HLF_PSI] = "PmenuSel", + [HLF_PMNI] = "PmenuMatch", + [HLF_PMSI] = "PmenuMatchSel", [HLF_PNK] = "PmenuKind", [HLF_PSK] = "PmenuKindSel", [HLF_PNX] = "PmenuExtra", diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 17e3db04da..e0cce81166 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -101,6 +101,8 @@ typedef enum { HLF_SPL, ///< SpellLocal HLF_PNI, ///< popup menu normal item HLF_PSI, ///< popup menu selected item + HLF_PMNI, ///< popup menu matched text in normal item + HLF_PMSI, ///< popup menu matched text in selected item HLF_PNK, ///< popup menu normal item "kind" HLF_PSK, ///< popup menu selected item "kind" HLF_PNX, ///< popup menu normal item "menu" (extra text) diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index aa98983b63..3c777e7c4d 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -169,6 +169,8 @@ static const char *highlight_init_both[] = { "default link PmenuExtraSel PmenuSel", "default link PmenuKind Pmenu", "default link PmenuKindSel PmenuSel", + "default link PmenuMatch Pmenu", + "default link PmenuMatchSel PmenuSel", "default link PmenuSbar Pmenu", "default link Substitute Search", "default link StatusLineTerm StatusLine", diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 139637b8a1..9b6805404f 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1388,6 +1388,12 @@ bool compl_match_curr_select(int selected) #define DICT_FIRST (1) ///< use just first element in "dict" #define DICT_EXACT (2) ///< "dict" is the exact name of a file +/// Get current completion leader +char *ins_compl_leader(void) +{ + return compl_leader; +} + /// Add any identifiers that match the given pattern "pat" in the list of /// dictionary files "dict_start" to the list of completions. /// diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index bd0fe699d9..404e58661c 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -55,13 +55,13 @@ #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ - "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \ - "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ - "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ - "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel," \ - "[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb," \ - "*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \ - "q:QuickFixLine,g:MsgArea,0:Whitespace,I:NormalNC" + "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC," \ + "c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \ + "A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap," \ + "R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind," \ + "]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel," \ + "_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm," \ + "Z:StatusLineTermNC,g:MsgArea,0:Whitespace,I:NormalNC" // Default values for 'errorformat'. // The "%f|%l| %m" one is used for when the contents of the quickfix window is diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 324254a188..a7fdd44ef0 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -25,6 +25,7 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" #include "nvim/extmark_defs.h" +#include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" @@ -48,6 +49,7 @@ #include "nvim/plines.h" #include "nvim/popupmenu.h" #include "nvim/pos_defs.h" +#include "nvim/search.h" #include "nvim/state_defs.h" #include "nvim/strings.h" #include "nvim/types_defs.h" @@ -435,6 +437,74 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_redraw(); } +/// Displays text on the popup menu with specific attributes. +static void pum_puts_with_attr(int col, const char *text, int attr) +{ + char *leader = ins_compl_leader(); + + if ((win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI) + && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) { + grid_line_puts(col, text, -1, attr); + return; + } + + char *rt_leader = NULL; + if (leader != NULL && curwin->w_p_rl) { + rt_leader = reverse_text(leader); + } + char *match_leader = rt_leader != NULL ? rt_leader : leader; + size_t leader_len = match_leader ? strlen(match_leader) : 0; + + const bool in_fuzzy = (get_cot_flags() & COT_FUZZY) != 0; + + garray_T *ga = NULL; + if (match_leader != NULL && leader_len > 0 && in_fuzzy) { + ga = fuzzy_match_str_with_pos(text, match_leader); + } + + // Render text with proper attributes + const char *ptr = text; + while (*ptr != NUL) { + int char_len = utfc_ptr2len(ptr); + int cells = utf_ptr2cells(ptr); + int new_attr = attr; + + if (ga != NULL) { + // Handle fuzzy matching + for (int i = 0; i < ga->ga_len; i++) { + int *match_pos = ((int *)ga->ga_data) + i; + int actual_char_pos = 0; + const char *temp_ptr = text; + while (temp_ptr < ptr) { + temp_ptr += utfc_ptr2len(temp_ptr); + actual_char_pos++; + } + if (actual_char_pos == match_pos[0]) { + new_attr = win_hl_attr(curwin, (attr == win_hl_attr(curwin, HLF_PSI) + ? HLF_PMSI : HLF_PMNI)); + break; + } + } + } else if (!in_fuzzy && ptr < text + leader_len + && strncmp(text, match_leader, leader_len) == 0) { + new_attr = win_hl_attr(curwin, (attr == win_hl_attr(curwin, HLF_PSI) + ? HLF_PMSI : HLF_PMNI)); + } + + grid_line_puts(col, ptr, char_len, new_attr); + col += cells; + ptr += char_len; + } + + if (ga != NULL) { + ga_clear(ga); + xfree(ga); + } + if (rt_leader) { + xfree(rt_leader); + } +} + /// Redraw the popup menu, using "pum_first" and "pum_selected". void pum_redraw(void) { @@ -593,13 +663,12 @@ void pum_redraw(void) size++; } } - grid_line_puts(grid_col - size + 1, rt, -1, attr); + pum_puts_with_attr(grid_col - size + 1, rt, attr); xfree(rt_start); xfree(st); grid_col -= width; } else { - // use grid_line_puts() to truncate the text - grid_line_puts(grid_col, st, -1, attr); + pum_puts_with_attr(grid_col, st, attr); xfree(st); grid_col += width; } diff --git a/src/nvim/search.c b/src/nvim/search.c index bee124e305..fef27dba48 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -21,12 +21,14 @@ #include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" @@ -3254,7 +3256,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat const char *const key, Callback *const item_cb, const bool retmatchpos, list_T *const fmatchlist, const int max_matches) - FUNC_ATTR_NONNULL_ARG(2, 5, 7) + FUNC_ATTR_NONNULL_ARG(2, 7) { int len = tv_list_len(l); if (len == 0) { @@ -3542,6 +3544,84 @@ int fuzzy_match_str(char *const str, const char *const pat) return score; } +/// Fuzzy match the position of string "pat" in string "str". +/// @returns a dynamic array of matching positions. If there is no match, returns NULL. +garray_T *fuzzy_match_str_with_pos(const char *const str, char *const pat) +{ + garray_T *match_positions = xmalloc(sizeof(garray_T)); + + ga_init(match_positions, sizeof(int), 10); + if (str == NULL || pat == NULL) { + ga_clear(match_positions); + return NULL; + } + list_T *l = tv_list_alloc(1); + + typval_T tv_str = { + .v_type = VAR_STRING, + .vval.v_string = xstrdup(str), + }; + tv_list_append_tv(l, &tv_str); + + list_T *retlist = tv_list_alloc(3); + list_T *match_str_list = tv_list_alloc(1); + list_T *match_pos_list = tv_list_alloc(1); + list_T *match_score_list = tv_list_alloc(1); + + tv_list_append_list(retlist, match_str_list); + tv_list_append_list(retlist, match_pos_list); + tv_list_append_list(retlist, match_score_list); + + fuzzy_match_in_list(l, pat, false, NULL, NULL, true, retlist, 1); + + varnumber_T score = 0; + listitem_T *score_item = tv_list_find(retlist, 2); + if (score_item != NULL && TV_LIST_ITEM_TV(score_item)->v_type == VAR_LIST) { + list_T *score_list = TV_LIST_ITEM_TV(score_item)->vval.v_list; + if (tv_list_len(score_list) > 0) { + listitem_T *first_score_item = tv_list_first(score_list); + if (first_score_item != NULL && TV_LIST_ITEM_TV(first_score_item)->v_type == VAR_NUMBER) { + score = TV_LIST_ITEM_TV(first_score_item)->vval.v_number; + } + } + } + if (score == 0) { + goto cleanup; + } + + listitem_T *positions_item = tv_list_find(retlist, 1); + if (positions_item != NULL && TV_LIST_ITEM_TV(positions_item)->v_type == VAR_LIST) { + list_T *positions_outer_list = TV_LIST_ITEM_TV(positions_item)->vval.v_list; + if (tv_list_len(positions_outer_list) > 0) { + listitem_T *outer_li = tv_list_first(positions_outer_list); + if (outer_li != NULL && TV_LIST_ITEM_TV(outer_li)->v_type == VAR_LIST) { + list_T *positions_inner_list = TV_LIST_ITEM_TV(outer_li)->vval.v_list; + TV_LIST_ITER_CONST(positions_inner_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + varnumber_T pos = TV_LIST_ITEM_TV(li)->vval.v_number; + GA_APPEND(int, match_positions, (int)pos); + } + }); + } + } + } + + xfree(tv_str.vval.v_string); + tv_list_free(retlist); + tv_list_free(l); + return match_positions; + +cleanup: + xfree(tv_str.vval.v_string); + tv_list_free(match_str_list); + tv_list_free(match_pos_list); + tv_list_free(match_score_list); + tv_list_free(retlist); + tv_list_free(l); + ga_clear(match_positions); + return NULL; +} + /// Copy a list of fuzzy matches into a string list after sorting the matches by /// the fuzzy score. Frees the memory allocated for "fuzmatch". void fuzzymatches_to_strmatches(fuzmatch_str_T *const fuzmatch, char ***const matches, diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index f7eb8394bd..619153724b 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -246,11 +246,11 @@ describe('ui/cursor', function() end end if m.hl_id then - m.hl_id = 64 + m.hl_id = 66 m.attr = { background = Screen.colors.DarkGray } end if m.id_lm then - m.id_lm = 71 + m.id_lm = 73 end end diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index cc21a797e3..0ce0fe4eab 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -1177,6 +1177,8 @@ describe('builtin popupmenu', function() ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey }, xn = { foreground = Screen.colors.White, background = Screen.colors.Magenta }, xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey }, + mn = { foreground = Screen.colors.Blue, background = Screen.colors.White }, + ms = { foreground = Screen.colors.Green, background = Screen.colors.White }, }) screen:attach({ ext_multigrid = multigrid }) end) @@ -4585,6 +4587,121 @@ describe('builtin popupmenu', function() ]]) end) end) + + -- oldtest: Test_pum_highlights_match() + it('can highlight matched text', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foo',}, + \ { 'word': 'foobar',}, + \ { 'word': 'fooBaz',}, + \ { 'word': 'foobala',}, + \ { 'word': '你好',}, + \ { 'word': '你好吗',}, + \ { 'word': '你不好吗',}, + \ { 'word': '你可好吗',}, + \]} + endfunc + set omnifunc=Omni_test + set completeopt=menu,noinsert,fuzzy + hi PmenuMatchSel guifg=Green guibg=White + hi PmenuMatch guifg=Blue guibg=White + ]]) + feed('i') + local pum_start = [[ + ^ | + {s:foo }{1: }| + {n:foobar }{1: }| + {n:fooBaz }{1: }| + {n:foobala }{1: }| + {n:你好 }{1: }| + {n:你好吗 }{1: }| + {n:你不好吗 }{1: }| + {n:你可好吗 }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 8} | + ]] + screen:expect(pum_start) + feed('fo') + screen:expect([[ + fo^ | + {ms:fo}{s:o }{1: }| + {mn:fo}{n:obar }{1: }| + {mn:fo}{n:oBaz }{1: }| + {mn:fo}{n:obala }{1: }| + {1:~ }|*14 + {2:-- }{5:match 1 of 8} | + ]]) + feed('S') + screen:expect(pum_start) + feed('你') + screen:expect([[ + 你^ | + {ms:你}{s:好 }{1: }| + {mn:你}{n:好吗 }{1: }| + {mn:你}{n:不好吗 }{1: }| + {mn:你}{n:可好吗 }{1: }| + {1:~ }|*14 + {2:-- }{5:match 1 of 8} | + ]]) + feed('吗') + screen:expect([[ + 你吗^ | + {ms:你}{s:好}{ms:吗}{s: }{1: }| + {mn:你}{n:不好}{mn:吗}{n: }{1: }| + {mn:你}{n:可好}{mn:吗}{n: }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 8} | + ]]) + + feed('') + command('set rightleft') + feed('S') + screen:expect([[ + ^ | + {1: }{s: oof}| + {1: }{n: raboof}| + {1: }{n: zaBoof}| + {1: }{n: alaboof}| + {1: }{n: 好你}| + {1: }{n: 吗好你}| + {1: }{n: 吗好不你}| + {1: }{n: 吗好可你}| + {1: ~}|*10 + {2:-- }{5:match 1 of 8} | + ]]) + feed('fo') + screen:expect([[ + ^ of| + {1: }{s: o}{ms:of}| + {1: }{n: rabo}{mn:of}| + {1: }{n: zaBo}{mn:of}| + {1: }{n: alabo}{mn:of}| + {1: ~}|*14 + {2:-- }{5:match 1 of 8} | + ]]) + feed('') + command('set norightleft') + + command('set completeopt-=fuzzy') + feed('S') + screen:expect(pum_start) + feed('fo') + screen:expect([[ + fo^ | + {ms:fo}{s:o }{1: }| + {mn:fo}{n:obar }{1: }| + {mn:fo}{n:oBaz }{1: }| + {mn:fo}{n:obala }{1: }| + {1:~ }|*14 + {2:-- }{5:match 1 of 8} | + ]]) + end) end end diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 5ae2ed5ed5..acebcae1dd 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1348,4 +1348,71 @@ func Test_pum_highlights_custom() call StopVimInTerminal(buf) endfunc +" Test match relate highlight group in pmenu +func Test_pum_highlights_match() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foo',}, + \ { 'word': 'foobar',}, + \ { 'word': 'fooBaz',}, + \ { 'word': 'foobala',}, + \ { 'word': '你好',}, + \ { 'word': '你好吗',}, + \ { 'word': '你不好吗',}, + \ { 'word': '你可好吗',}, + \]} + endfunc + set omnifunc=Omni_test + set completeopt=menu,noinsert,fuzzy + hi PmenuMatchSel ctermfg=6 ctermbg=225 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\\") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_03', {}) + call term_sendkeys(buf, "\S\\") + call TermWait(buf, 50) + call term_sendkeys(buf, "你") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_04', {}) + call term_sendkeys(buf, "吗") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_05', {}) + + if has('rightleft') + call term_sendkeys(buf, "\\u:set rightleft\") + call TermWait(buf, 50) + call term_sendkeys(buf, "i\\") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_06', {}) + call term_sendkeys(buf, "\\u:set norightleft\") + call TermWait(buf) + endif + + call term_sendkeys(buf, ":set completeopt-=fuzzy\") + call TermWait(buf) + call term_sendkeys(buf, "\S\\") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_07', {}) + + call term_sendkeys(buf, "\\u") + call TermWait(buf) + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From e2ef5330252b3b52b816503cdcb01934b68f43a0 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Jun 2024 04:28:41 +0800 Subject: vim-patch:9.1.0479: fuzzy_match_str_with_pos() does unnecessary list operations Problem: fuzzy_match_str_with_pos() does unnecessary list operations. Solution: Use fuzzy_match() directly (zeertzjq). closes: vim/vim#14987 https://github.com/vim/vim/commit/2f95ca9fcef8495d60e298ac2cd6d702b90bfb18 N/A patch: vim-patch:9.1.0478: potential deref of NULL pointer in fuzzy_match_str_with_pos --- runtime/doc/syntax.txt | 1 - src/nvim/popupmenu.c | 6 ++-- src/nvim/search.c | 83 +++++++++++--------------------------------------- 3 files changed, 21 insertions(+), 69 deletions(-) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index ac4dc6286c..53c8846a85 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5080,7 +5080,6 @@ PmenuThumb Popup menu: Thumb of the scrollbar. PmenuMatch Popup menu: Matched text in normal item *hl-PmenuMatchSel* PmenuMatchSel Popup menu: Matched text in selected item - *hl-Question* Question |hit-enter| prompt and yes/no questions. *hl-QuickFixLine* diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index a7fdd44ef0..50569fbc42 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -438,7 +438,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } /// Displays text on the popup menu with specific attributes. -static void pum_puts_with_attr(int col, const char *text, int attr) +static void pum_puts_with_attr(int col, char *text, int attr) { char *leader = ins_compl_leader(); @@ -472,8 +472,8 @@ static void pum_puts_with_attr(int col, const char *text, int attr) if (ga != NULL) { // Handle fuzzy matching for (int i = 0; i < ga->ga_len; i++) { - int *match_pos = ((int *)ga->ga_data) + i; - int actual_char_pos = 0; + uint32_t *match_pos = ((uint32_t *)ga->ga_data) + i; + uint32_t actual_char_pos = 0; const char *temp_ptr = text; while (temp_ptr < ptr) { temp_ptr += utfc_ptr2len(temp_ptr); diff --git a/src/nvim/search.c b/src/nvim/search.c index fef27dba48..994a0794b0 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3256,7 +3256,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat const char *const key, Callback *const item_cb, const bool retmatchpos, list_T *const fmatchlist, const int max_matches) - FUNC_ATTR_NONNULL_ARG(2, 7) + FUNC_ATTR_NONNULL_ARG(2, 5, 7) { int len = tv_list_len(l); if (len == 0) { @@ -3546,80 +3546,33 @@ int fuzzy_match_str(char *const str, const char *const pat) /// Fuzzy match the position of string "pat" in string "str". /// @returns a dynamic array of matching positions. If there is no match, returns NULL. -garray_T *fuzzy_match_str_with_pos(const char *const str, char *const pat) +garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat) { + if (str == NULL || pat == NULL) { + return NULL; + } + garray_T *match_positions = xmalloc(sizeof(garray_T)); + ga_init(match_positions, sizeof(uint32_t), 10); - ga_init(match_positions, sizeof(int), 10); - if (str == NULL || pat == NULL) { + unsigned matches[MAX_FUZZY_MATCHES]; + int score = 0; + if (!fuzzy_match(str, pat, false, &score, matches, MAX_FUZZY_MATCHES) + || score == 0) { ga_clear(match_positions); + xfree(match_positions); return NULL; } - list_T *l = tv_list_alloc(1); - - typval_T tv_str = { - .v_type = VAR_STRING, - .vval.v_string = xstrdup(str), - }; - tv_list_append_tv(l, &tv_str); - - list_T *retlist = tv_list_alloc(3); - list_T *match_str_list = tv_list_alloc(1); - list_T *match_pos_list = tv_list_alloc(1); - list_T *match_score_list = tv_list_alloc(1); - - tv_list_append_list(retlist, match_str_list); - tv_list_append_list(retlist, match_pos_list); - tv_list_append_list(retlist, match_score_list); - - fuzzy_match_in_list(l, pat, false, NULL, NULL, true, retlist, 1); - - varnumber_T score = 0; - listitem_T *score_item = tv_list_find(retlist, 2); - if (score_item != NULL && TV_LIST_ITEM_TV(score_item)->v_type == VAR_LIST) { - list_T *score_list = TV_LIST_ITEM_TV(score_item)->vval.v_list; - if (tv_list_len(score_list) > 0) { - listitem_T *first_score_item = tv_list_first(score_list); - if (first_score_item != NULL && TV_LIST_ITEM_TV(first_score_item)->v_type == VAR_NUMBER) { - score = TV_LIST_ITEM_TV(first_score_item)->vval.v_number; - } - } - } - if (score == 0) { - goto cleanup; - } - - listitem_T *positions_item = tv_list_find(retlist, 1); - if (positions_item != NULL && TV_LIST_ITEM_TV(positions_item)->v_type == VAR_LIST) { - list_T *positions_outer_list = TV_LIST_ITEM_TV(positions_item)->vval.v_list; - if (tv_list_len(positions_outer_list) > 0) { - listitem_T *outer_li = tv_list_first(positions_outer_list); - if (outer_li != NULL && TV_LIST_ITEM_TV(outer_li)->v_type == VAR_LIST) { - list_T *positions_inner_list = TV_LIST_ITEM_TV(outer_li)->vval.v_list; - TV_LIST_ITER_CONST(positions_inner_list, li, { - if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { - varnumber_T pos = TV_LIST_ITEM_TV(li)->vval.v_number; - GA_APPEND(int, match_positions, (int)pos); - } - }); - } + + int j = 0; + for (const char *p = pat; *p != NUL; MB_PTR_ADV(p)) { + if (!ascii_iswhite(utf_ptr2char(p))) { + GA_APPEND(uint32_t, match_positions, matches[j]); + j++; } } - xfree(tv_str.vval.v_string); - tv_list_free(retlist); - tv_list_free(l); return match_positions; - -cleanup: - xfree(tv_str.vval.v_string); - tv_list_free(match_str_list); - tv_list_free(match_pos_list); - tv_list_free(match_score_list); - tv_list_free(retlist); - tv_list_free(l); - ga_clear(match_positions); - return NULL; } /// Copy a list of fuzzy matches into a string list after sorting the matches by -- cgit From 53b2817fd35be22c556b356c71ea0b0676c8e230 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Jun 2024 04:36:44 +0800 Subject: vim-patch:9.1.0480: fuzzy string matching executed when not needed Problem: fuzzy string matching executed when not needed Solution: when no leader is available, can skip fuzzy logic, so return early (glepnir) closes: vim/vim#14986 https://github.com/vim/vim/commit/1c296026627d7ac8195e803b4c2393c21ae659b4 Co-authored-by: glepnir --- src/nvim/popupmenu.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 50569fbc42..6330f37bad 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -442,23 +442,24 @@ static void pum_puts_with_attr(int col, char *text, int attr) { char *leader = ins_compl_leader(); - if ((win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI) - && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) { + if (leader == NULL || *leader == NUL + || (win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI) + && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) { grid_line_puts(col, text, -1, attr); return; } char *rt_leader = NULL; - if (leader != NULL && curwin->w_p_rl) { + if (curwin->w_p_rl) { rt_leader = reverse_text(leader); } char *match_leader = rt_leader != NULL ? rt_leader : leader; - size_t leader_len = match_leader ? strlen(match_leader) : 0; + size_t leader_len = strlen(match_leader); const bool in_fuzzy = (get_cot_flags() & COT_FUZZY) != 0; garray_T *ga = NULL; - if (match_leader != NULL && leader_len > 0 && in_fuzzy) { + if (in_fuzzy) { ga = fuzzy_match_str_with_pos(text, match_leader); } -- cgit From aacd6c440d21bea9dbe9588da076d3351f9b228c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 15 Jun 2024 04:47:53 +0800 Subject: vim-patch:9.1.0485: Matched text shouldn't be highlighted in "kind" and "menu" Problem: Matched text shouldn't be highlighted in "kind" and "menu". Solution: Pass hlf_T instead of the attribute. Fix indent. (zeertzjq) closes: vim/vim#14996 https://github.com/vim/vim/commit/afbe5359e981e5d19ad23c394aefe60395c3648e --- src/nvim/popupmenu.c | 34 ++++++++--------- test/functional/ui/popupmenu_spec.lua | 72 +++++++++++++++++------------------ test/old/testdir/test_popup.vim | 16 ++++---- 3 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 6330f37bad..1ef5aac1e9 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -438,14 +438,14 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } /// Displays text on the popup menu with specific attributes. -static void pum_puts_with_attr(int col, char *text, int attr) +static void pum_puts_with_attr(int col, char *text, hlf_T hlf) { char *leader = ins_compl_leader(); - if (leader == NULL || *leader == NUL + if (leader == NULL || *leader == NUL || (hlf != HLF_PSI && hlf != HLF_PNI) || (win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI) && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) { - grid_line_puts(col, text, -1, attr); + grid_line_puts(col, text, -1, win_hl_attr(curwin, (int)hlf)); return; } @@ -468,7 +468,7 @@ static void pum_puts_with_attr(int col, char *text, int attr) while (*ptr != NUL) { int char_len = utfc_ptr2len(ptr); int cells = utf_ptr2cells(ptr); - int new_attr = attr; + int new_attr = win_hl_attr(curwin, (int)hlf); if (ga != NULL) { // Handle fuzzy matching @@ -481,15 +481,13 @@ static void pum_puts_with_attr(int col, char *text, int attr) actual_char_pos++; } if (actual_char_pos == match_pos[0]) { - new_attr = win_hl_attr(curwin, (attr == win_hl_attr(curwin, HLF_PSI) - ? HLF_PMSI : HLF_PMNI)); + new_attr = win_hl_attr(curwin, hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI); break; } } } else if (!in_fuzzy && ptr < text + leader_len && strncmp(text, match_leader, leader_len) == 0) { - new_attr = win_hl_attr(curwin, (attr == win_hl_attr(curwin, HLF_PSI) - ? HLF_PMSI : HLF_PMNI)); + new_attr = win_hl_attr(curwin, hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI); } grid_line_puts(col, ptr, char_len, new_attr); @@ -517,11 +515,9 @@ void pum_redraw(void) int thumb_height = 1; int n; -#define HA(hlf) (win_hl_attr(curwin, (hlf))) - // "word" "kind" "extra text" - const int attrsNorm[3] = { HA(HLF_PNI), HA(HLF_PNK), HA(HLF_PNX) }; - const int attrsSel[3] = { HA(HLF_PSI), HA(HLF_PSK), HA(HLF_PSX) }; -#undef HA + // "word" "kind" "extra text" + const hlf_T hlfsNorm[3] = { HLF_PNI, HLF_PNK, HLF_PNX }; + const hlf_T hlfsSel[3] = { HLF_PSI, HLF_PSK, HLF_PSX }; int grid_width = pum_width; int col_off = 0; @@ -588,8 +584,9 @@ void pum_redraw(void) for (int i = 0; i < pum_height; i++) { int idx = i + pum_first; - const int *const attrs = (idx == pum_selected) ? attrsSel : attrsNorm; - int attr = attrs[0]; // start with "word" highlight + const hlf_T *const hlfs = (idx == pum_selected) ? hlfsSel : hlfsNorm; + hlf_T hlf = hlfs[0]; // start with "word" highlight + int attr = win_hl_attr(curwin, (int)hlf); grid_line_start(&pum_grid, row); @@ -611,7 +608,8 @@ void pum_redraw(void) int totwidth = 0; for (int round = 0; round < 3; round++) { - attr = attrs[round]; + hlf = hlfs[round]; + attr = win_hl_attr(curwin, (int)hlf); int width = 0; char *s = NULL; @@ -664,12 +662,12 @@ void pum_redraw(void) size++; } } - pum_puts_with_attr(grid_col - size + 1, rt, attr); + pum_puts_with_attr(grid_col - size + 1, rt, hlf); xfree(rt_start); xfree(st); grid_col -= width; } else { - pum_puts_with_attr(grid_col, st, attr); + pum_puts_with_attr(grid_col, st, hlf); xfree(st); grid_col += width; } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 0ce0fe4eab..323be3bd4d 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -4597,14 +4597,14 @@ describe('builtin popupmenu', function() endif return { \ 'words': [ - \ { 'word': 'foo',}, - \ { 'word': 'foobar',}, - \ { 'word': 'fooBaz',}, - \ { 'word': 'foobala',}, - \ { 'word': '你好',}, - \ { 'word': '你好吗',}, - \ { 'word': '你不好吗',}, - \ { 'word': '你可好吗',}, + \ { 'word': 'foo', 'kind': 'fookind' }, + \ { 'word': 'foobar', 'kind': 'fookind' }, + \ { 'word': 'fooBaz', 'kind': 'fookind' }, + \ { 'word': 'foobala', 'kind': 'fookind' }, + \ { 'word': '你好' }, + \ { 'word': '你好吗' }, + \ { 'word': '你不好吗' }, + \ { 'word': '你可好吗' }, \]} endfunc set omnifunc=Omni_test @@ -4615,14 +4615,14 @@ describe('builtin popupmenu', function() feed('i') local pum_start = [[ ^ | - {s:foo }{1: }| - {n:foobar }{1: }| - {n:fooBaz }{1: }| - {n:foobala }{1: }| - {n:你好 }{1: }| - {n:你好吗 }{1: }| - {n:你不好吗 }{1: }| - {n:你可好吗 }{1: }| + {s:foo fookind }{1: }| + {n:foobar fookind }{1: }| + {n:fooBaz fookind }{1: }| + {n:foobala fookind }{1: }| + {n:你好 }{1: }| + {n:你好吗 }{1: }| + {n:你不好吗 }{1: }| + {n:你可好吗 }{1: }| {1:~ }|*10 {2:-- }{5:match 1 of 8} | ]] @@ -4630,10 +4630,10 @@ describe('builtin popupmenu', function() feed('fo') screen:expect([[ fo^ | - {ms:fo}{s:o }{1: }| - {mn:fo}{n:obar }{1: }| - {mn:fo}{n:oBaz }{1: }| - {mn:fo}{n:obala }{1: }| + {ms:fo}{s:o fookind }{1: }| + {mn:fo}{n:obar fookind }{1: }| + {mn:fo}{n:oBaz fookind }{1: }| + {mn:fo}{n:obala fookind }{1: }| {1:~ }|*14 {2:-- }{5:match 1 of 8} | ]]) @@ -4664,24 +4664,24 @@ describe('builtin popupmenu', function() feed('S') screen:expect([[ ^ | - {1: }{s: oof}| - {1: }{n: raboof}| - {1: }{n: zaBoof}| - {1: }{n: alaboof}| - {1: }{n: 好你}| - {1: }{n: 吗好你}| - {1: }{n: 吗好不你}| - {1: }{n: 吗好可你}| + {1: }{s: dnikoof oof}| + {1: }{n: dnikoof raboof}| + {1: }{n: dnikoof zaBoof}| + {1: }{n: dnikoof alaboof}| + {1: }{n: 好你}| + {1: }{n: 吗好你}| + {1: }{n: 吗好不你}| + {1: }{n: 吗好可你}| {1: ~}|*10 {2:-- }{5:match 1 of 8} | ]]) feed('fo') screen:expect([[ ^ of| - {1: }{s: o}{ms:of}| - {1: }{n: rabo}{mn:of}| - {1: }{n: zaBo}{mn:of}| - {1: }{n: alabo}{mn:of}| + {1: }{s: dnikoof o}{ms:of}| + {1: }{n: dnikoof rabo}{mn:of}| + {1: }{n: dnikoof zaBo}{mn:of}| + {1: }{n: dnikoofalabo}{mn:of}| {1: ~}|*14 {2:-- }{5:match 1 of 8} | ]]) @@ -4694,10 +4694,10 @@ describe('builtin popupmenu', function() feed('fo') screen:expect([[ fo^ | - {ms:fo}{s:o }{1: }| - {mn:fo}{n:obar }{1: }| - {mn:fo}{n:oBaz }{1: }| - {mn:fo}{n:obala }{1: }| + {ms:fo}{s:o fookind }{1: }| + {mn:fo}{n:obar fookind }{1: }| + {mn:fo}{n:oBaz fookind }{1: }| + {mn:fo}{n:obala fookind }{1: }| {1:~ }|*14 {2:-- }{5:match 1 of 8} | ]]) diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index acebcae1dd..e370484b28 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1358,14 +1358,14 @@ func Test_pum_highlights_match() endif return { \ 'words': [ - \ { 'word': 'foo',}, - \ { 'word': 'foobar',}, - \ { 'word': 'fooBaz',}, - \ { 'word': 'foobala',}, - \ { 'word': '你好',}, - \ { 'word': '你好吗',}, - \ { 'word': '你不好吗',}, - \ { 'word': '你可好吗',}, + \ { 'word': 'foo', 'kind': 'fookind' }, + \ { 'word': 'foobar', 'kind': 'fookind' }, + \ { 'word': 'fooBaz', 'kind': 'fookind' }, + \ { 'word': 'foobala', 'kind': 'fookind' }, + \ { 'word': '你好' }, + \ { 'word': '你好吗' }, + \ { 'word': '你不好吗' }, + \ { 'word': '你可好吗' }, \]} endfunc set omnifunc=Omni_test -- cgit