patch 9.2.0080: popup: a few redrawing problems

Commit: 
https://github.com/vim/vim/commit/cded5e22058e4f0bb54077a65c130164e3528283
Author: Yasuhiro Matsumoto <[email protected]>
Date:   Sat Feb 28 17:28:25 2026 +0000

    patch 9.2.0080: popup: a few redrawing problems
    
    Problem:   Popup windows leave ghost images when moved. Visual options
               do not trigger a redraw when updated via popup_setoptions().
               An empty borderhighlight list fails to clear existing
               highlights.
    Solution:  Modify f_popup_move() in src/popupwin.c to save the old
               position before moving and force a redraw. Enhance
               f_popup_setoptions() to trigger a redraw when
               visual-affecting options change. Modify
               apply_general_options() to explicitly clear border
               highlights when an empty list is provided
               (Yasuhiro Matsumoto).
    
    closes: #19297
    
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/drawscreen.c b/src/drawscreen.c
index 112484081..e5de9abf1 100644
--- a/src/drawscreen.c
+++ b/src/drawscreen.c
@@ -97,6 +97,9 @@ update_screen(int type_arg)
 #endif
     int                no_update = FALSE;
     int                save_pum_will_redraw = pum_will_redraw;
+#ifdef FEAT_PROP_POPUP
+    int                did_redraw_window = FALSE;
+#endif
 
     // Don't do anything if the screen structures are (not yet) valid.
     if (!screen_valid(TRUE))
@@ -319,6 +322,9 @@ update_screen(int type_arg)
        if (wp->w_redr_type != 0)
        {
            cursor_off();
+#ifdef FEAT_PROP_POPUP
+           did_redraw_window = TRUE;
+#endif
 #ifdef FEAT_GUI
            if (!did_one)
            {
@@ -363,7 +369,8 @@ update_screen(int type_arg)
 
 #ifdef FEAT_PROP_POPUP
     // Display popup windows on top of the windows and command line.
-    update_popups(win_update);
+    if (did_redraw_window || popup_need_redraw())
+       update_popups(win_update);
 #endif
 
 #ifdef FEAT_TERMINAL
diff --git a/src/highlight.c b/src/highlight.c
index 0c4fc0560..f4d6867aa 100644
--- a/src/highlight.c
+++ b/src/highlight.c
@@ -3167,14 +3167,15 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, 
int blend_fg UNUSED)
            {
                if (blend_fg)
                {
-                   // blend_fg=TRUE: blend bg text fg from popup bg color to 
white
-                   // At blend=0: fg becomes popup bg (blue, invisible - 
opaque popup)
-                   // At blend=100: fg is white (visible - transparent popup)
-                   // Always use white (0xFFFFFF) as the target color for 
consistency
+                   // blend_fg=TRUE: fade underlying text toward popup bg.
                    if (popup_aep->ae_u.gui.bg_color != INVALCOLOR)
                    {
+                       int base_fg = 0xFFFFFF;
+                       if (char_aep != NULL
+                               && char_aep->ae_u.gui.fg_color != INVALCOLOR)
+                           base_fg = char_aep->ae_u.gui.fg_color;
                        new_en.ae_u.gui.fg_color = blend_colors(
-                               popup_aep->ae_u.gui.bg_color, 0xFFFFFF, blend);
+                               base_fg, popup_aep->ae_u.gui.bg_color, blend);
                    }
                }
                else if (popup_aep->ae_u.gui.fg_color != INVALCOLOR)
@@ -3230,12 +3231,15 @@ hl_blend_attr(int char_attr, int popup_attr, int blend, 
int blend_fg UNUSED)
                // Blend RGB colors for termguicolors mode
                if (blend_fg)
                {
-                   // blend_fg=TRUE: blend bg text fg from popup bg color to 
white
-                   // Always use white (0xFFFFFF) as the target color for 
consistency
+                   // blend_fg=TRUE: fade underlying text toward popup bg.
                    if (popup_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
                    {
+                       int base_fg = 0xFFFFFF;
+                       if (char_aep != NULL
+                               && char_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
+                           base_fg = char_aep->ae_u.cterm.fg_rgb;
                        new_en.ae_u.cterm.fg_rgb = blend_colors(
-                               popup_aep->ae_u.cterm.bg_rgb, 0xFFFFFF, blend);
+                               base_fg, popup_aep->ae_u.cterm.bg_rgb, blend);
                    }
                }
                else if (popup_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
diff --git a/src/popupwin.c b/src/popupwin.c
index 267ffb6ff..e49a86348 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -852,23 +852,36 @@ apply_general_options(win_T *wp, dict_T *dict)
            int         i;
 
            CHECK_LIST_MATERIALIZE(list);
-           for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
-                                                    ++i, li = li->li_next)
+           wp->w_border_highlight_isset = TRUE;
+           // Clear all highlights if list is empty
+           if (list->lv_len == 0)
            {
-               str = tv_get_string(&li->li_tv);
-               if (*str != NUL)
+               for (i = 0; i < 4; ++i)
                {
                    vim_free(wp->w_border_highlight[i]);
-                   wp->w_border_highlight[i] = vim_strsave(str);
+                   wp->w_border_highlight[i] = NULL;
                }
            }
-           if (list->lv_len == 1 && wp->w_border_highlight[0] != NULL)
-               for (i = 1; i < 4; ++i)
+           else
+           {
+               for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
+                                                    ++i, li = li->li_next)
                {
-                   vim_free(wp->w_border_highlight[i]);
-                   wp->w_border_highlight[i] =
-                                       vim_strsave(wp->w_border_highlight[0]);
+                   str = tv_get_string(&li->li_tv);
+                   if (*str != NUL)
+                   {
+                       vim_free(wp->w_border_highlight[i]);
+                       wp->w_border_highlight[i] = vim_strsave(str);
+                   }
                }
+               if (list->lv_len == 1 && wp->w_border_highlight[0] != NULL)
+                   for (i = 1; i < 4; ++i)
+                   {
+                       vim_free(wp->w_border_highlight[i]);
+                       wp->w_border_highlight[i] =
+                                       vim_strsave(wp->w_border_highlight[0]);
+                   }
+           }
        }
     }
 
