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

Commit: 
https://github.com/vim/vim/commit/1a09f11f5d96f169d40eb74c260a8520efd10558
Author: Girish Palya <[email protected]>
Date:   Tue Oct 14 18:48:36 2025 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index 89d52e260..c08147c60 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -1,4 +1,4 @@
-*insert.txt*    For Vim version 9.1.  Last change: 2025 Oct 12
+*insert.txt*    For Vim version 9.1.  Last change: 2025 Oct 14
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -1287,26 +1287,9 @@ remove existing text in the preview window.  The size of 
the preview window is
 three lines, but 'previewheight' is used when it has a value of 1 or 2.
 
                                                *complete-popup*
-When "popup" is in 'completeopt' a popup window is used to display the "info".
-Then the 'completepopup' option specifies the properties of the popup.  This
-is used when the info popup is created.  The option is a comma-separated list
-of values:
-       height          maximum height of the popup
-       width           maximum width of the popup
-       highlight       highlight group of the popup (default is PmenuSel)
-       align           "item" (default) or "menu"
-       border          "on" (default) or "off"
-Example: >
-       :set completepopup=height:10,width:60,highlight:InfoPopup
-
-When `"align"` is set to `"item"`, the popup is positioned near the selected
-item, and moves as the selection changes.
-When set to `"menu"`, the popup aligns with the top of the menu (if the menu
-appears below the text), or with the bottom (if the menu appears above).
-
-If the 'mouse' is enabled, a close button and resize handle will appear on the
-popup border.
-
+When "popup" is included in 'completeopt', a popup window is used to display
+the completion "info" text.  The appearance and behavior of this popup are
+controlled by the 'completepopup' option when the popup is created.
 After creation, the info popup can be located with |popup_findinfo()| and
 modified using |popup_setoptions()|.
 
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index f32f58ba6..f4fd5ed80 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2025 Oct 12
+*options.txt*  For Vim version 9.1.  Last change: 2025 Oct 14
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2339,12 +2339,50 @@ A jump table for the options with a short description 
can be found at |Q_op|.
                        global
                        {not available when compiled without the |+textprop|
                        or |+quickfix| feature}
-       When 'completeopt' contains "popup" then this option is used for the
+       When 'completeopt' contains "popup", this option specifies the
        properties of the info popup when it is created.  If an info popup
        window already exists it is closed, so that the option value is
        applied when it is created again.
-       You can also use |popup_findinfo()| and then set properties for an
-       existing info popup with |popup_setoptions()|.  See |complete-popup|.
+
+       The option is a comma-separated list of values:
+               align           "item" (default) or "menu"
+               border          border style:
+                               "single"        thin box-drawing characters
+                               "double"        double-line
+                               "round"         rounded corners
+                               "ascii"         ASCII characters (-, |, +)
+                               "custom:X;X;X;X;X;X;X;X"
+                                               eight characters separated by
+                                               semicolons, in the order: top,
+                                               right, bottom, left, topleft,
+                                               topright, botright, botleft
+                               "on"            same as "double" when
+                                               'encoding' is "utf-8" and
+                                               'ambiwidth' is "single", else
+                                               "ascii" (default)
+                               "off"           no border
+               borderhighlight highlight group for the popup border characters
+               close           show close button: "on" (default) or "off"
+               height          maximum height of the popup
+               highlight       popup highlight group (default: PmenuSel)
+               resize          show resize handle: "on" (default) or "off"
+               shadow          "off" (default) or "on" using |hl-PmenuShadow|
+               width           maximum width of the popup
+
+       Example: >
+               :set completepopup=height:10,border:single,highlight:InfoPopup
+               :set completepopup=width:60,border:custom:─;│;─;│;┌;┐;┘;└
+<
+       When "align" is set to "item", the popup is positioned near the
+       selected item and moves as the selection changes.
+       When set to "menu", the popup aligns with the top of the menu (if the
+       menu appears below the text) or with the bottom (if it appears above).
+
+       The close button and resize handle are shown on the popup border only
+       when 'mouse' is enabled.
+
+       After creation, the info popup can be located with |popup_findinfo()|
+       and modified using |popup_setoptions()|.  See also: |complete-popup|.
 
                                                *'completeslash'* *'csl'*
 'completeslash' 'csl'  string  (default: "")
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 90cd5c4c7..ef7762253 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -41717,6 +41717,7 @@ Options: ~
   to ">" by default, indicating text that extends beyond the window width.
 - 'guioptions': New value |'go-C'| to style the title/caption bar on Windows 11
   (see also the below platform specific change).
+- 'completepopup': Add more values to style popup windows.
 
 Ex commands: ~
 - allow to specify a priority when defining a new sign |:sign-define|
diff --git a/src/optiondefs.h b/src/optiondefs.h
index a7b7f2e7e..b5c649f27 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -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,
diff --git a/src/optionstr.c b/src/optionstr.c
index b50aed547..6bfb75253 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -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_on_off_values,
+                   ARRAY_LENGTH(p_popup_option_on_off_values) - 1,
+                   numMatches,
+                   matches);
+       }
+       if (is_border)
        {
            return expand_set_opt_string(
                    args,
-                   p_popup_option_border_values,
-                   ARRAY_LENGTH(p_popup_option_border_values) - 1,
+                   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)
diff --git a/src/popupwin.c b/src/popupwin.c
index 9edd2fb63..96f669a50 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -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,29 +1865,160 @@ 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 (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);
 
-           if (!on && !off)
+                   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)
        {
            // Note: Keep this in sync with p_popup_option_align_values.
@@ -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
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index 4e04d696f..e5af863cc 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -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);
diff --git a/src/structs.h b/src/structs.h
index 711c12387..c64ca8254 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_9.dump 
b/src/testdir/dumps/Test_popupwin_infopopup_9.dump
new file mode 100644
index 000000000..e28549000
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_9.dump
@@ -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
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 363d93ea0..9fe790c4e 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -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', @:)
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index 129e14cd8..4b616a8a4 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index e9ff82a99..6e8ea1bdf 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1856,
 /**/
     1855,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1v8kFi-00EkJk-7b%40256bit.org.

Raspunde prin e-mail lui