commit 922816877febf397fe854f01d8013a510d73f1d2
parent 47aef025a7079f35234157e1080c997c4e90a91e
Author: glepnir <glephunter@gmail.com>
Date: Sat, 27 Dec 2025 21:56:34 +0800
fix(pum): hide info floating window when insufficient space (#37107)
fix(pum): hide info window when insufficient space
Problem:
1. Info window was displayed even with insufficient space.
2. Tab characters counted as single cells.
Solution:
1. Hide window when space < 10 columns. Will be configurable
via completepopup width option in the future.
2. Use win_linetabsize over mb_string2cells.
Diffstat:
2 files changed, 59 insertions(+), 12 deletions(-)
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
@@ -910,15 +910,16 @@ void pum_redraw(void)
/// Set the informational text in the preview buffer when the completion
/// item does not include a dedicated preview or popup window.
///
-/// @param[in] buf Buffer where the text will be set.
+/// @param[in] win Window containing buffer where the text will be set.
/// @param[in] info Informational text to display in the preview buffer.
/// @param[in] lnum Where to start the text. Incremented for each added line.
/// @param[out] max_width Maximum width of the displayed text.
-static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *max_width)
+static void pum_preview_set_text(win_T *win, char *info, linenr_T *lnum, int *max_width)
{
Error err = ERROR_INIT;
Arena arena = ARENA_EMPTY;
Array replacement = ARRAY_DICT_INIT;
+ buf_T *buf = win->w_buffer;
buf->b_p_ma = true;
// Iterate through the string line by line by temporarily replacing newlines with NUL
@@ -931,7 +932,7 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma
break;
}
- *max_width = MAX(*max_width, (int)mb_string2cells(curr));
+ *max_width = MAX(*max_width, win_linetabsize(win, 0, curr, MAXCOL));
ADD(replacement, STRING_OBJ(cstr_to_string(curr)));
(*lnum)++;
@@ -941,9 +942,7 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma
}
int original_textlock = textlock;
- if (textlock > 0) {
- textlock = 0;
- }
+ textlock = 0;
nvim_buf_set_lines(0, buf->handle, 0, -1, false, replacement, &arena, &err);
textlock = original_textlock;
if (ERROR_SET(&err)) {
@@ -956,7 +955,7 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma
}
/// adjust floating info preview window position
-static void pum_adjust_info_position(win_T *wp, int width)
+static bool pum_adjust_info_position(win_T *wp, int width)
{
int border_width = pum_border_width();
int col = pum_col + pum_width + 1 + MAX(border_width, pum_scrollbar);
@@ -965,6 +964,14 @@ static void pum_adjust_info_position(win_T *wp, int width)
int right_extra = Columns - col;
int left_extra = pum_col - 2;
+ int max_extra = MAX(right_extra, left_extra);
+ // Close info window if there's insufficient space
+ // TODO(glepnir): Replace the hardcoded value (10) with values from the 'completepopup' width/height options.
+ if (max_extra < 10) {
+ wp->w_config.hide = true;
+ return false;
+ }
+
if (right_extra > width) { // place in right
wp->w_config.width = width;
wp->w_config.col = col - 1;
@@ -973,7 +980,7 @@ static void pum_adjust_info_position(win_T *wp, int width)
wp->w_config.col = pum_col - wp->w_config.width - 1;
} else { // either width is enough just use the biggest one.
const bool place_in_right = right_extra > left_extra;
- wp->w_config.width = place_in_right ? right_extra : left_extra;
+ wp->w_config.width = max_extra;
wp->w_config.col = place_in_right ? col - 1 : pum_col - wp->w_config.width - 1;
}
// when pum_above is SW otherwise is NW
@@ -984,6 +991,7 @@ static void pum_adjust_info_position(win_T *wp, int width)
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);
+ return true;
}
/// Used for nvim__complete_set
@@ -1010,12 +1018,14 @@ win_T *pum_set_info(int selected, char *info)
}
linenr_T lnum = 0;
int max_info_width = 0;
- pum_preview_set_text(wp->w_buffer, info, &lnum, &max_info_width);
+ pum_preview_set_text(wp, info, &lnum, &max_info_width);
no_u_sync--;
RedrawingDisabled--;
redraw_later(wp, UPD_NOT_VALID);
- pum_adjust_info_position(wp, max_info_width);
+ if (!pum_adjust_info_position(wp, max_info_width)) {
+ wp = NULL;
+ }
unblock_autocmds();
return wp;
}
@@ -1167,7 +1177,7 @@ static bool pum_set_selected(int n, int repeat)
if (res == OK) {
linenr_T lnum = 0;
int max_info_width = 0;
- pum_preview_set_text(curbuf, pum_array[pum_selected].pum_info, &lnum, &max_info_width);
+ pum_preview_set_text(curwin, pum_array[pum_selected].pum_info, &lnum, &max_info_width);
// Increase the height of the preview window to show the
// text, but no more than 'previewheight' lines.
if (repeat == 0 && !use_float) {
@@ -1191,7 +1201,9 @@ static bool pum_set_selected(int n, int repeat)
if (use_float) {
// adjust floating window by actually height and max info text width
- pum_adjust_info_position(curwin, max_info_width);
+ if (!pum_adjust_info_position(curwin, max_info_width) && win_valid(curwin_save)) {
+ win_enter(curwin_save, false);
+ }
}
if ((curwin != curwin_save && win_valid(curwin_save))
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
@@ -2238,6 +2238,41 @@ describe('builtin popupmenu', function()
feed('S<C-x><C-o><C-N>')
eq(1, n.eval([[len(uniq(copy(g:bufnrs))) == 1]]))
end)
+
+ it('handles tabs in info width calculation', function()
+ screen:try_resize(50, 11)
+ command([[
+ set cot+=menuone
+ let g:list = [#{word: 'class', info: "\tClassName() = default;"}]
+ ]])
+ feed('S<C-x><C-o>')
+ local info = fn.complete_info()
+ eq(30, api.nvim_win_get_width(info.preview_winid))
+ feed('<ESC>')
+ exec([[
+ setlocal tabstop=1
+ autocmd ModeChanged *:i ++once call complete(1, [#{word: 'a'}])
+ \| call nvim__complete_set(0, #{info: "\tfloob\tfloob"})
+ ]])
+ feed('i')
+ info = fn.complete_info()
+ eq(21, api.nvim_win_get_width(info.preview_winid))
+ if not multigrid then
+ screen:expect([[
+ a^s |
+ {12:a }{n: floob floob}{1: }|
+ {1:~ }|*8
+ {5:-- INSERT --} |
+ ]])
+ end
+ end)
+
+ it('hide info window when insufficient space', function()
+ screen:try_resize(12, 11)
+ feed('S<C-x><C-o>')
+ local info = fn.complete_info()
+ eq(true, api.nvim_win_get_config(info.preview_winid).hide)
+ end)
end)
it('with vsplits', function()