diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index a05f22d9b3..c9a21c45e3 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -219,6 +219,7 @@ HIGHLIGHTS • |hl-DiffTextAdd| highlights added text within a changed line. • |hl-OkMsg| |hl-StderrMsg| |hl-StdoutMsg| • |hl-SnippetTabstopActive| highlights the currently active tabstop. +• |hl-PmenuBorder |hl-PmenuShadow| |hl-PmenuShadowThrough| see 'pumborder'. LSP @@ -301,6 +302,7 @@ OPTIONS • 'winborder' "bold" style, custom border style. • |g:clipboard| accepts a string name to force any builtin clipboard tool. • 'busy' sets a buffer "busy" status. Indicated in the default statusline. +• 'pumborder' add a border to the popup menu. PERFORMANCE diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 96b507fe6a..c691bca8e4 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4912,7 +4912,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'pumborder' string (default "") global Defines the default border style of popupmenu windows. Same as - 'winborder'. + 'winborder'. |hl-PmenuBorder| is used. When style is "shadow", the + |hl-PmenuShadow| and |hl-PmenuShadowThrough| are used. *'pumheight'* *'ph'* 'pumheight' 'ph' number (default 0) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 8cf72ff134..35d08c46b6 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5354,6 +5354,11 @@ Normal Normal text. NormalFloat Normal text in floating windows. *hl-FloatBorder* FloatBorder Border of floating windows. + *hl-FloatShadow* +FloatShadow Blended areas when border is shadow. + *hl-FLoatShadowThrough* +FloatShadownThrough + shadow corners when border is shadow. *hl-FloatTitle* FloatTitle Title of floating windows. *hl-FloatFooter* @@ -5382,6 +5387,13 @@ PmenuMatch Popup menu: Matched text in normal item. Combined with *hl-PmenuMatchSel* PmenuMatchSel Popup menu: Matched text in selected item. Combined with |hl-PmenuMatch| and |hl-PmenuSel|. + *hl-PmenuBorder* +PmenuBorder Popup menu: border of popup menu. + *hl-PmenuShadow* +PmenuShadow Popup menu: blended areas when 'pumborder' is shadow. + *hl-PmenuShadowThrough* +PmenuShadownThrough + Popup menu: shadow corners when 'pumborder' is shadow. *hl-ComplMatchIns* ComplMatchIns Matched text of the currently inserted completion. *hl-PreInsert* diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 059623fec7..175817834c 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -5130,7 +5130,8 @@ vim.go.pumblend = vim.o.pumblend vim.go.pb = vim.go.pumblend --- Defines the default border style of popupmenu windows. Same as ---- 'winborder'. +--- 'winborder'. `hl-PmenuBorder` is used. When style is "shadow", the +--- `hl-PmenuShadow` and `hl-PmenuShadowThrough` are used. --- --- @type string vim.o.pumborder = "" diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 81c33c1da0..7ca6121d01 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1079,7 +1079,7 @@ static void generate_api_error(win_T *wp, const char *attribute, Error *err) } /// Parses a border style name or custom (comma-separated) style. -bool parse_winborder(WinConfig *fconfig, const char *border_opt, Error *err) +bool parse_winborder(WinConfig *fconfig, char *border_opt, Error *err) { if (!fconfig) { return false; @@ -1088,7 +1088,7 @@ bool parse_winborder(WinConfig *fconfig, const char *border_opt, Error *err) if (strchr(border_opt, ',')) { Array border_chars = ARRAY_DICT_INIT; - char *p = p_winborder; + char *p = border_opt; char part[MAX_SCHAR_SIZE] = { 0 }; int count = 0; @@ -1116,7 +1116,7 @@ bool parse_winborder(WinConfig *fconfig, const char *border_opt, Error *err) style = ARRAY_OBJ(border_chars); } else { - style = CSTR_TO_OBJ(p_winborder); + style = CSTR_TO_OBJ(border_opt); } parse_border_style(style, fconfig, err); diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 3c1dd4defe..be8c07aa4e 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -658,7 +658,7 @@ int update_screen(void) win_grid_alloc(wp); if (wp->w_redr_border || wp->w_redr_type >= UPD_NOT_VALID) { - grid_draw_border(&wp->w_grid_alloc, wp->w_config, wp->w_border_adj, (int)wp->w_p_winbl, + grid_draw_border(&wp->w_grid_alloc, &wp->w_config, wp->w_border_adj, (int)wp->w_p_winbl, wp->w_ns_hl_attr); } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 12fd694f3d..7c204cb7ba 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -1115,9 +1115,9 @@ static int get_bordertext_col(int total_col, int text_width, AlignTextPos align) } /// draw border on floating window grid -void grid_draw_border(ScreenGrid *grid, WinConfig config, int *adj, int winbl, int *hl_attr) +void grid_draw_border(ScreenGrid *grid, WinConfig *config, int *adj, int winbl, int *hl_attr) { - int *attrs = config.border_attr; + int *attrs = config->border_attr; int default_adj[4] = { 1, 1, 1, 1 }; if (adj == NULL) { adj = default_adj; @@ -1128,7 +1128,7 @@ void grid_draw_border(ScreenGrid *grid, WinConfig config, int *adj, int winbl, i } for (int i = 0; i < 8; i++) { - chars[i] = schar_from_str(config.border_chars[i]); + chars[i] = schar_from_str(config->border_chars[i]); } int irow = grid->rows - adj[0] - adj[2]; @@ -1144,9 +1144,9 @@ void grid_draw_border(ScreenGrid *grid, WinConfig config, int *adj, int winbl, i grid_line_put_schar(i + adj[3], chars[1], attrs[1]); } - if (config.title) { - int title_col = get_bordertext_col(icol, config.title_width, config.title_pos); - grid_draw_bordertext(config.title_chunks, title_col, winbl, hl_attr, kBorderTextTitle); + if (config->title) { + int title_col = get_bordertext_col(icol, config->title_width, config->title_pos); + grid_draw_bordertext(config->title_chunks, title_col, winbl, hl_attr, kBorderTextTitle); } if (adj[1]) { grid_line_put_schar(icol + adj[3], chars[2], attrs[2]); @@ -1179,9 +1179,9 @@ void grid_draw_border(ScreenGrid *grid, WinConfig config, int *adj, int winbl, i grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]); } - if (config.footer) { - int footer_col = get_bordertext_col(icol, config.footer_width, config.footer_pos); - grid_draw_bordertext(config.footer_chunks, footer_col, winbl, hl_attr, kBorderTextFooter); + if (config->footer) { + int footer_col = get_bordertext_col(icol, config->footer_width, config->footer_pos); + grid_draw_bordertext(config->footer_chunks, footer_col, winbl, hl_attr, kBorderTextFooter); } if (adj[1]) { grid_line_put_schar(icol + adj[3], chars[4], attrs[4]); diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 3fd511e1e9..52ae450e7d 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -6727,7 +6727,8 @@ local options = { values = { '', 'double', 'single', 'shadow', 'rounded', 'solid', 'bold', 'none' }, desc = [=[ Defines the default border style of popupmenu windows. Same as - 'winborder'. + 'winborder'. |hl-PmenuBorder| is used. When style is "shadow", the + |hl-PmenuShadow| and |hl-PmenuShadowThrough| are used. ]=], short_desc = N_('border of popupmenu'), type = 'string', diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 6b072b8ad4..cf5e6a28ba 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2125,7 +2125,7 @@ const char *did_set_winbar(optset_T *args) return did_set_statustabline_rulerformat(args, false, false); } -static bool parse_border_opt(const char *border_opt) +static bool parse_border_opt(char *border_opt) { WinConfig fconfig = WIN_CONFIG_INIT; Error err = ERROR_INIT; diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 26955dd315..e172c6f312 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -298,6 +298,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_rl = (State & MODE_CMDLINE) == 0 && curwin->w_p_rl; int pum_border_size = *p_pumborder != NUL ? 2 : 0; + if (strequal(p_pumborder, opt_winborder_values[3])) { + pum_border_size -= 1; + } do { // Mark the pum as visible already here, @@ -605,19 +608,34 @@ void pum_redraw(void) pum_border_width = 2; fconfig.border = true; Error err = ERROR_INIT; - parse_border_style(CSTR_AS_OBJ(p_pumborder), &fconfig, &err); - // shadow style uses different highlights for different positions - if (strcmp(p_pumborder, opt_winborder_values[3]) == 0) { + if (!parse_winborder(&fconfig, p_pumborder, &err)) { + if (ERROR_SET(&err)) { + emsg(err.msg); + } + api_clear_error(&err); + return; + } + + // Shadow style: only adds border on right and bottom edges + if (strequal(p_pumborder, opt_winborder_values[3])) { + fconfig.shadow = true; + pum_border_width = 1; int blend = SYN_GROUP_STATIC("PmenuShadow"); int through = SYN_GROUP_STATIC("PmenuShadowThrough"); - int attrs[] = { 0, 0, through, blend, blend, blend, through, 0 }; - memcpy(fconfig.border_attr, attrs, sizeof(attrs)); - } else { - // Non-shadow styles use PumBorder highlight for all border chars + fconfig.border_hl_ids[2] = through; + fconfig.border_hl_ids[3] = blend; + fconfig.border_hl_ids[4] = blend; + fconfig.border_hl_ids[5] = blend; + fconfig.border_hl_ids[6] = through; + } + + // convert border highlight IDs to attributes, use PmenuBorder as default + for (int i = 0; i < 8; i++) { int attr = hl_attr_active[HLF_PBR]; - for (int i = 0; i < 8; i++) { - fconfig.border_attr[i] = attr; + if (fconfig.border_hl_ids[i]) { + attr = hl_get_ui_attr(-1, HLF_PBR, fconfig.border_hl_ids[i], false); } + fconfig.border_attr[i] = attr; } api_clear_error(&err); } @@ -653,9 +671,11 @@ void pum_redraw(void) // avoid set border for mouse menu int mouse_menu = State != MODE_CMDLINE && pum_grid.zindex == kZIndexCmdlinePopupMenu; if (!mouse_menu && fconfig.border) { - grid_draw_border(&pum_grid, fconfig, NULL, 0, NULL); - row++; - col_off++; + grid_draw_border(&pum_grid, &fconfig, NULL, 0, NULL); + if (!fconfig.shadow) { + row++; + col_off++; + } } // Never display more than we have diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index c65b3c5118..fde3c2cd1a 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -1061,6 +1061,8 @@ describe('builtin popupmenu', function() [111] = { background = Screen.colors.Plum1, foreground = Screen.colors.DarkBlue }, [112] = { background = Screen.colors.Plum1, foreground = Screen.colors.DarkGreen }, [113] = { background = Screen.colors.Yellow, foreground = Screen.colors.Black }, + [114] = { background = Screen.colors.Grey0, blend = 100 }, + [115] = { background = Screen.colors.Grey0, blend = 80 }, -- popup non-selected item n = { background = Screen.colors.Plum1 }, -- popup scrollbar knob @@ -9130,6 +9132,106 @@ describe('builtin popupmenu', function() ]]) end end) + it('custom pumborder characters', function() + command('set pumborder=+,+,=,+,+,-,+,+') + feed('S') + if not multigrid then + screen:expect([[ + one^ | + ++++++++++++++++={n:1info}{1: }| + +{12:one }+{1: }| + +{n:two }+{1: }| + +{n:three }+{1: }| + +---------------+{1: }| + {1:~ }|*4 + {5:-- }{6:match 1 of 3} | + ]]) + end + end) + it('pumborder with shadow', function() + command('set pumborder=shadow') + feed('S') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------]|*10 + [3:------------------------------]| + ## grid 2 + one^ | + {1:~ }|*9 + ## grid 3 + {5:-- }{6:match 1 of 3} | + ## grid 4 + {n:1info}| + ## grid 5 + {12:one }{114: }| + {n:two }{115: }| + {n:three }{115: }| + {114: }{115: }| + ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 30, + win = 1000, + }, + }, + float_pos = { + [5] = { -1, 'NW', 2, 1, 0, false, 100, 2, 1, 0 }, + [4] = { 1001, 'NW', 1, 1, 17, false, 50, 1, 1, 17 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 3, + linecount = 1, + sum_scroll_delta = 0, + }, + [4] = { + win = 1001, + topline = 0, + 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([[ + one^ | + {12:one }{114: }{1: }{n:1info}{1: }| + {n:two }{115: }{1: }| + {n:three }{115: }{1: }| + {114: }{115: }{1: }| + {1:~ }|*5 + {5:-- }{6:match 1 of 3} | + ]]) + end + end) end) end diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 0d2c1ca9c5..76aae7d19f 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -300,6 +300,7 @@ let test_values = { \ 'alpha,hex,bin'], \ ['xxx']], \ 'patchmode': [['', 'xxx', '.x'], [&backupext, '*']], + \ 'pumborder': [['rounded', 'none', 'single', 'solid'], ['xxx', 'none,solid']], "\ 'previewpopup': [['', 'height:13', 'width:20', 'highlight:That', "\ " 'align:item', 'align:menu', 'border:on', 'border:off', "\ " 'width:10,height:234,highlight:Mine'],