0
0
mirror of https://github.com/vim/vim.git synced 2025-10-20 08:14:18 -04:00

patch 9.1.1856: cannot style popup window (border, shadow, etc)

Problem:  cannot style popup window (border, shadow, etc)
Solution: Extend the 'completepopup' option with additional properties
          (Girish Palya)

This patch extends the 'completepopup' option with additional settings
to allow more configuration of info popup window.

New values:
```
- close           "on" (default) or "off"
- resize          "on" (default) or "off"
- borderchars     specify eight characters (separated by semicolons) to
                  draw the popup border: top, right, bottom, left,
                  topleft, topright, botright, botleft.
- borderhighlight highlight group for the popup border characters
- shadow          pum shadow
```

closes: #18487

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Girish Palya
2025-10-14 18:48:36 +00:00
committed by Christian Brabandt
parent e15cd0f065
commit 1a09f11f5d
12 changed files with 324 additions and 59 deletions

View File

@@ -711,7 +711,7 @@ static struct vimoption options[] =
SCTX_INIT},
{"completepopup", "cpp", P_STRING|P_VI_DEF|P_COMMA|P_NODUP|P_COLON,
#if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
(char_u *)&p_cpp, PV_NONE, did_set_completepopup, expand_set_popupoption,
(char_u *)&p_cpp, PV_NONE, did_set_completepopup, expand_set_completepopup,
{(char_u *)"", (char_u *)0L}
#else
(char_u *)NULL, PV_NONE, NULL, NULL,
@@ -1991,7 +1991,7 @@ static struct vimoption options[] =
{(char_u *)12L, (char_u *)0L} SCTX_INIT},
{"previewpopup", "pvp", P_STRING|P_VI_DEF|P_COMMA|P_NODUP|P_COLON,
#ifdef FEAT_PROP_POPUP
(char_u *)&p_pvp, PV_NONE, did_set_previewpopup, expand_set_popupoption,
(char_u *)&p_pvp, PV_NONE, did_set_previewpopup, expand_set_previewpopup,
{(char_u *)"", (char_u *)0L}
#else
(char_u *)NULL, PV_NONE, NULL, NULL,

View File

@@ -70,9 +70,14 @@ static char *(p_fdo_values[]) = {"all", "block", "hor", "mark", "percent",
static char *(p_kpc_protocol_values[]) = {"none", "mok2", "kitty", NULL};
#ifdef FEAT_PROP_POPUP
// Note: Keep this in sync with parse_popup_option()
static char *(p_popup_option_values[]) = { "align:", "border:", "height:",
"highlight:", "shadow:", "width:", NULL};
static char *(p_popup_option_border_values[]) = {"on", "off", NULL};
static char *(p_popup_cpp_option_values[]) = {"align:", "border:",
"borderhighlight:", "close:", "height:", "highlight:", "resize:",
"shadow:", "width:", NULL};
static char *(p_popup_pvp_option_values[]) = {"height:", "highlight:",
"width:", NULL};
static char *(p_popup_option_on_off_values[]) = {"on", "off", NULL};
static char *(p_popup_cpp_border_values[]) = {"single", "double", "round",
"ascii", "on", "off", "custom:", NULL};
static char *(p_popup_option_align_values[]) = {"item", "menu", NULL};
#endif
#if defined(FEAT_SPELL)
@@ -3408,8 +3413,9 @@ did_set_previewpopup(optset_T *args UNUSED)
return NULL;
}
int
expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches)
static int
expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches,
int previewpopup)
{
expand_T *xp = args->oe_xp;
@@ -3417,13 +3423,34 @@ expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches)
{
// Within "highlight:"/"border:"/"align:", we have a subgroup of possible options.
int border_len = (int)STRLEN("border:");
if (xp->xp_pattern - args->oe_set_arg >= border_len &&
STRNCMP(xp->xp_pattern - border_len, "border:", border_len) == 0)
int close_len = (int)STRLEN("close:");
int resize_len = (int)STRLEN("resize:");
int shadow_len = (int)STRLEN("shadow:");
int is_border = xp->xp_pattern - args->oe_set_arg >= border_len &&
STRNCMP(xp->xp_pattern - border_len, "border:", border_len) == 0;
int is_close = xp->xp_pattern - args->oe_set_arg >= close_len &&
STRNCMP(xp->xp_pattern - close_len, "close:", close) == 0;
int is_resize = xp->xp_pattern - args->oe_set_arg >= resize_len &&
STRNCMP(xp->xp_pattern - resize_len, "resize:", resize_len) == 0;
int is_shadow = xp->xp_pattern - args->oe_set_arg >= shadow_len &&
STRNCMP(xp->xp_pattern - shadow_len, "shadow:", shadow_len) == 0;
if (is_close || is_resize || is_shadow)
{
return expand_set_opt_string(
args,
p_popup_option_border_values,
ARRAY_LENGTH(p_popup_option_border_values) - 1,
p_popup_option_on_off_values,
ARRAY_LENGTH(p_popup_option_on_off_values) - 1,
numMatches,
matches);
}
if (is_border)
{
return expand_set_opt_string(
args,
previewpopup ? p_popup_option_on_off_values
: p_popup_cpp_border_values,
(previewpopup ? ARRAY_LENGTH(p_popup_option_on_off_values)
: ARRAY_LENGTH(p_popup_cpp_border_values)) - 1,
numMatches,
matches);
}
@@ -3439,8 +3466,15 @@ expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches)
matches);
}
int highlight_len = (int)STRLEN("highlight:");
if (xp->xp_pattern - args->oe_set_arg >= highlight_len &&
STRNCMP(xp->xp_pattern - highlight_len, "highlight:", highlight_len) == 0)
int borderhighlight_len = (int)STRLEN("borderhighlight:");
int is_highlight = xp->xp_pattern - args->oe_set_arg >= highlight_len
&& STRNCMP(xp->xp_pattern - highlight_len, "highlight:",
highlight_len) == 0;
int is_borderhighlight
= xp->xp_pattern - args->oe_set_arg >= borderhighlight_len
&& STRNCMP(xp->xp_pattern - borderhighlight_len, "highlight:",
borderhighlight_len) == 0;
if (is_highlight || is_borderhighlight)
{
// Return the list of all highlight names
return expand_set_opt_generic(
@@ -3454,11 +3488,25 @@ expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches)
return expand_set_opt_string(
args,
p_popup_option_values,
ARRAY_LENGTH(p_popup_option_values) - 1,
previewpopup ? p_popup_pvp_option_values
: p_popup_cpp_option_values,
previewpopup ? ARRAY_LENGTH(p_popup_pvp_option_values) - 1
: ARRAY_LENGTH(p_popup_cpp_option_values) - 1,
numMatches,
matches);
}
int
expand_set_previewpopup(optexpand_T *args, int *numMatches, char_u ***matches)
{
return expand_set_popupoption(args, numMatches, matches, TRUE);
}
int
expand_set_completepopup(optexpand_T *args, int *numMatches, char_u ***matches)
{
return expand_set_popupoption(args, numMatches, matches, FALSE);
}
#endif
#if defined(FEAT_POSTSCRIPT)

