From b64c2d763e8f16258cf649640b265443773dcd11 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 8 Apr 2025 13:32:34 +0800 Subject: [PATCH 1/5] vim-patch:9.1.1262: heap-buffer-overflow with narrow 'pummaxwidth' value Problem: heap-buffer-overflow occurs with narrow 'pummaxwidth' value (after v9.1.1250) Solution: test that st_end points after st pointer (Hirohito Higashi) closes: vim/vim#17005 https://github.com/vim/vim/commit/f13c8561544dad4f82b7f4f71041d35f55b5feaa Co-authored-by: Hirohito Higashi --- test/old/testdir/test_popup.vim | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 9ade648e5f..03e378237b 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -2076,6 +2076,69 @@ func Test_pum_maxwidth_multibyte() call StopVimInTerminal(buf) endfunc +func Test_pum_maxwidth_with_many_items() + CheckScreendump + + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, + \ #{word: "bar", menu: "barMenu", kind: "barKind"}, + \ #{word: "baz", menu: "bazMenu", kind: "bazKind"}, + \ ] + endfunc + set omnifunc=Omni_test + END + call writefile(lines, 'Xtest', 'D') + let buf = RunVimInTerminal('-S Xtest', {}) + call TermWait(buf) + + call term_sendkeys(buf, ":set pummaxwidth=20\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_01', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=19\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_02', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=18\") " display Ellipsis + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_03', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=16\") " display Ellipsis + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_04', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=15\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_05', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=12\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_06', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=10\") " display Ellipsis + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_07', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set pummaxwidth=1\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_08', {'rows': 8}) + call term_sendkeys(buf, "\") + + call StopVimInTerminal(buf) +endfunc + func Test_pum_clear_when_switch_tab_or_win() CheckScreendump From 1c723b2e6f51634c2e2f994f016b8e70ac50c955 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 8 Apr 2025 13:32:28 +0800 Subject: [PATCH 2/5] vim-patch:9.1.1284: not possible to configure pum truncation char Problem: not possible to configure the completion menu truncation character Solution: add the "trunc" suboption to the 'fillchars' setting to configure the truncation indicator (glepnir). closes: vim/vim#17006 https://github.com/vim/vim/commit/b87620466c6500fb37fd9be1016a27fa9626749a Co-authored-by: glepnir --- runtime/doc/options.txt | 17 +- runtime/lua/vim/_meta/options.lua | 17 +- runtime/optwin.vim | 4 +- src/nvim/buffer_defs.h | 1 + src/nvim/options.lua | 17 +- src/nvim/optionstr.c | 10 +- src/nvim/popupmenu.c | 33 +-- test/functional/ui/popupmenu_spec.lua | 399 ++++++++++++++++++++------ test/old/testdir/gen_opt_test.vim | 5 +- test/old/testdir/test_popup.vim | 146 ++++++---- 10 files changed, 464 insertions(+), 185 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c298924d3e..f55e7b80c5 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2615,8 +2615,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'fillchars'* *'fcs'* 'fillchars' 'fcs' string (default "") global or local to window |global-local| - Characters to fill the statuslines, vertical separators and special - lines in the window. + Characters to fill the statuslines, vertical separators, special + lines in the window and truncated text in the |ins-completion-menu|. It is a comma-separated list of items. Each item has a name, a colon and the value of that item: |E1511| @@ -2640,6 +2640,8 @@ A jump table for the options with a short description can be found at |Q_op|. msgsep ' ' message separator 'display' eob '~' empty lines at the end of a buffer lastline '@' 'display' contains lastline/truncate + trunc '>' truncated text in the + |ins-completion-menu|. Any one that is omitted will fall back to the default. @@ -2671,9 +2673,14 @@ A jump table for the options with a short description can be found at |Q_op|. vertright WinSeparator |hl-WinSeparator| verthoriz WinSeparator |hl-WinSeparator| fold Folded |hl-Folded| + foldopen FoldColumn |hl-FoldColumn| + foldclose FoldColumn |hl-FoldColumn| + foldsep FoldColumn |hl-FoldColumn| diff DiffDelete |hl-DiffDelete| eob EndOfBuffer |hl-EndOfBuffer| lastline NonText |hl-NonText| + trunc one of the many Popup menu highlighting groups like + |hl-PmenuSel| *'findfunc'* *'ffu'* *E1514* 'findfunc' 'ffu' string (default "") @@ -4656,8 +4663,10 @@ A jump table for the options with a short description can be found at |Q_op|. global Maximum width for the popup menu (|ins-completion-menu|). When zero, there is no maximum width limit, otherwise the popup menu will never be - wider than this value. Truncated text will be indicated by "..." at the - end. Takes precedence over 'pumwidth'. + wider than this value. Truncated text will be indicated by "trunc" + value of 'fillchars' option. + + This option takes precedence over 'pumwidth'. *'pumwidth'* *'pw'* 'pumwidth' 'pw' number (default 15) diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index a3a5750e78..17f64be99d 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2307,8 +2307,8 @@ vim.o.ft = vim.o.filetype vim.bo.filetype = vim.o.filetype vim.bo.ft = vim.bo.filetype ---- Characters to fill the statuslines, vertical separators and special ---- lines in the window. +--- Characters to fill the statuslines, vertical separators, special +--- lines in the window and truncated text in the `ins-completion-menu`. --- It is a comma-separated list of items. Each item has a name, a colon --- and the value of that item: `E1511` --- @@ -2332,6 +2332,8 @@ vim.bo.ft = vim.bo.filetype --- msgsep ' ' message separator 'display' --- eob '~' empty lines at the end of a buffer --- lastline '@' 'display' contains lastline/truncate +--- trunc '>' truncated text in the +--- `ins-completion-menu`. --- --- Any one that is omitted will fall back to the default. --- @@ -2366,9 +2368,14 @@ vim.bo.ft = vim.bo.filetype --- vertright WinSeparator `hl-WinSeparator` --- verthoriz WinSeparator `hl-WinSeparator` --- fold Folded `hl-Folded` +--- foldopen FoldColumn `hl-FoldColumn` +--- foldclose FoldColumn `hl-FoldColumn` +--- foldsep FoldColumn `hl-FoldColumn` --- diff DiffDelete `hl-DiffDelete` --- eob EndOfBuffer `hl-EndOfBuffer` --- lastline NonText `hl-NonText` +--- trunc one of the many Popup menu highlighting groups like +--- `hl-PmenuSel` --- --- @type string vim.o.fillchars = "" @@ -4838,8 +4845,10 @@ vim.go.ph = vim.go.pumheight --- Maximum width for the popup menu (`ins-completion-menu`). When zero, --- there is no maximum width limit, otherwise the popup menu will never be ---- wider than this value. Truncated text will be indicated by "..." at the ---- end. Takes precedence over 'pumwidth'. +--- wider than this value. Truncated text will be indicated by "trunc" +--- value of 'fillchars' option. +--- +--- This option takes precedence over 'pumwidth'. --- --- @type integer vim.o.pummaxwidth = 0 diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 79715460ac..9af414d909 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project -" Last Change: 2025 Apr 06 +" Last Change: 2025 Apr 07 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -333,7 +333,7 @@ call AddOption("sidescrolloff", gettext("minimal number of columns to keep call append("$", " \tset siso=" . &siso) call AddOption("display", gettext("include \"lastline\" to show the last line even if it doesn't fit\ninclude \"uhex\" to show unprintable characters as a hex number")) call OptionG("dy", &dy) -call AddOption("fillchars", gettext("characters to use for the status line, folds and filler lines")) +call AddOption("fillchars", gettext("characters to use for the status line, folds, diffs, buffer text, filler lines and truncation in the completion menu")) call OptionG("fcs", &fcs) call AddOption("cmdheight", gettext("number of lines used for the command-line")) call append("$", " \tset ch=" . &ch) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 1e2d8bbc93..3e56711499 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1056,6 +1056,7 @@ typedef struct { schar_T msgsep; schar_T eob; schar_T lastline; + schar_T trunc; } fcs_chars_T; /// Structure which contains all information that belongs to a window. diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 78616abaa1..fe971a39b7 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -3035,8 +3035,8 @@ local options = { defaults = '', deny_duplicates = true, desc = [=[ - Characters to fill the statuslines, vertical separators and special - lines in the window. + Characters to fill the statuslines, vertical separators, special + lines in the window and truncated text in the |ins-completion-menu|. It is a comma-separated list of items. Each item has a name, a colon and the value of that item: |E1511| @@ -3060,6 +3060,8 @@ local options = { msgsep ' ' message separator 'display' eob '~' empty lines at the end of a buffer lastline '@' 'display' contains lastline/truncate + trunc '>' truncated text in the + |ins-completion-menu|. Any one that is omitted will fall back to the default. @@ -3091,9 +3093,14 @@ local options = { vertright WinSeparator |hl-WinSeparator| verthoriz WinSeparator |hl-WinSeparator| fold Folded |hl-Folded| + foldopen FoldColumn |hl-FoldColumn| + foldclose FoldColumn |hl-FoldColumn| + foldsep FoldColumn |hl-FoldColumn| diff DiffDelete |hl-DiffDelete| eob EndOfBuffer |hl-EndOfBuffer| lastline NonText |hl-NonText| + trunc one of the many Popup menu highlighting groups like + |hl-PmenuSel| ]=], expand_cb = 'expand_set_chars_option', full_name = 'fillchars', @@ -6477,8 +6484,10 @@ local options = { desc = [=[ Maximum width for the popup menu (|ins-completion-menu|). When zero, there is no maximum width limit, otherwise the popup menu will never be - wider than this value. Truncated text will be indicated by "..." at the - end. Takes precedence over 'pumwidth'. + wider than this value. Truncated text will be indicated by "trunc" + value of 'fillchars' option. + + This option takes precedence over 'pumwidth'. ]=], full_name = 'pummaxwidth', scope = { 'global' }, diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 905656ccc9..dbf5851130 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2132,6 +2132,7 @@ static const struct chars_tab fcs_tab[] = { CHARSTAB_ENTRY(&fcs_chars.msgsep, "msgsep", " ", NULL), CHARSTAB_ENTRY(&fcs_chars.eob, "eob", "~", NULL), CHARSTAB_ENTRY(&fcs_chars.lastline, "lastline", "@", NULL), + CHARSTAB_ENTRY(&fcs_chars.trunc, "trunc", ">", NULL), }; static lcs_chars_T lcs_chars; @@ -2237,6 +2238,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo } const char *s = p + tab[i].name.size + 1; + if (what == kListchars && strcmp(tab[i].name.data, "multispace") == 0) { if (round == 0) { // Get length of lcs-multispace string in the first round @@ -2257,7 +2259,6 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo e_wrong_number_of_characters_for_field_str, tab[i].name.data); } - p = s; } else { int multispace_pos = 0; while (*s != NUL && *s != ',') { @@ -2266,14 +2267,14 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo lcs_chars.multispace[multispace_pos++] = c1; } } - p = s; } + p = s; break; } if (what == kListchars && strcmp(tab[i].name.data, "leadmultispace") == 0) { if (round == 0) { - // get length of lcs-leadmultispace string in first round + // Get length of lcs-leadmultispace string in first round last_lmultispace = p; lead_multispace_len = 0; while (*s != NUL && *s != ',') { @@ -2291,7 +2292,6 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo e_wrong_number_of_characters_for_field_str, tab[i].name.data); } - p = s; } else { int multispace_pos = 0; while (*s != NUL && *s != ',') { @@ -2300,8 +2300,8 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo lcs_chars.leadmultispace[multispace_pos++] = c1; } } - p = s; } + p = s; break; } diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 27b183a44a..3e6ae16293 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -577,6 +577,7 @@ void pum_redraw(void) int thumb_pos = 0; int thumb_height = 1; int n; + schar_T fcs_trunc = curwin->w_p_fcs_chars.trunc; // "word" "kind" "extra text" const hlf_T hlfsNorm[3] = { HLF_PNI, HLF_PNK, HLF_PNX }; @@ -644,8 +645,6 @@ void pum_redraw(void) thumb_pos = (pum_first * (pum_height - thumb_height) + scroll_range / 2) / scroll_range; } - const int ellipsis_width = 3; - for (int i = 0; i < pum_height; i++) { int idx = i + pum_first; const hlf_T *const hlfs = (idx == pum_selected) ? hlfsSel : hlfsNorm; @@ -668,7 +667,7 @@ void pum_redraw(void) // Do this 3 times and order from p_cia int grid_col = col_off; int totwidth = 0; - bool need_ellipsis = false; + bool need_fcs_trunc = false; int order[3]; int items_width_array[3] = { pum_base_width, pum_kind_width, pum_extra_width }; pum_align_order(order); @@ -721,9 +720,8 @@ void pum_redraw(void) char *rt = reverse_text(st); char *rt_start = rt; int cells = (int)mb_string2cells(rt); - if (p_pmw > ellipsis_width && pum_width == p_pmw - && grid_col - cells < col_off - pum_width) { - need_ellipsis = true; + if (pum_width == p_pmw && totwidth + 1 + cells >= pum_width) { + need_fcs_trunc = true; } if (grid_col - cells < col_off - pum_width) { @@ -751,9 +749,8 @@ void pum_redraw(void) grid_col -= width; } else { int cells = (int)mb_string2cells(st); - if (p_pmw > ellipsis_width && pum_width == p_pmw - && grid_col + cells > col_off + pum_width) { - need_ellipsis = true; + if (pum_width == p_pmw && totwidth + 1 + cells >= pum_width) { + need_fcs_trunc = true; } if (attrs == NULL) { @@ -820,21 +817,21 @@ void pum_redraw(void) if (pum_rl) { const int lcol = col_off - pum_width + 1; grid_line_fill(lcol, grid_col + 1, schar_from_ascii(' '), orig_attr); - if (need_ellipsis) { - bool over_wide = pum_width > ellipsis_width && linebuf_char[lcol + ellipsis_width] == NUL; - grid_line_fill(lcol, lcol + ellipsis_width, schar_from_ascii('.'), orig_attr); - if (over_wide) { - grid_line_put_schar(lcol + ellipsis_width, schar_from_ascii(' '), orig_attr); + if (need_fcs_trunc) { + linebuf_char[lcol] = fcs_trunc != NUL && fcs_trunc != schar_from_ascii('>') + ? fcs_trunc : schar_from_ascii('<'); + if (pum_width > 1 && linebuf_char[lcol + 1] == NUL) { + linebuf_char[lcol + 1] = schar_from_ascii(' '); } } } else { const int rcol = col_off + pum_width; grid_line_fill(grid_col, rcol, schar_from_ascii(' '), orig_attr); - if (need_ellipsis) { - if (pum_width > ellipsis_width && linebuf_char[rcol - ellipsis_width] == NUL) { - grid_line_put_schar(rcol - ellipsis_width - 1, schar_from_ascii(' '), orig_attr); + if (need_fcs_trunc) { + if (pum_width > 1 && linebuf_char[rcol - 1] == NUL) { + linebuf_char[rcol - 2] = schar_from_ascii(' '); } - grid_line_fill(rcol - ellipsis_width, rcol, schar_from_ascii('.'), orig_attr); + linebuf_char[rcol - 1] = fcs_trunc != NUL ? fcs_trunc : schar_from_ascii('>'); } } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index a96a45fedb..a0039ca103 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5626,7 +5626,7 @@ describe('builtin popupmenu', function() end) -- oldtest: Test_pum_maxwidth() - it('"pummaxwidth"', function() + it("'pummaxwidth'", function() screen:try_resize(60, 8) api.nvim_buf_set_lines(0, 0, -1, true, { '123456789_123456789_123456789_a', @@ -5683,8 +5683,8 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | ## grid 4 - {s: 1234567...}| - {n: 1234567...}| + {s: 123456789>}| + {n: 123456789>}| ]], float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100, 1, 3, 11 } }, }) @@ -5693,8 +5693,8 @@ describe('builtin popupmenu', function() 123456789_123456789_123456789_a | 123456789_123456789_123456789_b | 123456789_123456789_123456789_a^ | - {1:~ }{s: 1234567...}{1: }| - {1:~ }{n: 1234567...}{1: }| + {1:~ }{s: 123456789>}{1: }| + {1:~ }{n: 123456789>}{1: }| {1:~ }|*2 {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | ]]) @@ -5717,8 +5717,8 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | ## grid 4 - {s: 123456789_1234567...}| - {n: 123456789_1234567...}| + {s: 123456789_123456789>}| + {n: 123456789_123456789>}| ]], float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100, 1, 3, 11 } }, }) @@ -5727,8 +5727,8 @@ describe('builtin popupmenu', function() 123456789_123456789_123456789_a | 123456789_123456789_123456789_b | 123456789_123456789_123456789_a^ | - {1:~ }{s: 123456789_1234567...}{1: }| - {1:~ }{n: 123456789_1234567...}{1: }| + {1:~ }{s: 123456789_123456789>}{1: }| + {1:~ }{n: 123456789_123456789>}{1: }| {1:~ }|*2 {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | ]]) @@ -5751,8 +5751,8 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | ## grid 4 - {s: 12345...}| - {n: 12345...}| + {s: 1234567>}| + {n: 1234567>}| ]], float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100, 1, 3, 11 } }, }) @@ -5761,8 +5761,8 @@ describe('builtin popupmenu', function() 123456789_123456789_123456789_a | 123456789_123456789_123456789_b | 123456789_123456789_123456789_a^ | - {1:~ }{s: 12345...}{1: }| - {1:~ }{n: 12345...}{1: }| + {1:~ }{s: 1234567>}{1: }| + {1:~ }{n: 1234567>}{1: }| {1:~ }|*2 {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | ]]) @@ -5786,8 +5786,8 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- }{5:match 1 of 2} | ## grid 4 - {s: 12345...}| - {n: 12345...}| + {s: 1234567>}| + {n: 1234567>}| ]], float_pos = { [4] = { -1, 'NW', 2, 4, 11, false, 100, 1, 4, 11 } }, }) @@ -5797,8 +5797,8 @@ describe('builtin popupmenu', function() 123456789_123456789_123456789_b | 123456789_123456789_| 123456789_a^ | - {1:~ }{s: 12345...}{1: }| - {1:~ }{n: 12345...}{1: }| + {1:~ }{s: 1234567>}{1: }| + {1:~ }{n: 1234567>}{1: }| {1:~ }|*3 {2:-- }{5:match 1 of 2} | ]]) @@ -5810,18 +5810,36 @@ describe('builtin popupmenu', function() it("'pummaxwidth' with multibyte", function() screen:try_resize(60, 8) exec([[ + let g:change = 0 func Omni_test(findstart, base) if a:findstart return col(".") endif - return [ - \ #{word: "123456789_123456789_123456789_"}, - \ #{word: "一二三四五六七八九十"}, - \ #{word: "abcdefghij"}, - \ #{word: "上下左右"}, - \ ] + if g:change == 0 + return [ + \ #{word: "123456789_123456789_123456789_"}, + \ #{word: "一二三四五六七八九十"}, + \ #{word: "abcdefghij"}, + \ #{word: "上下左右"}, + \ ] + elseif g:change == 1 + return [ + \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, + \ #{word: "bar", menu: "barMenu", kind: "barKind"}, + \ #{word: "baz", menu: "bazMenu", kind: "bazKind"}, + \ ] + elseif g:change == 2 + return [ + \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, + \ #{word: "bar", menu: "fooMenu", kind: "一二三四"}, + \ #{word: "一二三四五", kind: "multi"}, + \ ] + else + return [#{word: "bar", menu: "fooMenu", kind: "一二三"}] + endif endfunc set omnifunc=Omni_test + set cot+=menuone ]]) feed('S') @@ -5871,9 +5889,9 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | ## grid 4 - {s:1234567...}| - {n:一二三 ...}| - {n:abcdefghij}| + {s:123456789>}| + {n:一二三四 >}| + {n:abcdefghi>}| {n:上下左右 }| ]], float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, @@ -5881,9 +5899,9 @@ describe('builtin popupmenu', function() else screen:expect([[ 123456789_123456789_123456789_^ | - {s:1234567...}{1: }| - {n:一二三 ...}{1: }| - {n:abcdefghij}{1: }| + {s:123456789>}{1: }| + {n:一二三四 >}{1: }| + {n:abcdefghi>}{1: }| {n:上下左右 }{1: }| {1:~ }|*2 {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | @@ -5905,9 +5923,9 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | ## grid 4 - {s:...7654321}| - {n:... 三二一}| - {n:jihgfedcba}| + {s:<987654321}| + {n:< 四三二一}| + {n:}| + {n: >}| + {n:a>}| + {n: >}| ]], float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, }) else screen:expect([[ 123456789_123456789_123456789_^ | - {s:12}{1: }| - {n:一}{1: }| - {n:ab}{1: }| - {n:上}{1: }| + {s:1>}{1: }| + {n: >}{1: }| + {n:a>}{1: }| + {n: >}{1: }| {1:~ }|*2 {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | ]]) end feed('') - end) - - it([['pummaxwidth' works with "kind" and "menu"]], function() - exec([[ - func Omni_test(findstart, base) - if a:findstart - return col(".") - endif - return [ - \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, - \ #{word: "bar", menu: "barMenu", kind: "barKind"}, - \ #{word: "baz", menu: "bazMenu", kind: "bazKind"}, - \ ] - endfunc - set omnifunc=Omni_test - ]]) + screen:try_resize(32, 20) command('set pummaxwidth=14') + command('let g:change=1') feed('S') if multigrid then screen:expect({ @@ -5990,24 +5994,155 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- }{5:match 1 of 3} | ## grid 4 - {s:foo fooKind...}| - {n:bar barKind...}| - {n:baz bazKind...}| + {s:foo fooKind f>}| + {n:bar barKind b>}| + {n:baz bazKind b>}| ]], float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, }) else screen:expect([[ foo^ | - {s:foo fooKind...}{1: }| - {n:bar barKind...}{1: }| - {n:baz bazKind...}{1: }| + {s:foo fooKind f>}{1: }| + {n:bar barKind b>}{1: }| + {n:baz bazKind b>}{1: }| {1:~ }|*15 {2:-- }{5:match 1 of 3} | ]]) end feed('') + -- Unicode Character U+2026 but one cell + command('set fcs+=trunc:…') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fooKind f…}| + {n:bar barKind b…}| + {n:baz bazKind b…}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooKind f…}{1: }| + {n:bar barKind b…}{1: }| + {n:baz bazKind b…}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + + command('let g:change=2') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fo…}| + {n:bar 一…}| + {n:一二三四五 mu…}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fo…}{1: }| + {n:bar 一…}{1: }| + {n:一二三四五 mu…}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + + command('set fcs&') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fo>}| + {n:bar 一>}| + {n:一二三四五 mu>}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fo>}{1: }| + {n:bar 一>}{1: }| + {n:一二三四五 mu>}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + + command('set fcs=trunc:_') + command('let g:change=1') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fooKind f_}| + {n:bar barKind b_}| + {n:baz bazKind b_}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooKind f_}{1: }| + {n:bar barKind b_}{1: }| + {n:baz bazKind b_}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + command('set fcs&') + command('set rightleft') feed('S') if multigrid then @@ -6022,26 +6157,120 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- }{5:match 1 of 3} | ## grid 4 - {s:...dniKoof oof}| - {n:...dniKrab rab}| - {n:...dniKzab zab}| + {s:') + + command('let g:change=2') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + ^ oof| + {1: ~}|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:') + + command('set fcs+=trunc:…') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + ^ oof| + {1: ~}|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:…of oof}| + {n:…一 rab}| + {n:…um 五四三二一}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 18, false, 100, 1, 1, 18 } }, + }) + else + screen:expect([[ + ^ oof| + {1: }{s:…of oof}| + {1: }{n:…一 rab}| + {1: }{n:…um 五四三二一}| + {1: ~}|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + + command('set fcs&') + command('let g:change=3') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + ^ rab| + {1: ~}|*18 + ## grid 3 + {2:-- The only match} | + ## grid 4 + {s:') command('set norightleft') - command('set pummaxwidth=13') + command('set pummaxwidth=4') + command('let g:change=2') feed('S') if multigrid then screen:expect({ @@ -6055,18 +6284,18 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- }{5:match 1 of 3} | ## grid 4 - {s:foo fooKin...}| - {n:bar barKin...}| - {n:baz bazKin...}| + {s:foo>}| + {n:bar>}| + {n:一 >}| ]], float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, }) else screen:expect([[ foo^ | - {s:foo fooKin...}{1: }| - {n:bar barKin...}{1: }| - {n:baz bazKin...}{1: }| + {s:foo>}{1: }| + {n:bar>}{1: }| + {n:一 >}{1: }| {1:~ }|*15 {2:-- }{5:match 1 of 3} | ]]) @@ -6087,18 +6316,18 @@ describe('builtin popupmenu', function() ## grid 3 {2:-- }{5:match 1 of 3} | ## grid 4 - {s:...niKoof oof}| - {n:...niKrab rab}| - {n:...niKzab zab}| + {s:3Gdd\"zp") call term_sendkeys(buf, ":set lines=10 columns=32\") + call TermWait(buf, 50) call term_sendkeys(buf, "GA\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_09', {'rows': 10, 'cols': 32}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_05', {'rows': 10, 'cols': 32}) call term_sendkeys(buf, "\3Gdd\"zp") call StopVimInTerminal(buf) @@ -2035,106 +2036,129 @@ func Test_pum_maxwidth_multibyte() CheckScreendump let lines =<< trim END + let g:change = 0 func Omni_test(findstart, base) if a:findstart return col(".") endif - return [ - \ #{word: "123456789_123456789_123456789_"}, - \ #{word: "一二三四五六七八九十"}, - \ #{word: "abcdefghij"}, - \ #{word: "上下左右"}, - \ ] + if g:change == 0 + return [ + \ #{word: "123456789_123456789_123456789_"}, + \ #{word: "一二三四五六七八九十"}, + \ #{word: "abcdefghij"}, + \ #{word: "上下左右"}, + \ ] + elseif g:change == 1 + return [ + \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, + \ #{word: "bar", menu: "barMenu", kind: "barKind"}, + \ #{word: "baz", menu: "bazMenu", kind: "bazKind"}, + \ ] + elseif g:change == 2 + return [ + \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, + \ #{word: "bar", menu: "fooMenu", kind: "一二三四"}, + \ #{word: "一二三四五", kind: "multi"}, + \ ] + else + return [#{word: "bar", menu: "fooMenu", kind: "一二三"}] + endif endfunc set omnifunc=Omni_test + set cot+=menuone END call writefile(lines, 'Xtest', 'D') let buf = RunVimInTerminal('-S Xtest', {}) call TermWait(buf) call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_05', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_06', {'rows': 8}) call term_sendkeys(buf, "\") call term_sendkeys(buf, ":set pummaxwidth=10\") call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_06', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_07', {'rows': 8}) call term_sendkeys(buf, "\") if has('rightleft') call term_sendkeys(buf, ":set rightleft\") call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_07', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_08', {'rows': 8}) call term_sendkeys(buf, "\:set norightleft\") endif call term_sendkeys(buf, ":set pummaxwidth=2\") call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_08', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_09', {'rows': 8}) call term_sendkeys(buf, "\") - call StopVimInTerminal(buf) -endfunc + call term_sendkeys(buf, ":set pummaxwidth=14\") + call term_sendkeys(buf, ":let g:change=1\S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_10', {'rows': 8}) + call term_sendkeys(buf, "\") -func Test_pum_maxwidth_with_many_items() - CheckScreendump - - let lines =<< trim END - func Omni_test(findstart, base) - if a:findstart - return col(".") - endif - return [ - \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, - \ #{word: "bar", menu: "barMenu", kind: "barKind"}, - \ #{word: "baz", menu: "bazMenu", kind: "bazKind"}, - \ ] - endfunc - set omnifunc=Omni_test - END - call writefile(lines, 'Xtest', 'D') - let buf = RunVimInTerminal('-S Xtest', {}) - call TermWait(buf) - - call term_sendkeys(buf, ":set pummaxwidth=20\") + " Unicode Character U+2026 but one cell + call term_sendkeys(buf, ":set fcs+=trunc:…\") call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_01', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_11', {'rows': 8}) call term_sendkeys(buf, "\") - call term_sendkeys(buf, ":set pummaxwidth=19\") - call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_02', {'rows': 8}) + call term_sendkeys(buf, ":let g:change=2\S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_12', {'rows': 8}) call term_sendkeys(buf, "\") - call term_sendkeys(buf, ":set pummaxwidth=18\") " display Ellipsis + call term_sendkeys(buf, ":set fcs&\") call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_03', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_13', {'rows': 8}) call term_sendkeys(buf, "\") - call term_sendkeys(buf, ":set pummaxwidth=16\") " display Ellipsis + call term_sendkeys(buf, ":set fcs=trunc:_\") + call term_sendkeys(buf, ":let g:change=1\") + call TermWait(buf, 50) call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_04', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_pum_maxwidth_14', {'rows': 8}) + call term_sendkeys(buf, "\") + call term_sendkeys(buf, ":set fcs&\") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_15', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":let g:change=2\") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_16', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set fcs+=trunc:…\") + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_17', {'rows': 8}) + call term_sendkeys(buf, "\") + + call term_sendkeys(buf, ":set fcs&\") + call term_sendkeys(buf, ":let g:change=3\") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_18', {'rows': 8}) + call term_sendkeys(buf, "\:set norightleft\") + endif + + call term_sendkeys(buf, ":set pummaxwidth=4\") + call term_sendkeys(buf, ":let g:change=2\") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_19', {'rows': 8}) call term_sendkeys(buf, "\") - call term_sendkeys(buf, ":set pummaxwidth=15\") - call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_05', {'rows': 8}) - call term_sendkeys(buf, "\") - - call term_sendkeys(buf, ":set pummaxwidth=12\") - call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_06', {'rows': 8}) - call term_sendkeys(buf, "\") - - call term_sendkeys(buf, ":set pummaxwidth=10\") " display Ellipsis - call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_07', {'rows': 8}) - call term_sendkeys(buf, "\") - - call term_sendkeys(buf, ":set pummaxwidth=1\") - call term_sendkeys(buf, "S\\") - call VerifyScreenDump(buf, 'Test_pum_maxwidth_with_many_items_08', {'rows': 8}) - call term_sendkeys(buf, "\") + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_20', {'rows': 8}) + call term_sendkeys(buf, "\:set norightleft\") + endif call StopVimInTerminal(buf) endfunc From 6e5671b00df14d2848b157beb42dae01205a6455 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 16 Apr 2025 07:29:44 +0800 Subject: [PATCH 3/5] vim-patch:9.1.1296: completion: incorrect truncation logic Problem: completion: incorrect truncation logic (after: v9.1.1284) Solution: replace string allocation with direct screen rendering and fixe RTL/LTR truncation calculations (glepnir) closes: vim/vim#17081 https://github.com/vim/vim/commit/d4dbf822dcb9fd95379bebab85def391e1179b21 Co-authored-by: glepnir --- runtime/doc/options.txt | 2 ++ runtime/lua/vim/_meta/options.lua | 2 ++ src/nvim/buffer_defs.h | 1 + src/nvim/options.lua | 2 ++ src/nvim/optionstr.c | 1 + src/nvim/popupmenu.c | 14 +++++++++----- test/functional/ui/popupmenu_spec.lua | 10 +++++----- test/old/testdir/test_popup.vim | 2 +- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index f55e7b80c5..4740b03c17 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2642,6 +2642,7 @@ A jump table for the options with a short description can be found at |Q_op|. lastline '@' 'display' contains lastline/truncate trunc '>' truncated text in the |ins-completion-menu|. + truncrl '<' same as "trunc' in 'rightleft' mode Any one that is omitted will fall back to the default. @@ -2681,6 +2682,7 @@ A jump table for the options with a short description can be found at |Q_op|. lastline NonText |hl-NonText| trunc one of the many Popup menu highlighting groups like |hl-PmenuSel| + truncrl same as "trunc" *'findfunc'* *'ffu'* *E1514* 'findfunc' 'ffu' string (default "") diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 17f64be99d..a73204c88b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2334,6 +2334,7 @@ vim.bo.ft = vim.bo.filetype --- lastline '@' 'display' contains lastline/truncate --- trunc '>' truncated text in the --- `ins-completion-menu`. +--- truncrl '<' same as "trunc' in 'rightleft' mode --- --- Any one that is omitted will fall back to the default. --- @@ -2376,6 +2377,7 @@ vim.bo.ft = vim.bo.filetype --- lastline NonText `hl-NonText` --- trunc one of the many Popup menu highlighting groups like --- `hl-PmenuSel` +--- truncrl same as "trunc" --- --- @type string vim.o.fillchars = "" diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3e56711499..cd7cf408d6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1057,6 +1057,7 @@ typedef struct { schar_T eob; schar_T lastline; schar_T trunc; + schar_T truncrl; } fcs_chars_T; /// Structure which contains all information that belongs to a window. diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fe971a39b7..504e5defa1 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -3062,6 +3062,7 @@ local options = { lastline '@' 'display' contains lastline/truncate trunc '>' truncated text in the |ins-completion-menu|. + truncrl '<' same as "trunc' in 'rightleft' mode Any one that is omitted will fall back to the default. @@ -3101,6 +3102,7 @@ local options = { lastline NonText |hl-NonText| trunc one of the many Popup menu highlighting groups like |hl-PmenuSel| + truncrl same as "trunc" ]=], expand_cb = 'expand_set_chars_option', full_name = 'fillchars', diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index dbf5851130..9d5b2f8ff8 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2133,6 +2133,7 @@ static const struct chars_tab fcs_tab[] = { CHARSTAB_ENTRY(&fcs_chars.eob, "eob", "~", NULL), CHARSTAB_ENTRY(&fcs_chars.lastline, "lastline", "@", NULL), CHARSTAB_ENTRY(&fcs_chars.trunc, "trunc", ">", NULL), + CHARSTAB_ENTRY(&fcs_chars.truncrl, "truncrl", "<", NULL), }; static lcs_chars_T lcs_chars; diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 3e6ae16293..7060367c13 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -577,7 +577,8 @@ void pum_redraw(void) int thumb_pos = 0; int thumb_height = 1; int n; - schar_T fcs_trunc = curwin->w_p_fcs_chars.trunc; + const schar_T fcs_trunc = pum_rl ? curwin->w_p_fcs_chars.truncrl + : curwin->w_p_fcs_chars.trunc; // "word" "kind" "extra text" const hlf_T hlfsNorm[3] = { HLF_PNI, HLF_PNK, HLF_PNX }; @@ -720,7 +721,9 @@ void pum_redraw(void) char *rt = reverse_text(st); char *rt_start = rt; int cells = (int)mb_string2cells(rt); - if (pum_width == p_pmw && totwidth + 1 + cells >= pum_width) { + if (pum_width == p_pmw + && (pum_width - totwidth < cells + || (j + 1 < 3 && pum_get_item(idx, order[j + 1]) != NULL))) { need_fcs_trunc = true; } @@ -749,7 +752,9 @@ void pum_redraw(void) grid_col -= width; } else { int cells = (int)mb_string2cells(st); - if (pum_width == p_pmw && totwidth + 1 + cells >= pum_width) { + if (pum_width == p_pmw + && (pum_width - totwidth < cells + || (j + 1 < 3 && pum_get_item(idx, order[j + 1]) != NULL))) { need_fcs_trunc = true; } @@ -818,8 +823,7 @@ void pum_redraw(void) const int lcol = col_off - pum_width + 1; grid_line_fill(lcol, grid_col + 1, schar_from_ascii(' '), orig_attr); if (need_fcs_trunc) { - linebuf_char[lcol] = fcs_trunc != NUL && fcs_trunc != schar_from_ascii('>') - ? fcs_trunc : schar_from_ascii('<'); + linebuf_char[lcol] = fcs_trunc != NUL ? fcs_trunc : schar_from_ascii('<'); if (pum_width > 1 && linebuf_char[lcol + 1] == NUL) { linebuf_char[lcol + 1] = schar_from_ascii(' '); } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index a0039ca103..e910cb7d8e 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5891,7 +5891,7 @@ describe('builtin popupmenu', function() ## grid 4 {s:123456789>}| {n:一二三四 >}| - {n:abcdefghi>}| + {n:abcdefghij}| {n:上下左右 }| ]], float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, @@ -5901,7 +5901,7 @@ describe('builtin popupmenu', function() 123456789_123456789_123456789_^ | {s:123456789>}{1: }| {n:一二三四 >}{1: }| - {n:abcdefghi>}{1: }| + {n:abcdefghij}{1: }| {n:上下左右 }{1: }| {1:~ }|*2 {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | @@ -5925,7 +5925,7 @@ describe('builtin popupmenu', function() ## grid 4 {s:<987654321}| {n:< 四三二一}| - {n:') - command('set fcs+=trunc:…') + command('set fcs+=truncrl:…') feed('S') if multigrid then screen:expect({ diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 4a3ca0d57c..bc727d1b0a 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -2132,7 +2132,7 @@ func Test_pum_maxwidth_multibyte() call VerifyScreenDump(buf, 'Test_pum_maxwidth_16', {'rows': 8}) call term_sendkeys(buf, "\") - call term_sendkeys(buf, ":set fcs+=trunc:…\") + call term_sendkeys(buf, ":set fcs+=truncrl:…\") call term_sendkeys(buf, "S\\") call VerifyScreenDump(buf, 'Test_pum_maxwidth_17', {'rows': 8}) call term_sendkeys(buf, "\") From 54d60550982812f32df76bb69dc4e44ac81a645e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 16 Apr 2025 07:38:57 +0800 Subject: [PATCH 4/5] vim-patch:9.1.1306: completion menu rendering can be improved Problem: Parts of the popup menu were rendered twice when the popup was at maximum width because the truncation flag was being set too liberally. Solution: Make the truncation condition more precise by only setting it when there's exactly one character of space remaining (glepnir). closes: vim/vim#17108 https://github.com/vim/vim/commit/32f2bb6e1e672f52d736579d9752473b14a5744d Co-authored-by: glepnir --- src/nvim/popupmenu.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 7060367c13..c0ebebaae2 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -688,6 +688,9 @@ void pum_redraw(void) int width = 0; char *s = NULL; p = pum_get_item(idx, item_type); + + const bool next_isempty = j + 1 < 3 && pum_get_item(idx, order[j + 1]) == NULL; + if (p != NULL) { for (;; MB_PTR_ADV(p)) { if (s == NULL) { @@ -721,12 +724,12 @@ void pum_redraw(void) char *rt = reverse_text(st); char *rt_start = rt; int cells = (int)mb_string2cells(rt); - if (pum_width == p_pmw - && (pum_width - totwidth < cells - || (j + 1 < 3 && pum_get_item(idx, order[j + 1]) != NULL))) { + int pad = next_isempty ? 0 : 2; + if (pum_width == p_pmw && pum_width - totwidth < cells + pad) { need_fcs_trunc = true; } + // only draw the text that fits if (grid_col - cells < col_off - pum_width) { do { cells -= utf_ptr2cells(rt); @@ -752,9 +755,8 @@ void pum_redraw(void) grid_col -= width; } else { int cells = (int)mb_string2cells(st); - if (pum_width == p_pmw - && (pum_width - totwidth < cells - || (j + 1 < 3 && pum_get_item(idx, order[j + 1]) != NULL))) { + int pad = next_isempty ? 0 : 2; + if (pum_width == p_pmw && pum_width - totwidth < cells + pad) { need_fcs_trunc = true; } @@ -796,10 +798,6 @@ void pum_redraw(void) n = order[j] == CPT_ABBR ? 1 : 0; } - bool next_isempty = false; - if (j + 1 < 3) { - next_isempty = pum_get_item(idx, order[j + 1]) == NULL; - } // Stop when there is nothing more to display. if ((j == 2) || (next_isempty && (j == 1 || (j == 0 && pum_get_item(idx, order[j + 2]) == NULL))) From afca5b564e766a2ba1049c52e6e4e04d82a09388 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 16 Apr 2025 07:49:16 +0800 Subject: [PATCH 5/5] vim-patch:9.1.1309: tests: no test for 'pummaxwidth' with non-truncated "kind" Problem: tests: no test for 'pummaxwidth' with non-truncated "kind". Solution: Add a test with "kind" and larger 'pummaxwidth' (zeertzjq). closes: vim/vim#17126 https://github.com/vim/vim/commit/031919c7ac66c4fcff3719b4f4158887b68d3315 --- test/functional/ui/popupmenu_spec.lua | 65 +++++++++++++++++++++++++++ test/old/testdir/test_popup.vim | 14 ++++++ 2 files changed, 79 insertions(+) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index e910cb7d8e..ec0d0c6266 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -6334,6 +6334,71 @@ describe('builtin popupmenu', function() end feed('') command('set norightleft') + + command('set pummaxwidth=16') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fooK>}| + {n:bar 一二>}| + {n:一二三四五 multi}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooK>}{1: }| + {n:bar 一二>}{1: }| + {n:一二三四五 multi}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + + command('set rightleft') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + ^ oof| + {1: ~}|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:') + command('set norightleft') end) it('does not crash when displayed in last column with rightleft #12032', function() diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index bc727d1b0a..8f65d768ce 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -2160,6 +2160,20 @@ func Test_pum_maxwidth_multibyte() call term_sendkeys(buf, "\:set norightleft\") endif + call term_sendkeys(buf, ":set pummaxwidth=16\") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_21', {'rows': 8}) + call term_sendkeys(buf, "\") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_22', {'rows': 8}) + call term_sendkeys(buf, "\:set norightleft\") + endif + call StopVimInTerminal(buf) endfunc