From 62da4e2949cc906102bd768cdd40b274623822b6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 28 Mar 2025 15:37:49 +0800 Subject: vim-patch:9.1.1250: cannot set the maximum popup menu width Problem: cannot set the maximum popup menu width (Lucas Mior) Solution: add the new global option value 'pummaxwidth' (glepnir) fixes: vim/vim#10901 closes: vim/vim#16943 https://github.com/vim/vim/commit/88d75934c3d5bc4c406343f106e1a61638abd3a7 Co-authored-by: glepnir --- runtime/doc/news.txt | 1 + runtime/doc/options.txt | 8 + runtime/lua/vim/_meta/options.lua | 11 ++ runtime/optwin.vim | 4 +- src/nvim/option_vars.h | 1 + src/nvim/options.lua | 15 ++ src/nvim/popupmenu.c | 109 ++++++++++++- test/functional/ui/popupmenu_spec.lua | 284 +++++++++++++++++++++++++++++++++- test/old/testdir/test_popup.vim | 77 +++++++++ 9 files changed, 507 insertions(+), 3 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index aa6af1ef55..5f47c474a7 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -79,6 +79,7 @@ LUA OPTIONS • 'diffopt' `inline:` configures diff highlighting for changes within a line. +• 'pummaxwidth' sets maximum width for the completion popup menu. PLUGINS diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ada4e250bc..bdc4f20fac 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4623,6 +4623,14 @@ A jump table for the options with a short description can be found at |Q_op|. Maximum number of items to show in the popup menu (|ins-completion-menu|). Zero means "use available screen space". + *'pummaxwidth'* *'pmw'* +'pummaxwidth' 'pmw' number (default 0) + 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'. + *'pumwidth'* *'pw'* 'pumwidth' 'pw' number (default 15) global diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 5f93e42c35..9ff4770e7c 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -4802,6 +4802,17 @@ vim.o.ph = vim.o.pumheight vim.go.pumheight = vim.o.pumheight 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'. +--- +--- @type integer +vim.o.pummaxwidth = 0 +vim.o.pmw = vim.o.pummaxwidth +vim.go.pummaxwidth = vim.o.pummaxwidth +vim.go.pmw = vim.go.pummaxwidth + --- Minimum width for the popup menu (`ins-completion-menu`). If the --- cursor column + 'pumwidth' exceeds screen width, the popup menu is --- nudged to fit on the screen. diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 56fbb954a7..41f9fc0373 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 Mar 07 +" Last Change: 2025 Mar 27 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -736,6 +736,8 @@ if has("insert_expand") call OptionG("ph", &ph) call AddOption("pumwidth", gettext("minimum width of the popup menu")) call OptionG("pw", &pw) + call AddOption("pumaxmwidth", gettext("maximum width of the popup menu")) + call OptionG("pmw", &pmw) call AddOption("completefunc", gettext("user defined function for Insert mode completion")) call append("$", "\t" .. s:local_to_buffer) call OptionL("cfu") diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 0b5d0a45b4..e624ab80ef 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -306,6 +306,7 @@ EXTERN char *p_csl; ///< 'completeslash' EXTERN OptInt p_pb; ///< 'pumblend' EXTERN OptInt p_ph; ///< 'pumheight' EXTERN OptInt p_pw; ///< 'pumwidth' +EXTERN OptInt p_pmw; ///< 'pummaxwidth' EXTERN char *p_com; ///< 'comments' EXTERN char *p_cpo; ///< 'cpoptions' EXTERN char *p_debug; ///< 'debug' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2f77d0ebb7..f261abf30e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -6425,6 +6425,21 @@ local options = { type = 'number', varname = 'p_ph', }, + { + abbreviation = 'pmw', + defaults = 0, + 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'. + ]=], + full_name = 'pummaxwidth', + scope = { 'global' }, + short_desc = N_('maximum width of the popup menu'), + type = 'number', + varname = 'p_pmw', + }, { abbreviation = 'pw', defaults = 15, diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index d26651fb84..75f1c93ed7 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -199,6 +199,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } int def_width = (int)p_pw; + if (p_pmw > 0 && def_width > p_pmw) { + def_width = (int)p_pmw; + } win_T *pvwin = NULL; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -307,6 +310,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_compute_size(); int max_width = pum_base_width; + if (p_pmw > 0 && max_width > p_pmw) { + max_width = (int)p_pmw; + } // if there are more items than room we need a scrollbar if (pum_height < size) { @@ -339,6 +345,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_width > content_width && pum_width > p_pw) { // Reduce width to fit item pum_width = MAX(content_width, (int)p_pw); + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } } else if (((cursor_col - min_col > p_pw || cursor_col - min_col > max_width) && !pum_rl) || (pum_rl && (cursor_col < max_col - p_pw @@ -365,6 +374,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_width < p_pw) { pum_width = (int)p_pw; + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } if (pum_rl) { if (pum_width > pum_col - min_col) { pum_width = pum_col - min_col; @@ -376,6 +388,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } } else if (pum_width > content_width && pum_width > p_pw) { pum_width = MAX(content_width, (int)p_pw); + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } } } } else if (max_col - min_col < def_width) { @@ -386,11 +401,17 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_col = min_col; } pum_width = max_col - min_col - 1; + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } } else { if (max_width > p_pw) { // truncate max_width = (int)p_pw; } + if (p_pmw > 0 && max_width > p_pmw) { + max_width = (int)p_pmw; + } if (pum_rl) { pum_col = min_col + max_width - 1; } else { @@ -611,6 +632,8 @@ 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; @@ -685,6 +708,9 @@ void pum_redraw(void) char *rt = reverse_text(st); char *rt_start = rt; int cells = vim_strsize(rt); + bool need_ellipsis = p_pmw > ellipsis_width + && pum_width == p_pmw + && grid_col - cells < col_off - pum_width; if (grid_col - cells < col_off - pum_width) { do { @@ -692,7 +718,41 @@ void pum_redraw(void) MB_PTR_ADV(rt); } while (grid_col - cells < col_off - pum_width); - if (grid_col - cells > col_off - pum_width) { + if (need_ellipsis) { + char *orig_rt = rt; + int used_cells = 0; + char *last_char = NULL; + while (*orig_rt != NUL) { + int char_cells = utf_ptr2cells(orig_rt); + if (used_cells + char_cells > ellipsis_width) { + break; + } + used_cells += char_cells; + MB_PTR_ADV(orig_rt); + last_char = orig_rt; + } + + if (last_char != NULL) { + int over_cell = 0; + if (used_cells < ellipsis_width) { + over_cell = ellipsis_width - used_cells; + MB_PTR_ADV(orig_rt); + last_char = orig_rt; + } + size_t kept_len = strlen(last_char); + char *new_str = xmalloc((size_t)ellipsis_width + (size_t)over_cell + + kept_len + 1); + memset(new_str, '.', (size_t)ellipsis_width); + if (over_cell > 0) { + memset(new_str + ellipsis_width, ' ', (size_t)over_cell); + } + memcpy(new_str + ellipsis_width + over_cell, last_char, kept_len); + new_str[(size_t)ellipsis_width + kept_len + (size_t)over_cell] = NUL; + char *old_rt = rt_start; + rt = rt_start = new_str; + xfree(old_rt); + } + } else if (grid_col - cells > col_off - pum_width) { // Most left character requires 2-cells but only 1 cell is available on // screen. Put a '<' on the left of the pum item. *(--rt) = '<'; @@ -710,6 +770,53 @@ void pum_redraw(void) xfree(st); grid_col -= width; } else { + size_t size = strlen(st); + int cells = (int)mb_string2cells_len(st, size); + bool need_ellipsis = p_pmw > ellipsis_width + && pum_width == p_pmw + && grid_col + cells > col_off + pum_width; + + // Add '...' indicator if truncated due to p_pmw + if (need_ellipsis) { + while (size > 0 && grid_col + cells > col_off + pum_width) { + size--; + size -= (size_t)utf_head_off(st, st + size); + cells -= utf_ptr2cells(st + size); + } + char *st_end = st + size; + int used_cells = 0; + char *last_char = NULL; + while (st_end > st) { + int char_cells = utf_ptr2cells(st_end); + if (used_cells + char_cells > ellipsis_width) { + break; + } + used_cells += char_cells; + MB_PTR_BACK(st, st_end); + last_char = st_end; + } + + if (last_char != NULL) { + int over_cell = 0; + if (used_cells < ellipsis_width) { + MB_PTR_BACK(st, st_end); + last_char = st_end; + over_cell = ellipsis_width - used_cells; + } + size_t kept_len = (size_t)(last_char - st); + char *new_str = xmalloc((size_t)ellipsis_width + (size_t)over_cell + + kept_len + 1); + memcpy(new_str, st, kept_len); + if (over_cell > 0) { + memset(new_str + kept_len, ' ', (size_t)over_cell); + } + memset(new_str + kept_len + over_cell, '.', (size_t)ellipsis_width); + new_str[kept_len + (size_t)ellipsis_width + (size_t)over_cell] = NUL; + xfree(st); + st = new_str; + } + } + if (attrs == NULL) { grid_line_puts(grid_col, st, -1, attr); } else { diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 0b7cff168b..7469269a1b 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5478,7 +5478,289 @@ describe('builtin popupmenu', function() end end) - it('does not crash when displayed in the last column with rightleft #12032', function() + -- oldtest: Test_pum_maxwidth() + it('"pummaxwidth"', function() + screen:try_resize(60, 8) + api.nvim_buf_set_lines(0, 0, -1, true, { + '123456789_123456789_123456789_a', + '123456789_123456789_123456789_b', + ' 123', + }) + feed('G"zyy') + feed('A') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 123456789_123456789_123456789_a }| + {n: 123456789_123456789_123456789_b }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 123456789_123456789_123456789_a }{1: }| + {1:~ }{n: 123456789_123456789_123456789_b }{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('3Gdd"zp') + + command('set pummaxwidth=10') + feed('GA') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 1234567...}| + {n: 1234567...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 1234567...}{1: }| + {1:~ }{n: 1234567...}{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('3Gdd"zp') + + command('set pummaxwidth=20') + feed('GA') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 123456789_1234567...}| + {n: 123456789_1234567...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 123456789_1234567...}{1: }| + {1:~ }{n: 123456789_1234567...}{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('3Gdd"zp') + + command('set pumwidth=20 pummaxwidth=8') + feed('GA') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 12345...}| + {n: 12345...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 12345...}{1: }| + {1:~ }{n: 12345...}{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('3Gdd"zp') + end) + + -- oldtest: Test_pum_maxwidth_multibyte() + it("'pummaxwidth' with multibyte", function() + screen:try_resize(60, 8) + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "123456789_123456789_123456789_"}, + \ #{word: "一二三四五六七八九十"}, + \ ] + endfunc + set omnifunc=Omni_test + ]]) + + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_^ | + {1:~ }|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ## grid 4 + {s:123456789_123456789_123456789_ }| + {n:一二三四五六七八九十 }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_^ | + {s:123456789_123456789_123456789_ }{1: }| + {n:一二三四五六七八九十 }{1: }| + {1:~ }|*4 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ]]) + end + feed('') + + command('set pummaxwidth=10') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_^ | + {1:~ }|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ## grid 4 + {s:1234567...}| + {n:一二三 ...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_^ | + {s:1234567...}{1: }| + {n:一二三 ...}{1: }| + {1:~ }|*4 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ]]) + end + feed('') + + command('set rightleft') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + ^ _987654321_987654321_987654321| + {1: ~}|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ## grid 4 + {s:...7654321}| + {n:... 三二一}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 50, false, 100 } }, + }) + else + screen:expect([[ + ^ _987654321_987654321_987654321| + {1: }{s:...7654321}| + {1: }{n:... 三二一}| + {1: ~}|*4 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ]]) + end + feed('') + command('set norightleft') + + command('set pummaxwidth=2') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_^ | + {1:~ }|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ## grid 4 + {s:12}| + {n:一}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_^ | + {s:12}{1: }| + {n:一}{1: }| + {1:~ }|*4 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + ]]) + end + feed('') + end) + + it('does not crash when displayed in last column with rightleft #12032', function() local col = 30 local items = { 'word', 'choice', 'text', 'thing' } local max_len = 0 diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 8f81db6213..631a81d5cc 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1992,4 +1992,81 @@ func Test_pum_complete_with_special_characters() call StopVimInTerminal(buf) endfunc +func Test_pum_maxwidth() + CheckScreendump + + let lines =<< trim END + 123456789_123456789_123456789_a + 123456789_123456789_123456789_b + 123 + END + call writefile(lines, 'Xtest', 'D') + let buf = RunVimInTerminal('Xtest', {}) + + call term_sendkeys(buf, "G\"zyy") + call term_sendkeys(buf, "A\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_01', {'rows': 8}) + call term_sendkeys(buf, "\3Gdd\"zp") + + call term_sendkeys(buf, ":set pummaxwidth=10\") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_02', {'rows': 8}) + call term_sendkeys(buf, "\3Gdd\"zp") + + call term_sendkeys(buf, ":set pummaxwidth=20\") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_03', {'rows': 8}) + call term_sendkeys(buf, "\3Gdd\"zp") + + call term_sendkeys(buf, ":set pumwidth=20 pummaxwidth=8\") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_04', {'rows': 8}) + call term_sendkeys(buf, "\3Gdd\"zp") + + call StopVimInTerminal(buf) +endfunc + +func Test_pum_maxwidth_multibyte() + CheckScreendump + + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "123456789_123456789_123456789_"}, + \ #{word: "一二三四五六七八九十"}, + \ ] + endfunc + set omnifunc=Omni_test + 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 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 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 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 term_sendkeys(buf, "\") + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 675ee057e06578e8d87ed2ea54ab8915caecdb0f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 29 Mar 2025 06:35:11 +0800 Subject: vim-patch:9.1.1255: missing test condition for 'pummaxwidth' setting Problem: missing test condition for 'pummaxwidth' setting, pummaxwidth not effective when width is 32 and height is 10 (after v9.1.1250) Solution: add missing comparison condition in pum_width() (glepnir) closes: vim/vim#16999 https://github.com/vim/vim/commit/532c5aec6fa8f0a3d743c7d1573d25d75dd36d5f Co-authored-by: glepnir --- runtime/optwin.vim | 4 ++-- src/nvim/popupmenu.c | 2 ++ test/functional/ui/popupmenu_spec.lua | 36 +++++++++++++++++++++++++++++++++++ test/old/testdir/test_popup.vim | 5 +++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 41f9fc0373..ce4590f01b 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 Mar 27 +" Last Change: 2025 Mar 28 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -736,7 +736,7 @@ if has("insert_expand") call OptionG("ph", &ph) call AddOption("pumwidth", gettext("minimum width of the popup menu")) call OptionG("pw", &pw) - call AddOption("pumaxmwidth", gettext("maximum width of the popup menu")) + call AddOption("pummaxwidth", gettext("maximum width of the popup menu")) call OptionG("pmw", &pmw) call AddOption("completefunc", gettext("user defined function for Insert mode completion")) call append("$", "\t" .. s:local_to_buffer) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 75f1c93ed7..4ce4f9632f 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -391,6 +391,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (p_pmw > 0 && pum_width > p_pmw) { pum_width = (int)p_pmw; } + } else if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; } } } else if (max_col - min_col < def_width) { diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 7469269a1b..d82c24bb83 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5621,6 +5621,42 @@ describe('builtin popupmenu', function() ]]) end feed('3Gdd"zp') + + screen:try_resize(32, 10) + feed('GA') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*9 + [3:--------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_| + 123456789_a^ | + {1:~ }|*5 + ## grid 3 + {2:-- }{5:match 1 of 2} | + ## grid 4 + {s: 12345...}| + {n: 12345...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 4, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_| + 123456789_a^ | + {1:~ }{s: 12345...}{1: }| + {1:~ }{n: 12345...}{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 2} | + ]]) + end + feed('3Gdd"zp') end) -- oldtest: Test_pum_maxwidth_multibyte() diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 631a81d5cc..94e437ee93 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -2023,6 +2023,11 @@ func Test_pum_maxwidth() call VerifyScreenDump(buf, 'Test_pum_maxwidth_04', {'rows': 8}) call term_sendkeys(buf, "\3Gdd\"zp") + call term_sendkeys(buf, ":set lines=10 columns=32\") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_09', {'rows': 10, 'cols': 32}) + call term_sendkeys(buf, "\3Gdd\"zp") + call StopVimInTerminal(buf) endfunc -- cgit From 686e7aca40b850789af45ec9a97905518654b49e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 28 Mar 2025 21:18:30 +0800 Subject: fix(pum): simplify 'pummaxwidth' truncation and avoid crash --- src/nvim/popupmenu.c | 114 +++++++------------------- test/functional/ui/popupmenu_spec.lua | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 86 deletions(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 4ce4f9632f..8ca4b28f3f 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -658,6 +658,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; int order[3]; int items_width_array[3] = { pum_base_width, pum_kind_width, pum_extra_width }; pum_align_order(order); @@ -710,9 +711,10 @@ void pum_redraw(void) char *rt = reverse_text(st); char *rt_start = rt; int cells = vim_strsize(rt); - bool need_ellipsis = p_pmw > ellipsis_width - && pum_width == p_pmw - && grid_col - cells < col_off - pum_width; + if (p_pmw > ellipsis_width && pum_width == p_pmw + && grid_col - cells < col_off - pum_width) { + need_ellipsis = true; + } if (grid_col - cells < col_off - pum_width) { do { @@ -720,41 +722,7 @@ void pum_redraw(void) MB_PTR_ADV(rt); } while (grid_col - cells < col_off - pum_width); - if (need_ellipsis) { - char *orig_rt = rt; - int used_cells = 0; - char *last_char = NULL; - while (*orig_rt != NUL) { - int char_cells = utf_ptr2cells(orig_rt); - if (used_cells + char_cells > ellipsis_width) { - break; - } - used_cells += char_cells; - MB_PTR_ADV(orig_rt); - last_char = orig_rt; - } - - if (last_char != NULL) { - int over_cell = 0; - if (used_cells < ellipsis_width) { - over_cell = ellipsis_width - used_cells; - MB_PTR_ADV(orig_rt); - last_char = orig_rt; - } - size_t kept_len = strlen(last_char); - char *new_str = xmalloc((size_t)ellipsis_width + (size_t)over_cell - + kept_len + 1); - memset(new_str, '.', (size_t)ellipsis_width); - if (over_cell > 0) { - memset(new_str + ellipsis_width, ' ', (size_t)over_cell); - } - memcpy(new_str + ellipsis_width + over_cell, last_char, kept_len); - new_str[(size_t)ellipsis_width + kept_len + (size_t)over_cell] = NUL; - char *old_rt = rt_start; - rt = rt_start = new_str; - xfree(old_rt); - } - } else if (grid_col - cells > col_off - pum_width) { + if (grid_col - cells > col_off - pum_width) { // Most left character requires 2-cells but only 1 cell is available on // screen. Put a '<' on the left of the pum item. *(--rt) = '<'; @@ -772,57 +740,16 @@ void pum_redraw(void) xfree(st); grid_col -= width; } else { - size_t size = strlen(st); - int cells = (int)mb_string2cells_len(st, size); - bool need_ellipsis = p_pmw > ellipsis_width - && pum_width == p_pmw - && grid_col + cells > col_off + pum_width; - - // Add '...' indicator if truncated due to p_pmw - if (need_ellipsis) { - while (size > 0 && grid_col + cells > col_off + pum_width) { - size--; - size -= (size_t)utf_head_off(st, st + size); - cells -= utf_ptr2cells(st + size); - } - char *st_end = st + size; - int used_cells = 0; - char *last_char = NULL; - while (st_end > st) { - int char_cells = utf_ptr2cells(st_end); - if (used_cells + char_cells > ellipsis_width) { - break; - } - used_cells += char_cells; - MB_PTR_BACK(st, st_end); - last_char = st_end; - } - - if (last_char != NULL) { - int over_cell = 0; - if (used_cells < ellipsis_width) { - MB_PTR_BACK(st, st_end); - last_char = st_end; - over_cell = ellipsis_width - used_cells; - } - size_t kept_len = (size_t)(last_char - st); - char *new_str = xmalloc((size_t)ellipsis_width + (size_t)over_cell - + kept_len + 1); - memcpy(new_str, st, kept_len); - if (over_cell > 0) { - memset(new_str + kept_len, ' ', (size_t)over_cell); - } - memset(new_str + kept_len + over_cell, '.', (size_t)ellipsis_width); - new_str[kept_len + (size_t)ellipsis_width + (size_t)over_cell] = NUL; - xfree(st); - st = new_str; - } + 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 (attrs == NULL) { grid_line_puts(grid_col, st, -1, attr); } else { - pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs); + pum_grid_puts_with_attrs(grid_col, cells, st, -1, attrs); } xfree(st); @@ -881,9 +808,24 @@ void pum_redraw(void) } if (pum_rl) { - grid_line_fill(col_off - pum_width + 1, grid_col + 1, schar_from_ascii(' '), orig_attr); + 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); + } + } } else { - grid_line_fill(grid_col, col_off + pum_width, schar_from_ascii(' '), orig_attr); + 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); + } + grid_line_fill(rcol - ellipsis_width, rcol, schar_from_ascii('.'), orig_attr); + } } if (pum_scrollbar > 0) { diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index d82c24bb83..6404feb9cd 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5796,6 +5796,152 @@ describe('builtin popupmenu', function() 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 + ]]) + + command('set pummaxwidth=14') + 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...}| + {n:bar barKind...}| + {n:baz bazKind...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooKind...}{1: }| + {n:bar barKind...}{1: }| + {n:baz bazKind...}{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:...dniKoof oof}| + {n:...dniKrab rab}| + {n:...dniKzab zab}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 18, false, 100 } }, + }) + else + screen:expect([[ + ^ oof| + {1: }{s:...dniKoof oof}| + {1: }{n:...dniKrab rab}| + {1: }{n:...dniKzab zab}| + {1: ~}|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + command('set norightleft') + + command('set pummaxwidth=13') + 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 fooKin...}| + {n:bar barKin...}| + {n:baz bazKin...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooKin...}{1: }| + {n:bar barKin...}{1: }| + {n:baz bazKin...}{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:...niKoof oof}| + {n:...niKrab rab}| + {n:...niKzab zab}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 19, false, 100 } }, + }) + else + screen:expect([[ + ^ oof| + {1: }{s:...niKoof oof}| + {1: }{n:...niKrab rab}| + {1: }{n:...niKzab zab}| + {1: ~}|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('') + command('set norightleft') + end) + it('does not crash when displayed in last column with rightleft #12032', function() local col = 30 local items = { 'word', 'choice', 'text', 'thing' } -- cgit From 52b19e012474c19601a491bf0a0ab6ba15cfae8d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 29 Mar 2025 20:12:12 +0800 Subject: vim-patch:9.1.1257: Mixing vim_strsize() with mb_ptr2cells() in pum_redraw() Problem: Mixing vim_strsize() with mb_ptr2cells() in pum_redraw(). Solution: Change vim_strsize() to mb_string2cells() (zeertzjq). Since vim_strsize() uses ptr2cells() for the cell width of each char, it is strange to mix it with mb_ptr2cells(), which is used both just below and in pum_screen_puts_with_attr(), and screen_puts_len() also uses something similar. Meanwhile mb_string2cells() uses mb_ptr2cells() for the cell width of each char. Note that the vim_strsize() and mb_string2cells() actually return the same value here, as the transstr() above makes sure the string only contains printable chars, and ptr2cells() and mb_ptr2cells() only return different values for unprintable chars. closes: vim/vim#17003 https://github.com/vim/vim/commit/90e52490b39f0052fb5313d67728eff77191aaae --- src/nvim/popupmenu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 8ca4b28f3f..2e5d08e5ae 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -710,7 +710,7 @@ void pum_redraw(void) if (pum_rl) { char *rt = reverse_text(st); char *rt_start = rt; - int cells = vim_strsize(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; -- cgit