View File

@@ -1219,8 +1219,10 @@ popup_adjust_position(win_T *wp)
int center_hor = FALSE;
int allow_adjust_left = !wp->w_popup_fixed;
int top_extra = popup_top_extra(wp);
int right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1];
int bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2];
int right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1]
+ (wp->w_popup_shadow ? 2 : 0);
int bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2]
+ wp->w_popup_shadow;
int left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
int extra_height = top_extra + bot_extra;
int extra_width = left_extra + right_extra;
@@ -1782,6 +1784,21 @@ popup_set_buffer_text(buf_T *buf, typval_T text)
curbuf = curwin->w_buffer;
}
#define SET_BORDER_CHARS(a0, a1, a2, a3, a4, a5, a6, a7) \
do { \
if (wp != NULL) \
{ \
wp->w_border_char[0] = (a0); \
wp->w_border_char[1] = (a1); \
wp->w_border_char[2] = (a2); \
wp->w_border_char[3] = (a3); \
wp->w_border_char[4] = (a4); \
wp->w_border_char[5] = (a5); \
wp->w_border_char[6] = (a6); \
wp->w_border_char[7] = (a7); \
} \
} while (0)
/*
* Parse the 'previewpopup' or 'completepopup' option and apply the values to
* window "wp" if it is not NULL.
@@ -1795,6 +1812,7 @@ parse_popup_option(win_T *wp, int is_preview)
!is_preview ? p_cpp :
#endif
p_pvp;
int border_enabled = FALSE;
if (wp != NULL)
wp->w_popup_flags &= ~POPF_INFO_MENU;
@@ -1847,28 +1865,159 @@ parse_popup_option(win_T *wp, int is_preview)
*p = NUL;
set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
s + 10, OPT_FREE|OPT_LOCAL, 0);
s + 10, OPT_FREE|OPT_LOCAL, 0);
*p = c;
}
}
else if (STRNCMP(s, "borderhighlight:", 16) == 0)
{
char_u *arg = s + 16;
if (*arg == NUL || *arg == ',')
return FAIL;
if (wp != NULL)
{
for (int i = 0; i < 4; ++i)
{
VIM_CLEAR(wp->w_border_highlight[i]);
wp->w_border_highlight[i] = vim_strnsave(arg, p - arg);
}
}
}
else if (STRNCMP(s, "border:", 7) == 0)
{
// Note: Keep this in sync with p_popup_option_border_values.
char_u *arg = s + 7;
int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
int i;
int token_len = p - arg;
char_u *token;
// Use box-drawing characters only when 'encoding' is "utf-8" and
// 'ambiwidth' is "single".
int can_use_box_chars = (enc_utf8 && *p_ambw == 's');
if (!on && !off)
if (token_len == 0
|| (STRNCMP(arg, "off", 3) == 0 && arg + 3 == p))
{
if (wp != NULL)
{
for (i = 0; i < 4; ++i)
wp->w_popup_border[i] = 0;
SET_BORDER_CHARS(0, 0, 0, 0, 0, 0, 0, 0);
// only show the X for close when there is a border
wp->w_popup_close = POPCLOSE_NONE;
}
continue;
}
token = vim_strnsave(arg, token_len);
if (token == NULL)
return FAIL;
if ((can_use_box_chars && (STRCMP(token, "single") == 0
|| STRCMP(token, "double") == 0
|| STRCMP(token, "on") == 0
|| STRCMP(token, "round") == 0))
|| STRCMP(token, "ascii") == 0
|| (STRNCMP(token, "custom:", 7) == 0))
{
if (STRCMP(token, "single") == 0)
SET_BORDER_CHARS(0x2500, 0x2502, 0x2500, 0x2502, // ─ │ ─ │
0x250c, 0x2510, 0x2518, 0x2514); // ┌ ┐ ┘ └
else if (STRCMP(token, "double") == 0)
SET_BORDER_CHARS(0x2550, 0x2551, 0x2550, 0x2551, // ═ ║ ═ ║
0x2554, 0x2557, 0x255D, 0x255A); // ╔ ╗ ╝ ╚
else if (STRCMP(token, "round") == 0)
SET_BORDER_CHARS(0x2500, 0x2502, 0x2500, 0x2502, // ─ │ ─ │
0x256d, 0x256e, 0x256f, 0x2570); // ╭ ╮ ╯ ╰
else if (STRCMP(token, "on") == 0)
SET_BORDER_CHARS(0, 0, 0, 0, 0, 0, 0, 0);
else if (STRCMP(token, "ascii") == 0)
SET_BORDER_CHARS('-', '|', '-', '|', '+', '+', '+', '+');
else if (STRNCMP(token, "custom:", 7) == 0)
{
char_u *q = token + 7;
int out[8];
int failed = FALSE;
SET_BORDER_CHARS(0, 0, 0, 0, 0, 0, 0, 0);
for (i = 0; i < 8 && !failed; i++)
{
if (*q == NUL)
failed = TRUE;
else
{
out[i] = mb_ptr2char(q);
mb_ptr2char_adv(&q);
if (i < 7)
{
if (*q != ';')
failed = TRUE; // must be semicolon
q++;
}
}
}
if (failed || *q != NUL) // must end exactly after the 8th char
{
vim_free(token);
return FAIL;
}
SET_BORDER_CHARS(out[0], out[1], out[2], out[3], out[4],
out[5], out[6], out[7]);
}
}
else
{
vim_free(token);
return FAIL;
}
if (wp != NULL)
{
for (i = 0; i < 4; ++i)
wp->w_popup_border[i] = on ? 1 : 0;
if (off)
// only show the X for close when there is a border
wp->w_popup_close = POPCLOSE_NONE;
wp->w_popup_border[i] = 1;
}
border_enabled = TRUE;
vim_free(token);
}
else if (STRNCMP(s, "close:", 6) == 0)
{
char_u *arg = s + 6;
int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
if ((!on && !off) || is_preview)
return FAIL;
on = on && mouse_has(MOUSE_INSERT) && border_enabled;
if (wp != NULL)
wp->w_popup_close = on ? POPCLOSE_BUTTON : POPCLOSE_NONE;
}
else if (STRNCMP(s, "resize:", 7) == 0)
{
char_u *arg = s + 7;
int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
if ((!on && !off) || is_preview)
return FAIL;
if (wp != NULL)
{
if (on && mouse_has(MOUSE_INSERT))
wp->w_popup_flags |= POPF_RESIZE;
else
wp->w_popup_flags &= ~POPF_RESIZE;
}
}
else if (STRNCMP(s, "shadow:", 7) == 0)
{
char_u *arg = s + 7;
int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
if ((!on && !off) || is_preview)
return FAIL;
if (wp != NULL)
wp->w_popup_shadow = on ? 1 : 0;
}
else if (STRNCMP(s, "align:", 6) == 0)
{
@@ -2297,6 +2446,12 @@ popup_create(typval_T *argvars, typval_T *rettv, create_type_T type)
parse_previewpopup(wp);
popup_set_wantpos_cursor(wp, wp->w_minwidth, d);
}
for (i = 0; i < 4; ++i)
VIM_CLEAR(wp->w_border_highlight[i]);
for (i = 0; i < 8; ++i)
wp->w_border_char[i] = 0;
# ifdef FEAT_QUICKFIX
if (type == TYPE_INFO)
{
@@ -2311,10 +2466,6 @@ popup_create(typval_T *argvars, typval_T *rettv, create_type_T type)
}
# endif
for (i = 0; i < 4; ++i)
VIM_CLEAR(wp->w_border_highlight[i]);
for (i = 0; i < 8; ++i)
wp->w_border_char[i] = 0;
wp->w_want_scrollbar = 1;
wp->w_popup_fixed = 0;
wp->w_filter_mode = MODE_ALL;
@@ -4151,7 +4302,6 @@ update_popups(void (*win_update)(win_T *wp))
else
wp->w_popup_flags &= ~POPF_ON_CMDLINE;
// We can only use these line drawing characters when 'encoding' is
// "utf-8" and 'ambiwidth' is "single".
if (enc_utf8 && *p_ambw == 's')
@@ -4374,6 +4524,18 @@ update_popups(void (*win_update)(win_T *wp))
popup_attr);
}
// right shadow
if (wp->w_popup_shadow)
{
int col = wincol + total_width;
for (i = 0; i < total_height; ++i)
{
row = wp->w_winrow + i + 1;
put_shadow_char(row, col);
put_shadow_char(row, col + 1);
}
}
if (wp->w_popup_padding[2] > 0)
{
// bottom padding
@@ -4387,7 +4549,7 @@ update_popups(void (*win_update)(win_T *wp))
{
// bottom border
row = wp->w_winrow + total_height - 1;
screen_fill(row , row + 1,
screen_fill(row, row + 1,
wincol < 0 ? 0 : wincol,
wincol + total_width,
wp->w_popup_border[3] != 0 && wp->w_popup_leftoff == 0
@@ -4400,6 +4562,15 @@ update_popups(void (*win_update)(win_T *wp))
}
}
if (wp->w_popup_shadow)
{
// bottom shadow
row = wp->w_winrow + total_height;
for (int col = 2 + (wincol < 0 ? 0 : wincol);
col < wincol + total_width; col++)
put_shadow_char(row, col);
}
if (wp->w_popup_close == POPCLOSE_BUTTON)
{
// close button goes on top of anything at the top-right corner

View File

@@ -130,7 +130,8 @@ int expand_set_nrformats(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_optexpr(optset_T *args);
char *did_set_pastetoggle(optset_T *args);
char *did_set_previewpopup(optset_T *args);
int expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_previewpopup(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_completepopup(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_printencoding(optset_T *args);
int expand_set_printoptions(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_renderoptions(optset_T *args);

View File

@@ -4054,6 +4054,7 @@ struct window_S
int w_popup_border[4]; // popup border top/right/bot/left
char_u *w_border_highlight[4]; // popup border highlight
int w_border_char[8]; // popup border characters
int w_popup_shadow; // popup shadow (right and bottom edges)
int w_popup_leftoff; // columns left of the screen
int w_popup_rightoff; // columns right of the screen

View File

@@ -0,0 +1,14 @@
|a+0&#ffffff0|w|o|r|d| @69
|a|w|o|r|d> @69
|w+0#0000001#e0e0e08|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#0000001| +0#0000001#e0e0e08|w|o|r|d|s| |a|r|e| |c|o@1|l| | +0#4040ff13#ffffff0@36
|a+0#0000001#ffd7ff255|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#0000001| +0#4040ff13#ffffff0@1| +0#6c6c6c255#0000001@15| +0#4040ff13#ffffff0@34
|n+0#0000001#ffd7ff255|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@52
|~| @73
|~| @73
|~| @73
|~| @73
|~| @73
|~| @73
|~| @73
|~| @73
|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26

View File

@@ -637,7 +637,7 @@ func Test_set_completion_string_values()
set keyprotocol&
" previewpopup / completepopup
call assert_equal('align:', getcompletion('set previewpopup=', 'cmdline')[0])
call assert_equal('height:', getcompletion('set previewpopup=', 'cmdline')[0])
call assert_equal('EndOfBuffer', getcompletion('set previewpopup=highlight:End*Buffer', 'cmdline')[0])
call feedkeys(":set previewpopup+=border:\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"set previewpopup+=border:on', @:)

View File

@@ -3701,6 +3701,12 @@ func Test_popupmenu_info_border()
call term_sendkeys(buf, "a\<C-X>\<C-U>")
call VerifyScreenDump(buf, 'Test_popupwin_infopopup_8', {})
" Test shadow
call term_sendkeys(buf, "\<Esc>")
call term_sendkeys(buf, ":set completepopup=border:off,shadow:on\<CR>")
call term_sendkeys(buf, "Sa\<C-X>\<C-U>")
call VerifyScreenDump(buf, 'Test_popupwin_infopopup_9', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
endfunc

View File

@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1856,
/**/
1855,
/**/