diff options
author | glepnir <glephunter@gmail.com> | 2025-03-10 04:19:01 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-09 13:19:01 -0700 |
commit | d9585bdcfb5bc85c5a2f6fa5d211295c9c1011ea (patch) | |
tree | 5ef22626c90141de5e2d4e189ce6ba7407d3640e | |
parent | 34a2bfdcc5ce03a1c8e2128cd1c3e7ab99755d12 (diff) | |
download | rneovim-d9585bdcfb5bc85c5a2f6fa5d211295c9c1011ea.tar.gz rneovim-d9585bdcfb5bc85c5a2f6fa5d211295c9c1011ea.tar.bz2 rneovim-d9585bdcfb5bc85c5a2f6fa5d211295c9c1011ea.zip |
fix(nvim__set_complete): pum preview info truncated during completion #32555
Problem:
1. The original info text is truncated in pum_set_preview_text.
2. There is an extra newline character at the end of the info text.
3. Incorrect usage of plines_win leads to errors in calculating the window height.
Solution:
1. Improved string handling in pum_preview_set_text to safely process line content without modifying the original text. Now, the preview info is correctly preserved while splitting lines.
2. Do not append a trailing newline to the preview buffer when one is present at the end.
3. Set w_widher_inner in advance; otherwise, plines_win cannot correctly calculate the wrapped line height.
-rw-r--r-- | src/nvim/popupmenu.c | 56 | ||||
-rw-r--r-- | test/functional/ui/popupmenu_spec.lua | 325 |
2 files changed, 265 insertions, 116 deletions
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 3860ba8f6a..b057cae08d 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -803,14 +803,25 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma Error err = ERROR_INIT; Arena arena = ARENA_EMPTY; Array replacement = ARRAY_DICT_INIT; - char *token = NULL; - char *line = os_strtok(info, "\n", &token); buf->b_p_ma = true; - while (line != NULL) { - ADD(replacement, STRING_OBJ(cstr_to_string(line))); + + // Iterate through the string line by line by temporarily replacing newlines with NUL + for (char *curr = info, *next; curr; curr = next ? next + 1 : NULL) { + if ((next = strchr(curr, '\n'))) { + *next = NUL; // Temporarily replace the newline with a string terminator + } + // Only skip if this is an empty line AND it's the last line + if (*curr == '\0' && !next) { + break; + } + + *max_width = MAX(*max_width, (int)mb_string2cells(curr)); + ADD(replacement, STRING_OBJ(cstr_to_string(curr))); (*lnum)++; - (*max_width) = MAX(*max_width, (int)mb_string2cells(line)); - line = os_strtok(NULL, "\n", &token); + + if (next) { + *next = '\n'; + } } int original_textlock = textlock; @@ -828,8 +839,23 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma buf->b_p_ma = false; } +/// Calculate the total height (in screen lines) of the first 'count' buffer lines in window 'wp'. +/// Takes line wrapping and other display factors into account. +/// +/// @param wp Window pointer +/// @param count Number of buffer lines to measure (1-based) +/// @return Total height in screen lines +static inline int pum_preview_win_height(win_T *wp, linenr_T count) +{ + int height = 0; + for (int i = 1; i <= count; i++) { + height += plines_win(wp, i, false); + } + return height; +} + /// adjust floating info preview window position -static void pum_adjust_info_position(win_T *wp, int height, int width) +static void pum_adjust_info_position(win_T *wp, int width) { int col = pum_col + pum_width + pum_scrollbar + 1; // TODO(glepnir): support config align border by using completepopup @@ -850,8 +876,10 @@ static void pum_adjust_info_position(win_T *wp, int height, int width) } // when pum_above is SW otherwise is NW wp->w_config.anchor = pum_above ? kFloatAnchorSouth : 0; - wp->w_config.row = pum_above ? pum_row + height : pum_row; - wp->w_config.height = MIN(Rows, height); + linenr_T count = wp->w_buffer->b_ml.ml_line_count; + wp->w_width_inner = wp->w_config.width; + wp->w_config.height = MIN(Rows, pum_preview_win_height(wp, count)); + wp->w_config.row = pum_above ? pum_row + wp->w_config.height : pum_row; wp->w_config.hide = false; win_config_float(wp, wp->w_config); } @@ -884,10 +912,7 @@ win_T *pum_set_info(int selected, char *info) RedrawingDisabled--; redraw_later(wp, UPD_NOT_VALID); - if (wp->w_p_wrap) { - lnum += plines_win(wp, lnum, true); - } - pum_adjust_info_position(wp, lnum, max_info_width); + pum_adjust_info_position(wp, max_info_width); unblock_autocmds(); return wp; } @@ -1063,11 +1088,8 @@ static bool pum_set_selected(int n, int repeat) curwin->w_cursor.col = 0; if (use_float) { - if (curwin->w_p_wrap) { - lnum += plines_win(curwin, lnum, true); - } // adjust floating window by actually height and max info text width - pum_adjust_info_position(curwin, lnum, max_info_width); + pum_adjust_info_position(curwin, max_info_width); } if ((curwin != curwin_save && win_valid(curwin_save)) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 3b52f62464..6f3b38ce83 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -1851,15 +1851,16 @@ describe('builtin popupmenu', function() end) describe('floating window preview popup', function() - it('pum popup preview', function() + before_each(function() --row must > 10 screen:try_resize(40, 11) exec([[ + let g:list = [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "looooooooooooooong"}] funct Omni_test(findstart, base) if a:findstart return col(".") - 1 endif - return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "looooooooooooooong"}] + return g:list endfunc set omnifunc=Omni_test set completeopt=menu,popup @@ -1886,28 +1887,42 @@ describe('builtin popupmenu', function() autocmd! Group autocmd CompleteChanged * call TsHl() endfunc + funct Append_multipe() + call extend(g:list, [#{word: "for .. ipairs", info: "```lua\nfor index, value in ipairs(t) do\n\t\nend\n```"}]) + endfunc ]]) + end) + + it('pum popup preview', function() feed('Gi<C-x><C-o>') --floating preview in right if multigrid then - screen:expect { + screen:expect({ grid = [[ - ## grid 1 - [2:----------------------------------------]|*10 - [3:----------------------------------------]| - ## grid 2 - one^ | - {1:~ }|*9 - ## grid 3 - {2:-- }{5:match 1 of 3} | - ## grid 4 - {n:1info}| - {n: }| - ## grid 5 - {s:one }| - {n:two }| - {n:looooooooooooooong }| - ]], + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + one^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {n:1info}| + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| + ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 40, + win = 1000, + }, + }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, [4] = { 1001, 'NW', 1, 1, 19, false, 50 }, @@ -1925,25 +1940,39 @@ describe('builtin popupmenu', function() [4] = { win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0, }, }, - } + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [4] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1001, + }, + }, + }) else - screen:expect { - grid = [[ + screen:expect([[ one^ | {s:one }{n:1info}{1: }| - {n:two }{1: }| + {n:two }{1: }| {n:looooooooooooooong }{1: }| {1:~ }|*6 {2:-- }{5:match 1 of 3} | - ]], - } + ]]) end -- delete one character make the pum width smaller than before @@ -1962,10 +1991,18 @@ describe('builtin popupmenu', function() {2:-- }{5:match 1 of 3} | ## grid 4 {n:1info}| - {n: }| ## grid 5 {s:one }| ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 40, + win = 1000, + }, + }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, [4] = { 1001, 'NW', 1, 1, 15, false, 50 }, @@ -1983,7 +2020,7 @@ describe('builtin popupmenu', function() [4] = { win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2008,15 +2045,12 @@ describe('builtin popupmenu', function() }, }) else - screen:expect({ - grid = [[ - on^ | - {s:one }{n:1info}{1: }| - {1:~ }{n: }{1: }| - {1:~ }|*7 - {2:-- }{5:match 1 of 3} | - ]], - }) + screen:expect([[ + on^ | + {s:one }{n:1info}{1: }| + {1:~ }|*8 + {2:-- }{5:match 1 of 3} | + ]]) end -- when back to original the preview float should be closed. @@ -2034,7 +2068,6 @@ describe('builtin popupmenu', function() {2:-- }{8:Back at original} | ## grid 4 (hidden) {n:1info}| - {n: }| ## grid 5 {n:one }| ]], @@ -2063,7 +2096,7 @@ describe('builtin popupmenu', function() [4] = { win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2097,9 +2130,11 @@ describe('builtin popupmenu', function() ]], }) end + feed('<C-E><ESC>') + end) - -- test nvim__complete_set_info - feed('<ESC>S<C-X><C-O><C-N><C-N>') + it('nvim__set_complete', function() + feed('S<C-X><C-O><C-N><C-N>') if multigrid then screen:expect({ grid = [[ @@ -2111,13 +2146,12 @@ describe('builtin popupmenu', function() {1:~ }|*9 ## grid 3 {2:-- }{5:match 3 of 3} | + ## grid 4 + {n:3info}| ## grid 5 {n:one }| {n:two }| {s:looooooooooooooong }| - ## grid 6 - {n:3info}| - {n: }| ]], win_pos = { [2] = { @@ -2130,7 +2164,7 @@ describe('builtin popupmenu', function() }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, - [6] = { 1002, 'NW', 1, 1, 19, false, 50 }, + [4] = { 1001, 'NW', 1, 1, 19, false, 50 }, }, win_viewport = { [2] = { @@ -2142,10 +2176,10 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [6] = { - win = 1002, + [4] = { + win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2160,30 +2194,29 @@ describe('builtin popupmenu', function() top = 0, win = 1000, }, - [6] = { + [4] = { bottom = 0, left = 0, right = 0, top = 0, - win = 1002, + win = 1001, }, }, }) else - screen:expect { - grid = [[ + screen:expect([[ looooooooooooooong^ | {n:one 3info}{1: }| - {n:two }{1: }| + {n:two }{1: }| {s:looooooooooooooong }{1: }| {1:~ }|*6 {2:-- }{5:match 3 of 3} | - ]], - } + ]]) end + feed('<C-E><ESC>') + end) - -- preview in left - feed('<ESC>cc') + it('popup preview place in left', function() insert(('test'):rep(5)) feed('i<C-x><C-o>') if multigrid then @@ -2193,17 +2226,16 @@ describe('builtin popupmenu', function() [2:----------------------------------------]|*10 [3:----------------------------------------]| ## grid 2 - itesttesttesttesttesone^t | + testtesttesttesttesone^t | {1:~ }|*9 ## grid 3 {2:-- }{5:match 1 of 3} | + ## grid 4 + {n:1info}| ## grid 5 {s: one }| {n: two }| {n: looooooooooooooong }| - ## grid 7 - {n:1info}| - {n: }| ]], win_pos = { [2] = { @@ -2215,8 +2247,8 @@ describe('builtin popupmenu', function() }, }, float_pos = { - [7] = { 1003, 'NW', 1, 1, 14, false, 50 }, - [5] = { -1, 'NW', 2, 1, 19, false, 100 }, + [5] = { -1, 'NW', 2, 1, 18, false, 100 }, + [4] = { 1001, 'NW', 1, 1, 13, false, 50 }, }, win_viewport = { [2] = { @@ -2224,14 +2256,14 @@ describe('builtin popupmenu', function() topline = 0, botline = 2, curline = 0, - curcol = 23, + curcol = 22, linecount = 1, sum_scroll_delta = 0, }, - [7] = { - win = 1003, + [4] = { + win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2246,30 +2278,29 @@ describe('builtin popupmenu', function() top = 0, win = 1000, }, - [7] = { + [4] = { bottom = 0, left = 0, right = 0, top = 0, - win = 1003, + win = 1001, }, }, }) else - screen:expect { - grid = [[ - itesttesttesttesttesone^t | - {1:~ }{n:1info}{s: one }{1: }| - {1:~ }{n: two }{1: }| - {1:~ }{n: looooooooooooooong }{1: }| + screen:expect([[ + testtesttesttesttesone^t | + {1:~ }{n:1info}{s: one }{1: }| + {1:~ }{n: two }{1: }| + {1:~ }{n: looooooooooooooong }{1: }| {1:~ }|*6 {2:-- }{5:match 1 of 3} | - ]], - } + ]]) end feed('<C-E><Esc>') + end) - -- works when scroll with treesitter highlight + it('works when scroll with treesitter highlight', function() command('call TestTs()') feed('S<C-x><C-o>') if multigrid then @@ -2283,17 +2314,16 @@ describe('builtin popupmenu', function() {1:~ }|*9 ## grid 3 {2:-- }{5:match 1 of 3} | - ## grid 5 - {s:one }| - {n:two }| - {n:looooooooooooooong }| - ## grid 8 + ## grid 4 {n:```lua }| {n:function test()}| {n: print('foo') }| {n:end }| {n:``` }| - {n: }| + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| ]], win_pos = { [2] = { @@ -2306,7 +2336,7 @@ describe('builtin popupmenu', function() }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, - [8] = { 1004, 'NW', 1, 1, 19, false, 50 }, + [4] = { 1001, 'NW', 1, 1, 19, false, 50 }, }, win_viewport = { [2] = { @@ -2318,10 +2348,10 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [8] = { - win = 1004, + [4] = { + win = 1001, topline = 0, - botline = 6, + botline = 5, curline = 0, curcol = 0, linecount = 5, @@ -2336,30 +2366,127 @@ describe('builtin popupmenu', function() top = 0, win = 1000, }, - [8] = { + [4] = { bottom = 0, left = 0, right = 0, top = 0, - win = 1004, + win = 1001, }, }, }) else + screen:expect([[ + one^ | + {s:one }{n:```lua }{1: }| + {n:two function test()}{1: }| + {n:looooooooooooooong print('foo') }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }|*4 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('<C-E><ESC>') + end) + + it('#avoid modified original info text', function() + screen:try_resize(40, 11) + command('call Append_multipe()') + feed('S<C-x><C-o><C-P><C-P>') + if multigrid then screen:expect({ grid = [[ - one^ | - {s:one }{n:```lua }{1: }| - {n:two function test()}{1: }| - {n:looooooooooooooong print('foo') }{1: }| - {1:~ }{n:end }{1: }| - {1:~ }{n:``` }{1: }| - {1:~ }{n: }{1: }| - {1:~ }|*3 - {2:-- }{5:match 1 of 3} | + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + for .. ipairs^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 4} | + ## grid 4 + {n:one }| + {n:two }| + {n:looooooooooooooong }| + {s:for .. ipairs }| + ## grid 5 + {n:```lua }| + {n:for index, value in }| + {n:ipairs(t) do }| + {n: }| + {n:end }| + {n:``` }| ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 40, + win = 1000, + }, + }, + float_pos = { + [5] = { 1001, 'NW', 1, 1, 19, false, 50 }, + [4] = { -1, 'NW', 2, 1, 0, false, 100 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 13, + linecount = 1, + sum_scroll_delta = 0, + }, + [5] = { + win = 1001, + topline = 0, + botline = 5, + curline = 0, + curcol = 0, + linecount = 5, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [5] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1001, + }, + }, }) + else + screen:expect([[ + for .. ipairs^ | + {n:one ```lua }{1: }| + {n:two for index, value in }{1: }| + {n:looooooooooooooong ipairs(t) do }{1: }| + {s:for .. ipairs }{n: }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 4} | + ]]) end + + feed('<C-N><C-N><C-N><C-N><C-N>') + if not multigrid then + screen:expect_unchanged() + end + feed('<C-E><ESC>') end) end) |