@@ -2180,6 +2193,23 @@ popup_redraw_all(void)
        wp->w_redr_type = UPD_NOT_VALID;
 }
 
+/*
+ * Return TRUE if any visible popup window needs a redraw.
+ */
+    int
+popup_need_redraw(void)
+{
+    win_T      *wp;
+
+    FOR_ALL_POPUPWINS(wp)
+       if ((wp->w_popup_flags & POPF_HIDDEN) == 0 && wp->w_redr_type != 0)
+           return TRUE;
+    FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
+       if ((wp->w_popup_flags & POPF_HIDDEN) == 0 && wp->w_redr_type != 0)
+           return TRUE;
+    return FALSE;
+}
+
 /*
  * Set the color for a notification window.
  */
@@ -3238,6 +3268,10 @@ f_popup_move(typval_T *argvars, typval_T *rettv UNUSED)
     dict_T     *dict;
     int                id;
     win_T      *wp;
+    int                old_winrow;
+    int                old_wincol;
+    int                old_height;
+    int                old_width;
 
     if (in_vim9script()
            && (check_for_number_arg(argvars, 0) == FAIL
@@ -3253,11 +3287,22 @@ f_popup_move(typval_T *argvars, typval_T *rettv UNUSED)
        return;
     dict = argvars[1].vval.v_dict;
 
+    // Save old position for redrawing
+    old_winrow = wp->w_winrow;
+    old_wincol = wp->w_wincol;
+    old_height = wp->w_height;
+    old_width = wp->w_width;
+
     apply_move_options(wp, dict);
 
     if (wp->w_winrow + wp->w_height >= cmdline_row)
        clear_cmdline = TRUE;
     popup_adjust_position(wp);
+
+    // Redraw the old position to clear ghost images
+    if (old_winrow != wp->w_winrow || old_wincol != wp->w_wincol
+           || old_height != wp->w_height || old_width != wp->w_width)
+       redraw_all_later(UPD_NOT_VALID);
 }
 
 /*
@@ -3273,6 +3318,13 @@ f_popup_setoptions(typval_T *argvars, typval_T *rettv 
UNUSED)
 #ifdef FEAT_PROP_POPUP
     int                old_blend;
 #endif
+    int                old_zindex;
+    int                old_popup_flags;
+    char_u     *old_scrollbar_highlight;
+    char_u     *old_thumb_highlight;
+    char_u     *old_border_highlight[4];
+    int                need_redraw = FALSE;
+    int                i;
 
     if (in_vim9script()
            && (check_for_number_arg(argvars, 0) == FAIL
@@ -3291,10 +3343,34 @@ f_popup_setoptions(typval_T *argvars, typval_T *rettv 
UNUSED)
 #ifdef FEAT_PROP_POPUP
     old_blend = wp->w_popup_blend;
 #endif
+    old_zindex = wp->w_zindex;
+    old_popup_flags = wp->w_popup_flags;
+    old_scrollbar_highlight = wp->w_scrollbar_highlight;
+    old_thumb_highlight = wp->w_thumb_highlight;
+    for (i = 0; i < 4; i++)
+       old_border_highlight[i] = wp->w_border_highlight[i];
 
     (void)apply_options(wp, dict, FALSE);
 
+    // Check if visual options changed and redraw if needed
     if (old_firstline != wp->w_firstline)
+       need_redraw = TRUE;
+    if (old_zindex != wp->w_zindex)
+       need_redraw = TRUE;
+    if (old_popup_flags != wp->w_popup_flags)
+       need_redraw = TRUE;
+    if (old_scrollbar_highlight != wp->w_scrollbar_highlight)
+       need_redraw = TRUE;
+    if (old_thumb_highlight != wp->w_thumb_highlight)
+       need_redraw = TRUE;
+    for (i = 0; i < 4; i++)
+       if (old_border_highlight[i] != wp->w_border_highlight[i])
+       {
+           need_redraw = TRUE;
+           break;
+       }
+
+    if (need_redraw)
        redraw_win_later(wp, UPD_NOT_VALID);
 #ifdef FEAT_PROP_POPUP
     // Force redraw if opacity value changed
@@ -3432,7 +3508,9 @@ get_borderhighlight(dict_T *dict, win_T *wp)
     for (i = 0; i < 4; ++i)
        if (wp->w_border_highlight[i] != NULL)
            break;
-    if (i == 4)
+    // Only include "borderhighlight" if it was explicitly set (even if empty)
+    // or if at least one highlight is set.
+    if (i == 4 && !wp->w_border_highlight_isset)
        return;
 
     list = list_alloc();
@@ -3440,6 +3518,9 @@ get_borderhighlight(dict_T *dict, win_T *wp)
        return;
 
     dict_add_list(dict, "borderhighlight", list);
+    // When all highlights are NULL (cleared to empty list), return empty list.
+    if (i == 4)
+       return;
     for (i = 0; i < 4; ++i)
        list_append_string(list, wp->w_border_highlight[i], -1);
 }
@@ -4240,11 +4321,68 @@ may_update_popup_position(void)
        popup_adjust_position(curwin);
 }
 
+#ifdef FEAT_PROP_POPUP
+static schar_T *base_screenlines = NULL;
+static int *base_screenattrs = NULL;
+static u8char_T *base_screenlinesuc = NULL;
+static int base_screen_rows = 0;
+static int base_screen_cols = 0;
+
+/*
+ * Get the base screen cell saved before drawing opacity popups.
+ * Returns TRUE if the cell is available.
+ */
+    int
+popup_get_base_screen_cell(int row, int col, schar_T *linep, int *attrp,
+                                                         u8char_T *ucp)
+{
+    if (base_screenlines == NULL || base_screenattrs == NULL)
+       return FALSE;
+    if (row < 0 || col < 0 || row >= base_screen_rows
+                                                    || col >= base_screen_cols)
+       return FALSE;
+
+    int off = row * base_screen_cols + col;
+    if (linep != NULL)
+       *linep = base_screenlines[off];
+    if (attrp != NULL)
+       *attrp = base_screenattrs[off];
+    if (ucp != NULL)
+    {
+       if (enc_utf8 && base_screenlinesuc != NULL)
+           *ucp = base_screenlinesuc[off];
+       else
+           *ucp = 0;
+    }
+    return TRUE;
+}
+
+/*
+ * Set the base screen cell saved before drawing opacity popups.
+ * Used to update the snapshot after blending a layer.
+ */
+    void
+popup_set_base_screen_cell(int row, int col, schar_T line, int attr, u8char_T 
uc)
+{
+    if (base_screenlines == NULL || base_screenattrs == NULL)
+       return;
+    if (row < 0 || col < 0 || row >= base_screen_rows
+                                                    || col >= base_screen_cols)
+       return;
+
+    int off = row * base_screen_cols + col;
+    base_screenlines[off] = line;
+    base_screenattrs[off] = attr;
+    if (enc_utf8 && base_screenlinesuc != NULL)
+       base_screenlinesuc[off] = uc;
+}
+#endif
+
 /*
  * Draw a single padding cell with opacity blending.
  * Restores background from saved data and blends with popup attribute.
  */
-    static void
+static void
 draw_opacity_padding_cell(
        int             row,
        int             col,
@@ -4254,7 +4392,9 @@ draw_opacity_padding_cell(
        int             save_start_row,
        int             save_start_col,
        int             save_rows,
-       int             save_cols)
+       int             save_cols,
+       int             pad_start_col,
+       int             pad_end_col)
 {
     int off = LineOffset[row] + col;
     int r = row - save_start_row;
@@ -4263,14 +4403,165 @@ draw_opacity_padding_cell(
     if (r >= 0 && r < save_rows && c >= 0 && c < save_cols)
     {
        int save_off = r * save_cols + c;
+       // If this is the second cell of a wide background character, blend
+       // the wide character instead of overwriting it.
+       if (enc_utf8 && saved_screenlinesuc != NULL)
+       {
+           int base_col = col - 1;
+           int base_off = off - 1;
+           int base_save_off = save_off - 1;
+           int wide_prev = FALSE;
+
+           // Prefer current screen state for detecting a wide char, since the
+           // saved data may not contain a reliable right-half marker.
+           if (base_off >= 0 && ScreenLines != NULL)
+           {
+               if (ScreenLinesUC != NULL
+                       && ScreenLinesUC[base_off] != 0
+                       && utf_char2cells(ScreenLinesUC[base_off]) == 2
+                       && ScreenLines[off] == 0)
+                   wide_prev = TRUE;
+           }
+           if (!wide_prev && save_off > 0)
+           {
+               if (saved_screenlinesuc[save_off - 1] != 0
+                       && utf_char2cells(saved_screenlinesuc[save_off - 1]) == 
2
+                       && saved_screenlines[save_off] == 0)
+                   wide_prev = TRUE;
+           }
+
+           if (wide_prev && base_col >= 0)
+           {
+               // If the wide character starts outside the padding area, do not
+               // overwrite it. Use the base screen cell if available.
+               if (base_col < pad_start_col)
+               {
+                   if (ScreenLinesUC != NULL
+                           && ScreenLinesUC[base_off] != 0
+                           && utf_char2cells(ScreenLinesUC[base_off]) == 2)
+                   {
+                       // The left half still has the wide char on screen.
+                       // Clear it to a space.
+                       ScreenLines[base_off] = ' ';
+                       ScreenLinesUC[base_off] = 0;
+                       ScreenAttrs[base_off] = 
saved_screenattrs[base_save_off];
+                       screen_char(base_off, row, base_col);
+
+                       // Draw padding in the right half.
+                       ScreenLines[off] = ' ';
+                       ScreenAttrs[off] = saved_screenattrs[save_off];
+                       if (enc_utf8)
+                           ScreenLinesUC[off] = 0;
+                       int popup_attr_val =
+                                       get_wcr_attr(screen_opacity_popup);
+                       int blend = screen_opacity_popup->w_popup_blend;
+                       ScreenAttrs[off] = hl_blend_attr(ScreenAttrs[off],
+                                       popup_attr_val, blend, TRUE);
+                       popup_set_base_screen_cell(row, col,
+                               ScreenLines[off], ScreenAttrs[off],
+                               ScreenLinesUC[off]);
+                       screen_char(off, row, col);
+                       return;
+                   }
+
+                   // screen_line() already cleared the base cell (popup
+                   // content was a space).  Restore the full wide char from
+                   // saved background so it shows through with opacity.
+                   if (base_save_off >= 0
+                           && saved_screenlinesuc != NULL
+                           && saved_screenlinesuc[base_save_off] != 0
+                           && utf_char2cells(
+                               saved_screenlinesuc[base_save_off]) == 2)
+                   {
+                       ScreenLines[base_off] =
+                                       saved_screenlines[base_save_off];
+                       ScreenLinesUC[base_off] =
+                                       saved_screenlinesuc[base_save_off];
+                       ScreenLines[off] = saved_screenlines[save_off];
+                       ScreenLinesUC[off] = saved_screenlinesuc[save_off];
+                       ScreenAttrs[base_off] =
+                                       saved_screenattrs[base_save_off];
+                       ScreenAttrs[off] = saved_screenattrs[save_off];
+
+                       int popup_attr_val =
+                                       get_wcr_attr(screen_opacity_popup);
+                       int blend = screen_opacity_popup->w_popup_blend;
+                       ScreenAttrs[base_off] = hl_blend_attr(
+                               ScreenAttrs[base_off],
+                               popup_attr_val, blend, TRUE);
+                       ScreenAttrs[off] = ScreenAttrs[base_off];
+                       popup_set_base_screen_cell(row, base_col,
+                               ScreenLines[base_off],
+                               ScreenAttrs[base_off],
+                               ScreenLinesUC[base_off]);
+                       popup_set_base_screen_cell(row, col,
+                               ScreenLines[off], ScreenAttrs[off],
+                               ScreenLinesUC[off]);
+                       screen_char(base_off, row, base_col);
+                       return;
+                   }
+
+                   // Draw padding in the right half.
+                   ScreenLines[off] = ' ';
+                   ScreenAttrs[off] = saved_screenattrs[save_off];
+                   if (enc_utf8 && ScreenLinesUC != NULL)
+                       ScreenLinesUC[off] = 0;
+                   int popup_attr_val = get_wcr_attr(screen_opacity_popup);
+                   int blend = screen_opacity_popup->w_popup_blend;
+                   ScreenAttrs[off] = hl_blend_attr(ScreenAttrs[off],
+                                   popup_attr_val, blend, TRUE);
+                   popup_set_base_screen_cell(row, col, ScreenLines[off],
+                                              ScreenAttrs[off], 
ScreenLinesUC[off]);
+                   screen_char(off, row, col);
+                   return;
+               }
+
+               // Base cell is inside the saved area, redraw the wide char.
+               if (save_off > 0)
+               {
+                   ScreenLines[base_off] = saved_screenlines[base_save_off];
+                   ScreenAttrs[base_off] = saved_screenattrs[base_save_off];
+                   ScreenLines[off] = saved_screenlines[save_off];
+                   ScreenAttrs[off] = saved_screenattrs[save_off];
+                   ScreenLinesUC[base_off] = 
saved_screenlinesuc[base_save_off];
+                   ScreenLinesUC[off] = saved_screenlinesuc[save_off];
+
+                   int popup_attr_val = get_wcr_attr(screen_opacity_popup);
+                   int blend = screen_opacity_popup->w_popup_blend;
+                   ScreenAttrs[base_off] = hl_blend_attr(ScreenAttrs[base_off],
+                                   popup_attr_val, blend, TRUE);
+                   ScreenAttrs[off] = ScreenAttrs[base_off];
+                   popup_set_base_screen_cell(row, base_col, 
ScreenLines[base_off],
+                                              ScreenAttrs[base_off], 
ScreenLinesUC[base_off]);
+                   popup_set_base_screen_cell(row, col, ScreenLines[off],
+                                              ScreenAttrs[off], 
ScreenLinesUC[off]);
+                   screen_char(base_off, row, base_col);
+               }
+               return;
+           }
+       }
        ScreenLines[off] = saved_screenlines[save_off];
        ScreenAttrs[off] = saved_screenattrs[save_off];
        if (enc_utf8 && saved_screenlinesuc != NULL)
            ScreenLinesUC[off] = saved_screenlinesuc[save_off];
+
+       // If the saved character is wide and would extend past the padding
+       // area into the content area, replace with a space to avoid
+       // corrupting the content.
+       if (enc_utf8 && ScreenLinesUC[off] != 0
+               && utf_char2cells(ScreenLinesUC[off]) == 2
+               && col + 1 >= pad_end_col)
+       {
+           ScreenLines[off] = ' ';
+           ScreenLinesUC[off] = 0;
+       }
+
        int popup_attr_val = get_wcr_attr(screen_opacity_popup);
        int blend = screen_opacity_popup->w_popup_blend;
        ScreenAttrs[off] = hl_blend_attr(ScreenAttrs[off],
                                popup_attr_val, blend, TRUE);
+       popup_set_base_screen_cell(row, col, ScreenLines[off],
+                                  ScreenAttrs[off], ScreenLinesUC[off]);
        screen_char(off, row, col);
     }
 }
@@ -4278,7 +4569,7 @@ draw_opacity_padding_cell(
 /*
  * Fill a rectangular padding area with opacity blending.
  */
-    static void
+static void
 fill_opacity_padding(
        int             start_row,
        int             end_row,
@@ -4296,7 +4587,8 @@ fill_opacity_padding(
        for (int pad_col = start_col; pad_col < end_col; pad_col++)
            draw_opacity_padding_cell(pad_row, pad_col,
                    saved_screenlines, saved_screenattrs, saved_screenlinesuc,
-                   save_start_row, save_start_col, save_rows, save_cols);
+                   save_start_row, save_start_col, save_rows, save_cols,
+                   start_col, end_col);
 }
 
 /*
@@ -4333,6 +4625,25 @@ update_popups(void (*win_update)(win_T *wp))
     // so that the window with a higher zindex is drawn later, thus goes on
     // top.
     popup_reset_handled(POPUP_HANDLED_5);
+#ifdef FEAT_PROP_POPUP
+    if (base_screenlines != NULL)
+    {
+       vim_free(base_screenlines);
+       base_screenlines = NULL;
+    }
+    if (base_screenattrs != NULL)
+    {
+       vim_free(base_screenattrs);
+       base_screenattrs = NULL;
+    }
+    if (base_screenlinesuc != NULL)
+    {
+       vim_free(base_screenlinesuc);
+       base_screenlinesuc = NULL;
+    }
+    base_screen_rows = 0;
+    base_screen_cols = 0;
+#endif
     while ((wp = find_next_popup(TRUE, POPUP_HANDLED_5)) != NULL)
     {
        int         title_len = 0;
@@ -4350,6 +4661,54 @@ update_popups(void (*win_update)(win_T *wp))
        else
            screen_opacity_popup = NULL;
 
+#ifdef FEAT_PROP_POPUP
+       if (screen_opacity_popup != NULL)
+       {
+           if (base_screenlines != NULL)
+           {
+               vim_free(base_screenlines);
+               base_screenlines = NULL;
+           }
+           if (base_screenattrs != NULL)
+           {
+               vim_free(base_screenattrs);
+               base_screenattrs = NULL;
+           }
+           if (base_screenlinesuc != NULL)
+           {
+               vim_free(base_screenlinesuc);
+               base_screenlinesuc = NULL;
+           }
+
+           base_screen_rows = screen_Rows;
+           base_screen_cols = screen_Columns;
+           base_screenlines = ALLOC_MULT(schar_T,
+                                   base_screen_rows * base_screen_cols);
+           base_screenattrs = ALLOC_MULT(int,
+                                   base_screen_rows * base_screen_cols);
+           if (enc_utf8)
+               base_screenlinesuc = ALLOC_MULT(u8char_T,
+                                   base_screen_rows * base_screen_cols);
+
+           if (base_screenlines != NULL && base_screenattrs != NULL)
+           {
+               for (int r = 0; r < base_screen_rows; r++)
+               {
+                   int off = LineOffset[r];
+                   int base_off = r * base_screen_cols;
+                   for (int c = 0; c < base_screen_cols; c++)
+                   {
+                       base_screenlines[base_off + c] = ScreenLines[off + c];
+                       base_screenattrs[base_off + c] = ScreenAttrs[off + c];
+                       if (enc_utf8 && base_screenlinesuc != NULL)
+                           base_screenlinesuc[base_off + c] =
+                                       ScreenLinesUC[off + c];
+                   }
+               }
+           }
+       }
+#endif
+
        // Save background ScreenLines for padding opacity.
        // We need to save it before win_update() overwrites it.
        schar_T *saved_screenlines = NULL;
@@ -4370,6 +4729,14 @@ update_popups(void (*win_update)(win_T *wp))
            save_rows = wp->w_popup_padding[0] + wp->w_height + 
wp->w_popup_padding[2];
            save_cols = wp->w_popup_padding[3] + wp->w_width + 
wp->w_popup_padding[1];
 
+           // Include one column to the left to handle wide chars that overlap
+           // the padding boundary.
+           if (save_start_col > 0)
+           {
+               --save_start_col;
+               ++save_cols;
+           }
+
            // Allocate buffers
            saved_screenlines = ALLOC_MULT(schar_T, save_rows * save_cols);
            saved_screenattrs = ALLOC_MULT(int, save_rows * save_cols);
@@ -4686,8 +5053,16 @@ update_popups(void (*win_update)(win_T *wp))
                    col = 0;
                }
                if (pad_left > 0)
-                   screen_fill(row, row + 1, col, col + pad_left,
+               {
+                   if (screen_opacity_popup != NULL && saved_screenlines != 
NULL)
+                       fill_opacity_padding(row, row + 1, col, col + pad_left,
+                               saved_screenlines, saved_screenattrs,
+                               saved_screenlinesuc, save_start_row,
+                               save_start_col, save_rows, save_cols);
+                   else
+                       screen_fill(row, row + 1, col, col + pad_left,
                                                         ' ', ' ', popup_attr);
+               }
            }
            // scrollbar
            if (wp->w_has_scrollbar)
@@ -4717,7 +5092,13 @@ update_popups(void (*win_update)(win_T *wp))
                        + wp->w_popup_padding[3] + wp->w_width + wp->w_leftcol;
                int pad_col_end = pad_col_start + wp->w_popup_padding[1];
 
-               screen_fill(row, row + 1, pad_col_start, pad_col_end,
+               if (screen_opacity_popup != NULL && saved_screenlines != NULL)
+                   fill_opacity_padding(row, row + 1, pad_col_start, 
pad_col_end,
+                           saved_screenlines, saved_screenattrs,
+                           saved_screenlinesuc, save_start_row, save_start_col,
+                           save_rows, save_cols);
+               else
+                   screen_fill(row, row + 1, pad_col_start, pad_col_end,
                                                             ' ', ' ', 
popup_attr);
            }
        }
@@ -4805,6 +5186,26 @@ update_popups(void (*win_update)(win_T *wp))
 #endif
     }
 
+#ifdef FEAT_PROP_POPUP
+    if (base_screenlines != NULL)
+    {
+       vim_free(base_screenlines);
+       base_screenlines = NULL;
+    }
+    if (base_screenattrs != NULL)
+    {
+       vim_free(base_screenattrs);
+       base_screenattrs = NULL;
+    }
+    if (base_screenlinesuc != NULL)
+    {
+       vim_free(base_screenlinesuc);
+       base_screenlinesuc = NULL;
+    }
+    base_screen_rows = 0;
+    base_screen_cols = 0;
+#endif
+
 #if defined(FEAT_SEARCH_EXTRA)
     // In case win_update() called start_search_hl().
     end_search_hl();
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index e92d5eb21..ee8efbb14 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -16,6 +16,7 @@ int parse_completepopup(win_T *wp);
 void popup_set_wantpos_cursor(win_T *wp, int width, dict_T *d);
 void popup_set_wantpos_rowcol(win_T *wp, int row, int col);
 void popup_redraw_all(void);
+int popup_need_redraw(void);
 void f_popup_clear(typval_T *argvars, typval_T *rettv);
 void f_popup_create(typval_T *argvars, typval_T *rettv);
 void f_popup_atcursor(typval_T *argvars, typval_T *rettv);
@@ -53,6 +54,8 @@ int popup_no_mapping(void);
 void popup_check_cursor_pos(void);
 void may_update_popup_mask(int type);
 void may_update_popup_position(void);
+int popup_get_base_screen_cell(int row, int col, schar_T *linep, int *attrp, 
u8char_T *ucp);
+void popup_set_base_screen_cell(int row, int col, schar_T line, int attr, 
u8char_T uc);
 void update_popups(void (*win_update)(win_T *wp));
 int set_ref_in_popups(int copyID);
 int popup_is_popup(win_T *wp);
diff --git a/src/screen.c b/src/screen.c
index fa686d2b4..905f18499 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -627,6 +627,7 @@ screen_line(
                && ScreenLines[off_from] == ' '
                && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
                && ScreenLines[off_to] == 0
+               && (!enc_utf8 || ScreenLinesUC[off_to] == 0)
                && off_to > 0
                && enc_utf8 && ScreenLinesUC[off_to - 1] != 0
                && utf_char2cells(ScreenLinesUC[off_to - 1]) == 2)
@@ -639,7 +640,8 @@ screen_line(
                && (flags & SLF_POPUP)
                && ScreenLines[off_from] == ' '
                && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
-               && ScreenLines[off_to] != 0)
+               && (ScreenLines[off_to] != 0
+                   || (enc_utf8 && ScreenLinesUC[off_to] != 0)))
        {
            int bg_char_cells = 1;
            if (enc_utf8 && ScreenLinesUC[off_to] != 0)
@@ -652,8 +654,23 @@ screen_line(
            {
                if (col + 1 >= endcol || off_from + 1 >= max_off_from
                                                   || off_to + 1 >= max_off_to)
-                   // At the edge of the screen, skip wide char.
+               {
+                   // Wide char doesn't fit at the edge.  Replace with a
+                   // blended space so opacity is still applied.
+                   int char_attr = ScreenAttrs[off_from];
+                   int popup_attr = get_wcr_attr(screen_opacity_popup);
+                   int combined = hl_combine_attr(popup_attr, char_attr);
+                   int blend = screen_opacity_popup->w_popup_blend;
+                   ScreenLines[off_to] = ' ';
+                   if (enc_utf8)
+                       ScreenLinesUC[off_to] = 0;
+                   ScreenAttrs[off_to] = hl_blend_attr(ScreenAttrs[off_to],
+                                                   combined, blend, TRUE);
+                   screen_char(off_to, row, col + coloff);
+                   opacity_blank = TRUE;
+                   redraw_this = FALSE;
                    goto skip_opacity;
+               }
                int next_off_from = off_from + 1;
                if (!(ScreenLines[next_off_from] == ' '
                        && (!enc_utf8 || ScreenLinesUC[next_off_from] == 0)))
@@ -681,6 +698,30 @@ screen_line(
                ScreenAttrs[off_to + 1] = ScreenAttrs[off_to];
            redraw_this = FALSE;
        }
+       // When a popup space overlaps the second half of a destroyed wide
+       // character (e.g., the first half was overwritten by popup content),
+       // the underlying cell has ScreenLines == 0 and no valid wide char
+       // at the previous cell.  Apply opacity blending so that the cell
+       // matches surrounding opacity-blended cells instead of appearing
+       // as a solid-colored gap.
+       else if (screen_opacity_popup != NULL
+               && (flags & SLF_POPUP)
+               && ScreenLines[off_from] == ' '
+               && (!enc_utf8 || ScreenLinesUC[off_from] == 0)
+               && ScreenLines[off_to] == 0
+               && (!enc_utf8 || ScreenLinesUC[off_to] == 0))
+       {
+           int char_attr = ScreenAttrs[off_from];
+           int popup_attr = get_wcr_attr(screen_opacity_popup);
+           int combined = hl_combine_attr(popup_attr, char_attr);
+           int blend = screen_opacity_popup->w_popup_blend;
+           ScreenLines[off_to] = ' ';
+           ScreenAttrs[off_to] = hl_blend_attr(ScreenAttrs[off_to],
+                                               combined, blend, TRUE);
+           screen_char(off_to, row, col + coloff);
+           opacity_blank = TRUE;
+           redraw_this = FALSE;
+       }
 skip_opacity:
 #endif
 
@@ -784,7 +825,11 @@ skip_opacity:
                }
            }
            if (char_cells == 2)
+           {
                ScreenLines[off_to + 1] = ScreenLines[off_from + 1];
+               if (enc_utf8)
+                   ScreenLinesUC[off_to + 1] = 0;
+           }
 
 #if defined(FEAT_GUI) || defined(UNIX)
            // The bold trick makes a single column of pixels appear in the
diff --git a/src/structs.h b/src/structs.h
index d09726fe7..805d9ada3 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4085,6 +4085,7 @@ struct window_S
     int                w_popup_padding[4]; // popup padding top/right/bot/left
     int                w_popup_border[4];  // popup border top/right/bot/left
     char_u     *w_border_highlight[4];  // popup border highlight
+    int                w_border_highlight_isset; // borderhighlight was 
explicitly set
     int                w_border_char[8];   // popup border characters
     int                w_popup_shadow;     // popup shadow (right and bottom 
edges)
 
diff --git a/src/testdir/dumps/Test_popupwin_mask_5.dump 
b/src/testdir/dumps/Test_popupwin_mask_5.dump
index be2feddbf..9462c2397 100644
--- a/src/testdir/dumps/Test_popupwin_mask_5.dump
+++ b/src/testdir/dumps/Test_popupwin_mask_5.dump
@@ -10,4 +10,4 @@
 
|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
 | 
+0&#e0e0e08@11|1+0&#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|═+0#0000001#ffd7ff255@13|X|4+0#0000000#ffffff0|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
 |o+0&#e0e0e08|m|e| |t|e|x|t| 
@3|1+0&#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|║+0#0000001#ffd7ff255|
 @4|2+0#0000000#ffffff0|9|3| 
+0#0000001#ffd7ff255@6|║|4+0#0000000#ffffff0|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
-|n+0&#e0e0e08|o|t|h|e|r| |l|i|n|e| | +0&#ffffff0@28|║+0#0000001#ffd7ff255| 
|j|u|s|t| |o|n|e| |l|i|n|e| |║|,+0#0000000#ffffff0|1| @10|T|o|p| 
+|:| |t+0&#e0e0e08|h| +0&#ffffff0@2|l+0&#e0e0e08|i|n|e| | 
+0&#ffffff0@28|║+0#0000001#ffd7ff255| |j|u|s|t| 
+0#0000000#ffffff0@2|e+0#0000001#ffd7ff255| |l|i|n|e| |║|,+0#0000000#ffffff0|1| 
@10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump 
b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
new file mode 100644
index 000000000..9ec6d5ba6
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
@@ -0,0 +1,15 @@
+>い*0&#ffffff0|え|ー@15|い|!+&| |1| @3
+|い*&|え|ー@15|い|!+&| |2| @3
+|い*&|え|ー@15|い|!+&| |3| @3
+|い*&|え*0#ffffff16#e000002|ー@6| |ー*0#0000000#ffffff0@7|い|!+&| |4| @3
+|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| |ー*0#0000000#ffffff0@7|い|!+&| 
|5| @3
+|い*&| +0#ffffff16#e000002|ポ*&|ッ|プ|ア|ッ|プ|で|─+&|╮| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |6| @3
+|い*&| +0#ffffff16#e000002|最*&|上|川| +&|ぼ*&|赤|い|な|│+&| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |7| @3
+|い*&| +0#ffffff16#e000002|│|あ*&|い|う|え|お|ー@1|│+&| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |8| @3
+|い*&| +&|│+0#ffffff16#0000e05|ー*&@6|│+&| +0#0000000#ffffff0|ー*&@7|い|!+&| |9| @3
+|い*&| +&|╰+0#ffffff16#0000e05|─@13|╯| +0#0000000#ffffff0|ー*&@7|い|!+&| |1|0| @2
+|い*&|え|ー@15|い|!+&| |1@1| @2
+|い*&|え|ー@15|い|!+&| |1|2| @2
+|い*&|え|ー@15|い|!+&| |1|3| @2
+|い*&|え|ー@15|い|!+&| |1|4| @2
+@27|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_opacity_wide_2.dump 
b/src/testdir/dumps/Test_popupwin_opacity_wide_2.dump
new file mode 100644
index 000000000..50ca50354
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_opacity_wide_2.dump
@@ -0,0 +1,15 @@
+>い*0&#ffffff0|え|ー@15|い|!+&| |1| @3
+|い*&|え|ー@15|い|!+&| |2| @3
+|い*&|え|ー@15|い|!+&| |3| @3
+|い*&|え|ー@15|い|!+&| |4| @3
+|い*&|え|ー@15|い|!+&| |5| @3
+|い*&| +&|╭+0#ffffff16#0000e05|─@13|╮| +0#0000000#ffffff0|ー*&@7|い|!+&| |6| @3
+|い*&| +&|│+0#ffffff16#0000e05|あ*&|め|ん|ぼ|赤|い|な|│+&| 
+0#0000000#ffffff0|ー*&@7|い|!+&| |7| @3
+|い*&| +&|│+0#ffffff16#0000e05|あ*&|い|う|え|お| +&| +0&#e000002|ー*&|│+&| 
|ー*&@5|ー*0#0000000#ffffff0@1|い|!+&| |8| @3
+|い*&| +&|│+0#ffffff16#0000e05|ー*&@4| +&| 
+0&#e000002|カ*&|ラ|フ|ル|な|ー@2|ー*0#0000000#ffffff0@1|い|!+&| |9| @3
+|い*&| 
+&|╰+0#ffffff16#0000e05|─@10|─+0&#e000002|ポ*&|ッ|プ|ア|ッ|プ|で|ー|ー*0#0000000#ffffff0@1|い|!+&|
 |1|0| @2
+|い*&|え|ー@4| +&| +0#ffffff16#e000002|最*&|上|川|ー@4|ー*0#0000000#ffffff0@1|い|!+&| 
|1@1| @2
+|い*&|え|ー@4| +&| +0#ffffff16#e000002|ー*&@7|ー*0#0000000#ffffff0@1|い|!+&| |1|2| @2
+|い*&|え|ー@15|い|!+&| |1|3| @2
+|い*&|え|ー@15|い|!+&| |1|4| @2
+|:| @25|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index 4c41bf75d..5f87f34e1 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -176,6 +176,11 @@ func Test_popup_with_border_and_padding()
   call popup_setoptions(winid, options)
   call assert_equal(options, popup_getoptions(winid))
 
+  " Check that borderhighlight can be cleared with empty list
+  call popup_setoptions(winid, #{borderhighlight: []})
+  let options_cleared = popup_getoptions(winid)
+  call assert_equal([], options_cleared.borderhighlight)
+
   " Check that range() doesn't crash
   call popup_setoptions(winid, #{
        \ padding: range(1, 4),
@@ -4845,4 +4850,53 @@ func Test_popup_getwininfo_tabnr()
   tabonly
 endfunc
 
+func Test_popup_opacity_wide_char_overlap()
+  CheckScreendump
+
+  " Two overlapping popups with opacity over wide-character background.
+  " Verifies that wide characters at the content/padding boundary of the
+  " higher-zindex popup are properly blended (no holes or missing chars).
+  let lines =<< trim END
+    set encoding=utf-8
+    for i in range(1, 20)
+      call setline(i, 'いえーーーーーーーーーーーーーーーーい! ' .. i)
+    endfor
+    hi MyPopup1 ctermbg=darkblue ctermfg=white
+    hi MyPopup2 ctermbg=darkred ctermfg=white
+    let g:p1 = popup_create(['あめんぼ赤いな','あいうえお'], #{
+        \ opacity: 50,
+        \ line: 6,
+        \ col: 4,
+        \ border: [],
+        \ borderchars: ['─','│','─','│','╭','╮','╯','╰'],
+        \ minwidth: 14,
+        \ minheight: 3,
+        \ highlight: 'MyPopup1',
+        \ zindex: 1,
+        \})
+    let g:p2 = popup_create(['カラフルな','ポップアップで','最上川'], #{
+        \ opacity: 50,
+        \ line: 4,
+        \ col: 3,
+        \ minwidth: 15,
+        \ minheight: 3,
+        \ padding: [1,1,1,1],
+        \ highlight: 'MyPopup2',
+        \ zindex: 2,
+        \})
+  END
+  call writefile(lines, 'XtestPopupOpacityWide', 'D')
+  let buf = RunVimInTerminal('-S XtestPopupOpacityWide', #{rows: 15, cols: 45})
+  call VerifyScreenDump(buf, 'Test_popupwin_opacity_wide_1', {})
+
+  " Move popups far apart so they don't overlap.
+  " Tests right edge of popup where wide chars span content/padding boundary.
+  call term_sendkeys(buf, ":call popup_move(g:p2, #{line: 14, col: 16})\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":\<CR>")
+  call VerifyScreenDump(buf, 'Test_popupwin_opacity_wide_2', {})
+
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2
diff --git a/src/version.c b/src/version.c
index 51b853a2b..5e72c7842 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    80,
 /**/
     79,
 /**/

-- 
-- 
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/E1vwONE-0002uG-Hj%40256bit.org.

Raspunde prin e-mail lui