feat(ui): overlay scrollbar on 'pumborder' #36273

Problem: When pumborder is set, the scrollbar still occupies
a column on the screen, wasting a 1 column of space.

Solution: Render the scrollbar on the right/left (rl mode) side
of the border when pumborder is set.
This commit is contained in:
glepnir
2025-10-24 06:44:02 +08:00
committed by GitHub
parent 4129fa5bac
commit 07d0da64ed
3 changed files with 97 additions and 54 deletions

View File

@@ -175,7 +175,6 @@ static const char *highlight_init_both[] = {
"default link PmenuKindSel PmenuSel", "default link PmenuKindSel PmenuSel",
"default link PmenuSbar Pmenu", "default link PmenuSbar Pmenu",
"default link PmenuBorder Pmenu", "default link PmenuBorder Pmenu",
"default link PmenuShadow FloatShadow",
"default link PmenuShadowThrough FloatShadowThrough", "default link PmenuShadowThrough FloatShadowThrough",
"default link PreInsert Added", "default link PreInsert Added",
"default link ComplMatchIns NONE", "default link ComplMatchIns NONE",
@@ -382,6 +381,7 @@ static const char *highlight_init_light[] = {
"OkMsg guifg=NvimDarkGreen ctermfg=2", "OkMsg guifg=NvimDarkGreen ctermfg=2",
"Pmenu guibg=NvimLightGrey3 cterm=reverse", "Pmenu guibg=NvimLightGrey3 cterm=reverse",
"PmenuThumb guibg=NvimLightGrey4", "PmenuThumb guibg=NvimLightGrey4",
"PmenuShadow guibg=NvimLightGrey4 ctermbg=0 blend=100",
"Question guifg=NvimDarkCyan ctermfg=6", "Question guifg=NvimDarkCyan ctermfg=6",
"QuickFixLine guifg=NvimDarkCyan ctermfg=6", "QuickFixLine guifg=NvimDarkCyan ctermfg=6",
"RedrawDebugClear guibg=NvimLightYellow ctermfg=15 ctermbg=3", "RedrawDebugClear guibg=NvimLightYellow ctermfg=15 ctermbg=3",
@@ -467,6 +467,7 @@ static const char *highlight_init_dark[] = {
"OkMsg guifg=NvimLightGreen ctermfg=10", "OkMsg guifg=NvimLightGreen ctermfg=10",
"Pmenu guibg=NvimDarkGrey3 cterm=reverse", "Pmenu guibg=NvimDarkGrey3 cterm=reverse",
"PmenuThumb guibg=NvimDarkGrey4", "PmenuThumb guibg=NvimDarkGrey4",
"PmenuShadow guibg=NvimDarkGrey4 ctermbg=0 blend=100",
"Question guifg=NvimLightCyan ctermfg=14", "Question guifg=NvimLightCyan ctermfg=14",
"QuickFixLine guifg=NvimLightCyan ctermfg=14", "QuickFixLine guifg=NvimLightCyan ctermfg=14",
"RedrawDebugClear guibg=NvimDarkYellow ctermfg=0 ctermbg=11", "RedrawDebugClear guibg=NvimDarkYellow ctermfg=0 ctermbg=11",

View File

@@ -599,18 +599,14 @@ void pum_redraw(void)
extra_space = true; extra_space = true;
} }
} }
if (pum_scrollbar > 0) {
grid_width++;
if (pum_rl) {
col_off++;
}
}
WinConfig fconfig = WIN_CONFIG_INIT; WinConfig fconfig = WIN_CONFIG_INIT;
int border_width = pum_border_width(); int border_width = pum_border_width();
int border_attr = 0;
schar_T border_char = 0;
schar_T fill_char = schar_from_ascii(' ');
bool has_border = border_width > 0;
// setup popup menu border if 'pumborder' option is set // setup popup menu border if 'pumborder' option is set
if (border_width > 0) { if (border_width > 0) {
fconfig.border = true;
Error err = ERROR_INIT; Error err = ERROR_INIT;
if (!parse_winborder(&fconfig, p_pumborder, &err)) { if (!parse_winborder(&fconfig, p_pumborder, &err)) {
if (ERROR_SET(&err)) { if (ERROR_SET(&err)) {
@@ -641,6 +637,17 @@ void pum_redraw(void)
fconfig.border_attr[i] = attr; fconfig.border_attr[i] = attr;
} }
api_clear_error(&err); api_clear_error(&err);
if (pum_scrollbar) {
border_char = schar_from_str(fconfig.border_chars[3]);
border_attr = fconfig.border_attr[3];
}
}
if (pum_scrollbar > 0 && !fconfig.border) {
grid_width++;
if (pum_rl) {
col_off++;
}
} }
grid_assign_handle(&pum_grid); grid_assign_handle(&pum_grid);
@@ -889,13 +896,10 @@ void pum_redraw(void)
} }
if (pum_scrollbar > 0) { if (pum_scrollbar > 0) {
if (pum_rl) { bool thumb = i >= thumb_pos && i < thumb_pos + thumb_height;
grid_line_puts(col_off - pum_width, " ", 1, int scrollbar_col = col_off + (pum_rl ? -pum_width : pum_width);
i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); grid_line_put_schar(scrollbar_col, (has_border && !thumb) ? border_char : fill_char,
} else { thumb ? attr_thumb : (has_border ? border_attr : attr_scroll));
grid_line_puts(col_off + pum_width, " ", 1,
i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll);
}
} }
grid_line_flush(); grid_line_flush();
row++; row++;
@@ -954,7 +958,10 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma
static void pum_adjust_info_position(win_T *wp, int width) static void pum_adjust_info_position(win_T *wp, int width)
{ {
int border_width = pum_border_width(); int border_width = pum_border_width();
int col = pum_col + pum_width + pum_scrollbar + 1 + border_width; int col = pum_col + pum_width + 1 + border_width;
if (border_width < 0) {
col += pum_scrollbar;
}
// TODO(glepnir): support config align border by using completepopup // TODO(glepnir): support config align border by using completepopup
// align menu // align menu
int right_extra = Columns - col; int right_extra = Columns - col;

View File

@@ -1064,6 +1064,7 @@ describe('builtin popupmenu', function()
[113] = { background = Screen.colors.Yellow, foreground = Screen.colors.Black }, [113] = { background = Screen.colors.Yellow, foreground = Screen.colors.Black },
[114] = { background = Screen.colors.Grey0, blend = 100 }, [114] = { background = Screen.colors.Grey0, blend = 100 },
[115] = { background = Screen.colors.Grey0, blend = 80 }, [115] = { background = Screen.colors.Grey0, blend = 80 },
[116] = { blend = 100, background = Screen.colors.NvimLightGrey4 },
-- popup non-selected item -- popup non-selected item
n = { background = Screen.colors.Plum1 }, n = { background = Screen.colors.Plum1 },
-- popup scrollbar knob -- popup scrollbar knob
@@ -8738,11 +8739,12 @@ describe('builtin popupmenu', function()
before_each(function() before_each(function()
screen:try_resize(30, 11) screen:try_resize(30, 11)
exec([[ exec([[
let g:list = [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}]
funct Omni_test(findstart, base) funct Omni_test(findstart, base)
if a:findstart if a:findstart
return col(".") - 1 return col(".") - 1
endif endif
return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] return g:list
endfunc endfunc
hi link PmenuBorder FloatBorder hi link PmenuBorder FloatBorder
set omnifunc=Omni_test set omnifunc=Omni_test
@@ -8992,9 +8994,9 @@ describe('builtin popupmenu', function()
end end
end) end)
it('pum border on cmdline', function() it("'pumborder' on cmdline and scrollbar rendering", function()
command('set wildoptions=pum') command('set wildoptions=pum')
feed(':<TAB>') feed(':t<TAB>')
if multigrid then if multigrid then
screen:expect({ screen:expect({
grid = [[ grid = [[
@@ -9005,18 +9007,18 @@ describe('builtin popupmenu', function()
| |
{1:~ }|*9 {1:~ }|*9
## grid 3 ## grid 3
:!^ | :t^ |
## grid 4 ## grid 4
╭────────────────╮| ╭────────────────╮|
│{12: ! }{c: }| │{12: t }{c: }|
│{n: # }{12: }│| │{n: tNext }│|
│{n: & }{12: }│| │{n: tab }│|
│{n: < }{12: }│| │{n: tabNext }│|
│{n: = }{12: }│| │{n: tabclose }│|
│{n: > }{12: }│| │{n: tabdo }│|
│{n: @ }{12: }│| │{n: tabedit }│|
│{n: Next }{12: }│| │{n: tabfind }│|
╰────────────────╯| ╰────────────────╯|
]], ]],
win_pos = { win_pos = {
[2] = { [2] = {
@@ -9053,17 +9055,50 @@ describe('builtin popupmenu', function()
}) })
else else
screen:expect([[ screen:expect([[
╭────────────────╮ | ╭────────────────╮ |
│{12: ! }{c: }{1: }| │{12: t }{c: }{1: }|
│{n: # }{12: }│{1: }| │{n: tNext }│{1: }|
│{n: & }{12: }│{1: }| │{n: tab }{1: }|
│{n: < }{12: }│{1: }| │{n: tabNext }│{1: }|
│{n: = }{12: }│{1: }| │{n: tabclose }{1: }|
│{n: > }{12: }│{1: }| │{n: tabdo }│{1: }|
│{n: @ }{12: }│{1: }| │{n: tabedit }│{1: }|
│{n: Next }{12: }│{1: }| │{n: tabfind }{1: }|
╰────────────────╯{1: }| ╰────────────────╯{1: }|
:!^ | :t^ |
]])
end
feed(('<C-N>'):rep(20))
if not multigrid then
screen:expect([[
╭────────────────╮ |
│{n: tabs }│{1: }|
│{n: tag }│{1: }|
│{n: tags }│{1: }|
│{n: tcd }{c: }{1: }|
│{12: tchdir }│{1: }|
│{n: tcl }│{1: }|
│{n: tcldo }│{1: }|
│{n: tclfile }│{1: }|
╰────────────────╯{1: }|
:tchdir^ |
]])
end
feed(('<C-P>'):rep(20))
if not multigrid then
screen:expect([[
╭────────────────╮ |
│{12: t }{c: }{1: }|
│{n: tNext }│{1: }|
│{n: tab }│{1: }|
│{n: tabNext }│{1: }|
│{n: tabclose }│{1: }|
│{n: tabdo }│{1: }|
│{n: tabedit }│{1: }|
│{n: tabfind }│{1: }|
╰────────────────╯{1: }|
:t^ |
]]) ]])
end end
end) end)
@@ -9084,9 +9119,9 @@ describe('builtin popupmenu', function()
## grid 3 ## grid 3
{5:-- }{6:match 1 of 3} | {5:-- }{6:match 1 of 3} |
## grid 4 ## grid 4
╭───────────────╮| ╭───────────────╮|
│{12:one }{c: }| │{12:one }{c: }|
╰───────────────╯| ╰───────────────╯|
]], ]],
win_pos = { win_pos = {
[2] = { [2] = {
@@ -9124,9 +9159,9 @@ describe('builtin popupmenu', function()
else else
screen:expect([[ screen:expect([[
one^ | one^ |
╭───────────────╮{1: }| ╭───────────────╮{1: }|
│{12:one }{c: }{1: }| │{12:one }{c: }{1: }|
╰───────────────╯{1: }| ╰───────────────╯{1: }|
{1:~ }| {1:~ }|
{3:[No Name] [+] }| {3:[No Name] [+] }|
{5:-- }{6:match 1 of 3} | {5:-- }{6:match 1 of 3} |
@@ -9167,9 +9202,9 @@ describe('builtin popupmenu', function()
{n:1info}| {n:1info}|
## grid 5 ## grid 5
{12:one }{114: }| {12:one }{114: }|
{n:two }{115: }| {n:two }{116: }|
{n:three }{115: }| {n:three }{116: }|
{114: }{115: }| {114: }{116: }|
]], ]],
win_pos = { win_pos = {
[2] = { [2] = {
@@ -9225,9 +9260,9 @@ describe('builtin popupmenu', function()
screen:expect([[ screen:expect([[
one^ | one^ |
{12:one }{114: }{n:1info}{1: }| {12:one }{114: }{n:1info}{1: }|
{n:two }{115: }{1: }| {n:two }{116: }{1: }|
{n:three }{115: }{1: }| {n:three }{116: }{1: }|
{114: }{115: }{1: }| {114: }{116: }{1: }|
{1:~ }|*5 {1:~ }|*5
{5:-- }{6:match 1 of 3} | {5:-- }{6:match 1 of 3} |
]]) ]])